1- from typing import Any , Callable , Dict , List , Optional
1+ from typing import Any , Dict
22
33from mcp .server .fastmcp import FastMCP
4- from mcp .types import Tool as MCPTool
54
65# Initialize the MCP server
76mcp = FastMCP ("Self-Extending MCP Server" )
87
9- built_in_tools : List [MCPTool ] = [
10- MCPTool (
11- name = "add_tool" ,
12- description = "Add a new tool to the MCP server by providing its Python code." ,
13- inputSchema = {
14- "type" : "object" ,
15- "properties" : {
16- "name" : {"type" : "string" , "description" : "Name of the tool" },
17- "code" : {
18- "type" : "string" ,
19- "description" : "Python code implementing the tool's function. Must define a function with the specified 'name'. Type hints in the function signature for the input schema." ,
20- },
21- "description" : {
22- "type" : "string" ,
23- "description" : "Description of what the tool does" ,
24- },
25- "inputSchema" : {
26- "type" : "object" ,
27- "description" : "JSON schema object describing the parameters the new tool expects (optional). This schema will be returned by get_tools and used for documentation." ,
28- },
29- },
30- "required" : ["name" , "code" , "description" ],
31- },
32- ),
33- MCPTool (
34- name = "call_tool" ,
35- description = "Call a registered dynamic tool with the given arguments." ,
36- inputSchema = {
37- "type" : "object" ,
38- "properties" : {
39- "name" : {
40- "type" : "string" ,
41- "description" : "Name of the dynamic tool to call" ,
42- },
43- "args" : {
44- "type" : "object" ,
45- "description" : "Dictionary of arguments to pass to the tool. Must conform to the dynamic tool's inferred JSON input schema." ,
46- },
47- },
48- "required" : ["name" , "args" ],
49- },
50- ),
51- ]
52-
53-
54- # Tool registry to track dynamically added tools
55- class ToolRegistry :
56- def __init__ (self ):
57- self .tools = {} # name -> function
58- self .metadata = {} # name -> metadata
59-
60- def register (
61- self ,
62- name : str ,
63- func : Callable ,
64- description : str ,
65- inputSchema : Dict [str , Any ] = None ,
66- ):
67- """Register a new tool in the registry."""
68- self .tools [name ] = func
69- self .metadata [name ] = {
70- "name" : name ,
71- "description" : description ,
72- "inputSchema" : inputSchema or {},
73- }
74-
75- def get_tool (self , name : str ) -> Optional [Callable ]:
76- """Get a tool by name."""
77- return self .tools .get (name )
78-
79- def get_metadata (self , name : str ) -> Optional [Dict [str , Any ]]:
80- """Get tool metadata by name."""
81- return self .metadata .get (name )
82-
83- def list_tools (self ) -> List [Dict [str , Any ]]:
84- """List all registered tools."""
85- return [self .metadata [name ] for name in sorted (self .tools .keys ())]
86-
87- def has_tool (self , name : str ) -> bool :
88- """Check if a tool exists."""
89- return name in self .tools
90-
91-
92- registry = ToolRegistry ()
93-
94-
95- @mcp ._mcp_server .list_tools ()
96- async def get_tools () -> List [MCPTool ]:
97- """Get a list of all available tools in the MCP server.
98-
99- Returns:
100- List of available tools and their metadata
101- """
102- all_tools = list (built_in_tools )
103-
104- tools = registry .list_tools ()
105-
106- for tool in tools :
107- all_tools .append (
108- MCPTool (
109- name = tool ["name" ],
110- description = tool ["description" ],
111- inputSchema = tool ["inputSchema" ],
112- )
113- )
114-
115- return all_tools
116-
1178
1189@mcp .tool ()
11910def add_tool (
@@ -151,7 +42,8 @@ def add_tool(
15142 }
15243
15344 # Check if tool already exists
154- if registry .has_tool (name ) or hasattr (mcp , name ):
45+ existing_tools = [tool .name for tool in mcp ._tool_manager .list_tools ()]
46+ if name in existing_tools :
15547 return {"status" : "error" , "message" : f"Tool '{ name } ' already exists" }
15648
15749 # Validate the code
@@ -160,32 +52,21 @@ def add_tool(
16052 namespace = {}
16153 exec (code , namespace )
16254
163- # Get the function
164- if name not in namespace :
165- return {
166- "status" : "error" ,
167- "message" : f"Function '{ name } ' not found in the provided code" ,
168- }
169-
170- func = namespace [name ]
171-
172- # Check if it's a function
173- if not callable (func ):
55+ if name not in namespace or not callable (namespace [name ]):
17456 return {
17557 "status" : "error" ,
176- "message" : f"'{ name } ' is not a callable function " ,
58+ "message" : f"Valid function '{ name } ' not found in code " ,
17759 }
17860
179- # Register the tool with our registry
180- registry .register (name , func , description , inputSchema )
181-
182- # Get the parameter information to return
183- params = registry .get_metadata (name )["inputSchema" ]
61+ # Register the tool with fast mcp
62+ mcp ._tool_manager .add_tool (
63+ namespace [name ], name = name , description = description
64+ )
18465
18566 return {
18667 "status" : "success" ,
18768 "message" : f"Tool '{ name } ' added successfully" ,
188- "inputSchema" : params ,
69+ "inputSchema" : inputSchema ,
18970 }
19071
19172 except SyntaxError as e :
@@ -200,86 +81,6 @@ def add_tool(
20081 return {"status" : "error" , "message" : str (e )}
20182
20283
203- @mcp .tool ()
204- def call_tool (name : str , args : Dict [str , Any ] = None ) -> Dict [str , Any ]:
205- """Call a registered dynamic tool with the given arguments.
206-
207- Args:
208- name: Name of the dynamic tool to call (required)
209- args: Dictionary of arguments to pass to the tool. You should consult the tool's schema from get_tools to know the expected structure. (required)
210-
211- Returns:
212- Dictionary with the tool's response
213- """
214-
215- args = args or {}
216-
217- try :
218- # Check if it's a built-in tool
219- matching_tool = next ((tool for tool in built_in_tools if tool .name == name ), None )
220- if matching_tool :
221- return {
222- "status" : "error" ,
223- "message" : f"Cannot call built-in tool '{ name } ' using call_tool" ,
224- "note" : f"Use the { name } function directly instead of call_tool" ,
225- "inputSchema" : matching_tool .inputSchema ,
226- }
227-
228- # Get the tool
229- tool = registry .get_tool (name )
230-
231- if not tool :
232- return {
233- "status" : "error" ,
234- "message" : f"Tool '{ name } ' not found" ,
235- "available_tools" : [t ["name" ] for t in registry .list_tools ()],
236- }
237-
238- # Call the tool with the provided arguments
239- try :
240- result = tool (** args )
241- return result
242- except TypeError as e :
243- # Likely an argument mismatch
244- params = registry .get_metadata (name )["inputSchema" ]
245-
246- # Build a usage example with actual parameter names
247- param_examples = {}
248-
249- # Handle different possible inputSchema structures
250- if isinstance (params , dict ):
251- if "properties" in params :
252- # Standard JSON Schema format
253- for param_name in params ["properties" ]:
254- param_examples [param_name ] = f"<{ param_name } _value>"
255- else :
256- # Simple dict of param_name -> description
257- for param_name in params :
258- param_examples [param_name ] = f"<{ param_name } _value>"
259-
260- # If no parameters found or empty schema, provide generic example
261- if not param_examples :
262- param_examples = {"param1" : "<value1>" , "param2" : "<value2>" }
263-
264- # Format the dictionary for better readability
265- usage_str = str (param_examples ).replace ("'<" , "<" ).replace (">'" , ">" )
266-
267- return {
268- "status" : "error" ,
269- "message" : f"Argument error calling tool '{ name } ': { str (e )} . Please fix your mistakes, add proper 'args' values!" ,
270- "inputSchema" : params ,
271- "example" : f"call_tool(name='{ name } ', args={ usage_str } )" ,
272- }
273- except Exception as e :
274- return {
275- "status" : "error" ,
276- "message" : f"Error calling tool '{ name } ': { str (e )} " ,
277- }
278-
279- except Exception as e :
280- return {"status" : "error" , "message" : str (e )}
281-
282-
28384# Run the server when the script is executed
28485if __name__ == "__main__" :
28586 mcp .run ()
0 commit comments