diff --git a/samples/mcp-dynamic-server/pyproject.toml b/samples/mcp-dynamic-server/pyproject.toml index 56dee1b..5d96491 100644 --- a/samples/mcp-dynamic-server/pyproject.toml +++ b/samples/mcp-dynamic-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-dynamic-server" -version = "0.0.3" +version = "0.0.7" description = "Dynamic tools MCP Server" authors = [{ name = "John Doe" }] dependencies = [ diff --git a/samples/mcp-dynamic-server/server.py b/samples/mcp-dynamic-server/server.py index 6169a86..4bf20fe 100644 --- a/samples/mcp-dynamic-server/server.py +++ b/samples/mcp-dynamic-server/server.py @@ -5,6 +5,28 @@ # Initialize the MCP server mcp = FastMCP("Self-Extending MCP Server") +built_in_tools = { + "get_tools": { + "description": "Get a list of all available tools in the MCP server.", + "parameters": {} + }, + "add_tool": { + "description": "Add a new tool to the MCP server.", + "parameters": { + "name": "Name of the tool (required)", + "code": "Python code implementing the tool function (required)", + "description": "Description of what the tool does (required)", + "param_descriptions": "Dictionary of parameter names to descriptions (optional)" + } + }, + "call_tool": { + "description": "Call a registered tool with the given arguments.", + "parameters": { + "name": "Name of the tool to call (required)", + "args": "Dictionary of arguments to pass to the tool" + } + } +} # Tool registry to track dynamically added tools class ToolRegistry: @@ -12,10 +34,14 @@ def __init__(self): self.tools = {} # name -> function self.metadata = {} # name -> metadata - def register(self, name: str, func: Callable, description: str): + def register(self, name: str, func: Callable, description: str, param_descriptions: Dict[str, str] = None): """Register a new tool in the registry.""" self.tools[name] = func - self.metadata[name] = {"name": name, "description": description} + self.metadata[name] = { + "name": name, + "description": description, + "parameters": param_descriptions or {} + } def get_tool(self, name: str) -> Optional[Callable]: """Get a tool by name.""" @@ -49,19 +75,23 @@ def get_tools() -> Dict[str, Any]: try: tools = registry.list_tools() - # Add the built-in tools - built_in_tools = ["get_tools", "add_tool", "call_tool"] - # Combine built-in tools with dynamic tools - all_tools = [{"name": tool, "built_in": True} for tool in built_in_tools] + all_tools = [] + for name, info in built_in_tools.items(): + all_tools.append({ + "name": name, + "description": info["description"], + "parameters": info["parameters"], + "built_in": True + }) + for tool in tools: - all_tools.append( - { - "name": tool["name"], - "description": tool["description"], - "built_in": False, - } - ) + all_tools.append({ + "name": tool["name"], + "description": tool["description"], + "parameters": tool["parameters"], + "built_in": False + }) return {"status": "success", "tools": all_tools} except Exception as e: @@ -70,18 +100,35 @@ def get_tools() -> Dict[str, Any]: # Core functionality: Add a new tool @mcp.tool() -def add_tool(name: str, code: str, description: str) -> Dict[str, Any]: +def add_tool(name: str = None, code: str = None, description: str = None, param_descriptions: Dict[str, str] = None) -> Dict[str, Any]: """Add a new tool to the MCP server. Args: name: Name of the tool code: Python code implementing the tool function description: Description of what the tool does + param_descriptions: Dictionary of parameter names to descriptions Returns: Dictionary with operation status """ try: + # Validate required parameters + missing_params = [] + if name is None: + missing_params.append("name") + if code is None: + missing_params.append("code") + if description is None: + missing_params.append("description") + + if missing_params: + return { + "status": "error", + "message": f"Missing required parameters: {', '.join(missing_params)}", + "usage_example": "add_tool(name='tool_name', code='def tool_name(param1, param2):\\n # code here\\n return {\"status\": \"success\"}', description='Tool description', param_descriptions={'param1': 'Description of param1', 'param2': 'Description of param2'})" + } + # Check if tool already exists if registry.has_tool(name) or hasattr(mcp, name): return {"status": "error", "message": f"Tool '{name}' already exists"} @@ -109,9 +156,16 @@ def add_tool(name: str, code: str, description: str) -> Dict[str, Any]: } # Register the tool with our registry - registry.register(name, func, description) + registry.register(name, func, description, param_descriptions) - return {"status": "success", "message": f"Tool '{name}' added successfully"} + # Get the parameter information to return + params = registry.get_metadata(name)["parameters"] + + return { + "status": "success", + "message": f"Tool '{name}' added successfully", + "parameters": params + } except SyntaxError as e: return { @@ -127,7 +181,7 @@ def add_tool(name: str, code: str, description: str) -> Dict[str, Any]: # Core functionality: Call a tool @mcp.tool() -def call_tool(name: str, args: Dict[str, Any]) -> Dict[str, Any]: +def call_tool(name: str, args: Dict[str, Any] = None) -> Dict[str, Any]: """Call a registered tool with the given arguments. Args: @@ -137,19 +191,28 @@ def call_tool(name: str, args: Dict[str, Any]) -> Dict[str, Any]: Returns: Dictionary with the tool's response """ + + args = args or {} + try: # Check if it's a built-in tool - if name in ["get_tools", "add_tool", "call_tool"]: + if name in built_in_tools: return { "status": "error", "message": f"Cannot call built-in tool '{name}' using call_tool", + "note": f"Use the {name} function directly instead of call_tool", + "parameters": built_in_tools[name]["parameters"] } # Get the tool tool = registry.get_tool(name) if not tool: - return {"status": "error", "message": f"Tool '{name}' not found"} + return { + "status": "error", + "message": f"Tool '{name}' not found", + "available_tools": [t["name"] for t in registry.list_tools()] + } # Call the tool with the provided arguments try: @@ -157,9 +220,12 @@ def call_tool(name: str, args: Dict[str, Any]) -> Dict[str, Any]: return result except TypeError as e: # Likely an argument mismatch + params = registry.get_metadata(name)["parameters"] return { "status": "error", "message": f"Argument error calling tool '{name}': {str(e)}", + "expected_parameters": params, + "usage_example": f"call_tool(name='{name}', args={{'param1': value1, 'param2': value2, ...}})" } except Exception as e: return { diff --git a/samples/mcp-dynamic-server/uv.lock b/samples/mcp-dynamic-server/uv.lock index c9ce10c..9beaa05 100644 --- a/samples/mcp-dynamic-server/uv.lock +++ b/samples/mcp-dynamic-server/uv.lock @@ -461,7 +461,7 @@ wheels = [ [[package]] name = "mcp-dynamic-server" -version = "0.0.1" +version = "0.0.3" source = { virtual = "." } dependencies = [ { name = "uipath-mcp" },