From cf2c70167044542e9189f76f041ddc30d5a10b48 Mon Sep 17 00:00:00 2001 From: dbth <1097224+believethehype@users.noreply.github.com> Date: Mon, 10 Feb 2025 10:43:25 +0100 Subject: [PATCH] support for multiple mcp servers per dvm --- nostr_dvm/backends/mcp/connection_check.py | 2 +- nostr_dvm/backends/mcp/server_config.json | 11 ++++ nostr_dvm/tasks/mcpbridge.py | 61 +++++++++++----------- tests/mcp_dvm_client.py | 4 +- tests/mcp_server.py | 18 +++++++ tests/mcp_test.py | 16 ++++-- 6 files changed, 76 insertions(+), 36 deletions(-) create mode 100644 tests/mcp_server.py diff --git a/nostr_dvm/backends/mcp/connection_check.py b/nostr_dvm/backends/mcp/connection_check.py index f683ded..bc543f3 100644 --- a/nostr_dvm/backends/mcp/connection_check.py +++ b/nostr_dvm/backends/mcp/connection_check.py @@ -18,7 +18,7 @@ async def main(): """Stripped-down script to initialize the server and send a ping.""" # Configuration values config_path = "server_config.json" - server_name = "sqlite" + server_name = "Echo" # Load server configuration server_params = await load_config(config_path, server_name) diff --git a/nostr_dvm/backends/mcp/server_config.json b/nostr_dvm/backends/mcp/server_config.json index eb6bb4a..e11cf48 100644 --- a/nostr_dvm/backends/mcp/server_config.json +++ b/nostr_dvm/backends/mcp/server_config.json @@ -3,6 +3,17 @@ "sqlite": { "command": "uvx", "args": ["mcp-server-sqlite", "--db-path", "test.db"] + }, + "Echo": { + "command": "uv", + "args": [ + "run", + "--with", + "mcp[cli]", + "mcp", + "run", + "~/Documents/GitHub/nostrdvm/tests/mcp_server.py" + ] } } } diff --git a/nostr_dvm/tasks/mcpbridge.py b/nostr_dvm/tasks/mcpbridge.py index 2fd2d9c..a298823 100644 --- a/nostr_dvm/tasks/mcpbridge.py +++ b/nostr_dvm/tasks/mcpbridge.py @@ -96,9 +96,18 @@ class MCPBridge(DVMTaskInterface): if options["command"] == "list-tools": tools = await self.list_tools(config_path, server_names) # Just return the first for now. + + if len(tools) == 0: + print("Couldnt load tools, shutting down.") + exit() + + final_tools = [] for tool in tools: - print(tool[1]) - return json.dumps(tool[1]) + j = json.loads(json.dumps(tool[1]))["tools"] + for tool in j: + final_tools.append(tool) + print(final_tools) + return json.dumps(final_tools) elif options["command"] == "execute-tool": @@ -134,6 +143,12 @@ class MCPBridge(DVMTaskInterface): try: async with stdio_client(server_params) as (read_stream, write_stream): # Initialize the server + init_result = await send_initialize(read_stream, write_stream) + + # check we got a result + if not init_result: + print("Server initialization failed") + return tools = await send_tools_list(read_stream, write_stream) if tools is not None: @@ -161,6 +176,11 @@ class MCPBridge(DVMTaskInterface): async with stdio_client(server_params) as (read_stream, write_stream): #Check if our current config has a tool. + init_result = await send_initialize(read_stream, write_stream) + # check we got a result + if not init_result: + print("Server initialization failed") + return tools = await send_tools_list(read_stream, write_stream) stuff = json.dumps(tools) @@ -173,44 +193,23 @@ class MCPBridge(DVMTaskInterface): print(f"Found tool {tool_name}.") server_has_tool = True if server_has_tool is False: - continue - - tool_response = await send_call_tool( - tool_name, tool_args, read_stream, write_stream) - raise Exception("I'm gonna leave you.") # Until we find a better way to leave the async with + print("no tool in server") + raise Exception() + else: + tool_response = await send_call_tool( + tool_name, tool_args, read_stream, write_stream) + raise Exception() # Until we find a better way to leave the async with except: pass - return tool_response - - raise Exception("I'm gonna leave you.") + raise Exception() except: pass - return "not_found" + return tool_response - alltools = [] - for server_name in server_names: - server_params = await config.load_config(config_path, server_name) - try: - async with stdio_client(server_params) as (read_stream, write_stream): - # Initialize the server - - tools = await send_tools_list(read_stream, write_stream) - if tools is not None: - alltools.append((server_name, tools)) - raise Exception("I'm gonna leave you.") - - else: - print("nada") - except: - pass - - print("All clear. We made it out of thread hell. never mind the error.") - return alltools - # We build an example here that we can call by either calling this file directly from the main directory, diff --git a/tests/mcp_dvm_client.py b/tests/mcp_dvm_client.py index 2124971..2ecdb80 100644 --- a/tests/mcp_dvm_client.py +++ b/tests/mcp_dvm_client.py @@ -90,7 +90,9 @@ async def nostr_client(): await client.subscribe(mcp_filter, None) #await nostr_client_test_mcp_get_tools() - await nostr_client_test_mcp_execute_tool(tool_name="get-crypto-price", tool_parameters={"symbol": "BTC"}) + #await nostr_client_test_mcp_execute_tool(tool_name="get-crypto-price", tool_parameters={"symbol": "BTC"}) + await nostr_client_test_mcp_execute_tool(tool_name="echo_tool", tool_parameters={"message": "Hello"}) + class NotificationHandler(HandleNotification): async def handle(self, relay_url, subscription_id, event: Event): diff --git a/tests/mcp_server.py b/tests/mcp_server.py new file mode 100644 index 0000000..2271360 --- /dev/null +++ b/tests/mcp_server.py @@ -0,0 +1,18 @@ +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("Echo") + +@mcp.resource("echo://{message}") +def echo_resource(message: str) -> str: + """Echo a message as a resource""" + return f"Resource echo: {message}" + +@mcp.tool() +def echo_tool(message: str) -> str: + """Echo a message as a tool""" + return f"Tool echo: {message}" + +@mcp.prompt() +def echo_prompt(message: str) -> str: + """Create an echo prompt""" + return f"Please process this message: {message}" \ No newline at end of file diff --git a/tests/mcp_test.py b/tests/mcp_test.py index 9fc5ad1..8c2da32 100644 --- a/tests/mcp_test.py +++ b/tests/mcp_test.py @@ -32,13 +32,23 @@ def playground(announce=False): # MCP CONFIG config_path = str(Path.absolute(Path(__file__).parent / "mcp_server_config.json")) - server_names = ["mcp-crypto-price"] + server_names = ["Echo", "mcp-crypto-price"] tools = asyncio.run(get_tools(config_path, server_names)) # for now get the first connected server only #print(tools) - j = json.loads(json.dumps(tools[0][1])) + if len (tools) == 0: + print("Couldnt load tools, shutting down.") + exit() + + final_tools =[] + for tool in tools: + j = json.loads(json.dumps(tool[1]))["tools"] + for tool in j: + final_tools.append(tool) + print(final_tools) + # Add NIP89 @@ -50,7 +60,7 @@ def playground(announce=False): "acceptsNutZaps": dvm_config.ENABLE_NUTZAP, "nip90Params": { }, - "tools": j["tools"] + "tools": final_tools }