Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
20 changes: 19 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.22)
project(MCPServer.cpp LANGUAGES CXX C VERSION 1.0.5.0)

# Add option for Python support
option(ENABLE_PYTHON_PLUGINS "Enable Python plugin support" ON)

if(UNIX AND NOT APPLE)
set(CMAKE_CXX_VISIBILITY_PRESET default)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
Expand Down Expand Up @@ -89,11 +92,26 @@ add_subdirectory(src/metrics)
add_subdirectory(plugins)
add_subdirectory(tools)
add_subdirectory(third_party/miniz)
add_subdirectory(third_party/pybind11)
add_subdirectory(utils)
add_subdirectory(src/Resources)
add_subdirectory(src/routers)
add_subdirectory(src/Prompts)

# Conditionally add Python support
if(ENABLE_PYTHON_PLUGINS)
find_package(Python COMPONENTS Interpreter Development)
if(Python_FOUND)
message(STATUS "Python found: ${Python_VERSION}")
message(STATUS "Python executable: ${Python_EXECUTABLE}")
message(STATUS "Python include dirs: ${Python_INCLUDE_DIRS}")
message(STATUS "Python libraries: ${Python_LIBRARIES}")
else()
message(WARNING "Python not found. Disabling Python plugin support.")
set(ENABLE_PYTHON_PLUGINS OFF)
endif()
endif()

# turn off mimalloc tests and examples
set(MI_BUILD_TESTS OFF CACHE BOOL "Build mimalloc tests" FORCE)
set(MI_BUILD_EXAMPLES OFF CACHE BOOL "Build mimalloc examples" FORCE)
Expand Down Expand Up @@ -234,4 +252,4 @@ endif()

set_target_properties(generate_cert PROPERTIES
COMPILE_DEFINITIONS "OPENSSL_SUPPRESS_DEPRECATED"
)
)
10 changes: 8 additions & 2 deletions plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
add_subdirectory(official)
# plugins/CMakeLists.txt

# Add subdirectories

add_subdirectory(pluginhub)
add_subdirectory(sdk)
add_subdirectory(pluginhub)

# Add official plugins
add_subdirectory(official)
4 changes: 3 additions & 1 deletion plugins/official/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ add_subdirectory(http_plugin)

add_subdirectory(safe_system_plugin)

add_subdirectory(example_stream_plugin)
add_subdirectory(example_stream_plugin)

add_subdirectory(python_example_plugin)
50 changes: 50 additions & 0 deletions plugins/official/python_example_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# CMakeLists.txt for Python Example Plugin
cmake_minimum_required(VERSION 3.23)
project(python_example_plugin)

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Set the path to MCPServer++ root directory
set(MCP_SERVER_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../.." CACHE STRING "Path to MCPServer++ root directory")

# Find required packages
find_package(Python COMPONENTS Interpreter Development REQUIRED)

# Add the plugin library
add_library(${PROJECT_NAME} SHARED
# The pybind wrapper that exposes Python functions as C interface
${MCP_SERVER_ROOT}/plugins/sdk/pybind_module_plugin.cpp
)


# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
# MCPServer++ include directories
${MCP_SERVER_ROOT}/plugins/sdk
${MCP_SERVER_ROOT}/include
${MCP_SERVER_ROOT}/third_party/nlohmann
${MCP_SERVER_ROOT}/third_party/pybind11/include
)

# Add preprocessor definition for DLL export
target_compile_definitions(${PROJECT_NAME} PRIVATE MCPSERVER_API_EXPORTS)

# Link libraries
target_link_libraries(${PROJECT_NAME} PRIVATE
pybind11::embed
)

# Ensure the Python plugin file is available
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/python_example_plugin.py
${CMAKE_CURRENT_BINARY_DIR}/python_example_plugin.py
COPYONLY)

# Copy the Python file to the output directory after build
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/python_example_plugin.py
$<TARGET_FILE_DIR:${PROJECT_NAME}>/python_example_plugin.py
)
106 changes: 106 additions & 0 deletions plugins/official/python_example_plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Python Plugin for MCPServer++

This is an example Python plugin for MCPServer++. It demonstrates how to create a Python plugin that can be compiled into a DLL and loaded by MCPServer++.

## How it works

Python plugins work by creating a DLL that wraps the Python code with a C interface. The DLL contains the standard MCP plugin functions (`get_tools`, `call_tool`, `free_result`) which interact with the Python code through pybind11.

## Building the Plugin

1. Make sure you have the required dependencies installed:
- Python (3.6 or higher)
- CMake (3.23 or higher)
- A C++ compiler with C++17 support
- pybind11

2. Copy [python_plugin_CMakeLists.txt](file:///d:/codespace/MCPServer%2B%2B/plugins/sdk/python_plugin_CMakeLists.txt) from the SDK directory to this directory and rename it to `CMakeLists.txt`:
```bash
cp ../../sdk/python_plugin_CMakeLists.txt CMakeLists.txt
```

3. Edit `CMakeLists.txt` and set the correct path to your MCPServer++ root directory:
```cmake
set(MCP_SERVER_ROOT "/path/to/mcpserver++")
```

4. Create a build directory and build the plugin:
```bash
mkdir build
cd build
cmake ..
cmake --build . --config Release
```

5. The resulting DLL file can be placed in the plugins directory of your MCPServer++ installation.

## Plugin Structure

- `python_example_plugin.py` - The main Python plugin code
- `CMakeLists.txt` - Build configuration (copied from the SDK)

## Customizing the Plugin

To create your own Python plugin:

1. Copy this example directory and rename it
2. Modify `python_example_plugin.py` to implement your tools
3. Update the `get_tools()` function to return information about your tools
4. Update the `call_tool()` function to implement the functionality of your tools
5. Build the plugin following the steps above

## API Reference

### ToolInfo Class

The `ToolInfo` class represents a tool provided by the plugin:

```python
class ToolInfo:
def __init__(self, name, description, parameters, is_streaming=False):
self.name = name # Tool name
self.description = description # Tool description
self.parameters = parameters # JSON Schema string describing the parameters
self.is_streaming = is_streaming # Whether the tool is streaming
```

### get_tools() Function

This function should return a list of `ToolInfo` objects describing the tools provided by the plugin:

```python
def get_tools():
return [
ToolInfo(
name="tool_name",
description="Tool description",
parameters='{"type": "object", "properties": {...}}'
)
]
```

### call_tool() Function

This function is called when one of the plugin's tools is invoked:

```python
def call_tool(name, args_json):
# Parse the arguments
args = json.loads(args_json)

# Implement tool functionality
if name == "tool_name":
# Process the tool call
result = {"result": "tool result"}
return json.dumps(result)

# Handle unknown tools
return json.dumps({"error": {"type": "unknown_tool", "message": f"Unknown tool: {name}"}})
```

## Notes

- The plugin must be compiled into a DLL to be loaded by MCPServer++
- The Python interpreter is embedded in the DLL, so the plugin can run without an external Python installation
- All data exchange between the C++ host and Python code happens through JSON strings
- Error handling should be done carefully to prevent crashes
69 changes: 69 additions & 0 deletions plugins/official/python_example_plugin/build_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
Build script for Python plugins
This script simplifies the process of building Python plugins into DLLs
"""

import os
import sys
import subprocess
import argparse
from pathlib import Path

def build_plugin(plugin_dir, build_dir="build"):
"""
Build a Python plugin into a DLL

Args:
plugin_dir (str): Path to the plugin directory
build_dir (str): Name of the build directory
"""

plugin_path = Path(plugin_dir).absolute()
build_path = plugin_path / build_dir

# Create build directory
build_path.mkdir(exist_ok=True)

# Change to build directory
os.chdir(build_path)

# Run CMake configuration
print("Configuring with CMake...")
cmake_config_cmd = [
"cmake",
"..",
f"-DMCP_SERVER_ROOT={Path(__file__).parent.parent.parent.absolute()}"
]

result = subprocess.run(cmake_config_cmd)
if result.returncode != 0:
print("CMake configuration failed!")
return False

# Build the plugin
print("Building plugin...")
cmake_build_cmd = ["cmake", "--build", ".", "--config", "Release"]
result = subprocess.run(cmake_build_cmd)
if result.returncode != 0:
print("Build failed!")
return False

print("Build completed successfully!")
return True

def main():
parser = argparse.ArgumentParser(description="Build Python plugin for MCPServer++")
parser.add_argument("--plugin-dir", default=".", help="Path to plugin directory (default: current directory)")
parser.add_argument("--build-dir", default="build", help="Build directory name (default: build)")

args = parser.parse_args()

if build_plugin(args.plugin_dir, args.build_dir):
print("Plugin built successfully!")
else:
print("Failed to build plugin!")
sys.exit(1)

if __name__ == "__main__":
main()
82 changes: 82 additions & 0 deletions plugins/official/python_example_plugin/python_example_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
Example Python Plugin for MCPServer++

This plugin demonstrates how to create a Python plugin that can be compiled into
a DLL and loaded by MCPServer++.

To compile this plugin into a DLL:
1. Copy the python_plugin_CMakeLists.txt to this directory and rename it to CMakeLists.txt
2. Modify the MCP_SERVER_ROOT path in CMakeLists.txt
3. Build using CMake as described in the CMakeLists.txt file
"""

class ToolInfo:
"""Tool information structure"""
def __init__(self, name, description, parameters, is_streaming=False):
self.name = name
self.description = description
self.parameters = parameters # JSON Schema string
self.is_streaming = is_streaming

def get_tools():
"""
Get the list of tools provided by this plugin
This function will be called by the C++ wrapper
"""
return [
ToolInfo(
name="python_echo",
description="Echo back the input text",
parameters='{"type": "object", "properties": {"text": {"type": "string", "description": "Text to echo"}}, "required": ["text"]}'
),
ToolInfo(
name="python_calculate",
description="Perform a simple calculation",
parameters='{"type": "object", "properties": {"expression": {"type": "string", "description": "Mathematical expression to evaluate"}}, "required": ["expression"]}'
)
]

def call_tool(name, args_json):
"""
Call a specific tool with arguments
This function will be called by the C++ wrapper

Args:
name: Tool name
args_json: JSON string with tool arguments

Returns:
JSON string with tool result
"""
import json

try:
args = json.loads(args_json) if args_json else {}
except json.JSONDecodeError:
return json.dumps({"error": {"type": "invalid_json", "message": "Invalid JSON in arguments"}})

if name == "python_echo":
text = args.get("text", "")
return json.dumps({"result": text})

elif name == "python_calculate":
expression = args.get("expression", "")
try:
result = eval(expression, {"__builtins__": {}}, {})
return json.dumps({"result": result})
except Exception as e:
return json.dumps({"error": {"type": "calculation_error", "message": str(e)}})

else:
return json.dumps({"error": {"type": "unknown_tool", "message": f"Unknown tool: {name}"}})

# For testing purposes when running the script directly
if __name__ == "__main__":
# This section is for testing the plugin directly
print("Tools:", [tool.name for tool in get_tools()])

result = call_tool("python_echo", '{"text": "Hello from Python!"}')
print("Echo result:", result)

result = call_tool("python_calculate", '{"expression": "2+2"}')
print("Calculation result:", result)
Loading
Loading