feat: implement mesh MCP proxy — dynamic tool sharing between peers

Peers can register MCP servers with the mesh and other peers can invoke
those tools through the existing claudemesh connection without restarting.

Broker: in-memory MCP registry with mcp_register/unregister/list/call
handlers, call forwarding to hosting peer with 30s timeout, and automatic
cleanup on peer disconnect.

CLI: mcpRegister/mcpUnregister/mcpList/mcpCall client methods, inbound
mcp_call_forward handler, and 4 new MCP tools (mesh_mcp_register,
mesh_mcp_list, mesh_tool_call, mesh_mcp_remove).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-07 23:50:54 +01:00
parent 7d432b3aaa
commit 08e289a5e3
5 changed files with 755 additions and 1 deletions

View File

@@ -194,6 +194,10 @@ If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder
| schedule_reminder(message, in_seconds?, deliver_at?, to?) | Schedule a reminder to yourself (no \`to\`) or a delayed message to a peer/group. Delivered as a push with \`subtype: reminder\` in the channel meta. |
| list_scheduled() | List pending scheduled reminders and messages. |
| cancel_scheduled(id) | Cancel a pending scheduled item. |
| mesh_mcp_register(server_name, description, tools) | Register an MCP server with the mesh. Other peers can call its tools. |
| mesh_mcp_list() | List MCP servers available in the mesh with their tools. |
| mesh_tool_call(server_name, tool_name, args?) | Call a tool on a mesh-registered MCP server (30s timeout). |
| mesh_mcp_remove(server_name) | Unregister an MCP server you registered. |
If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
@@ -959,6 +963,55 @@ Your message mode is "${messageMode}".
return text(results.join("\n"));
}
// --- MCP Proxy ---
case "mesh_mcp_register": {
const { server_name, description, tools: regTools } = (args ?? {}) as {
server_name?: string;
description?: string;
tools?: Array<{ name: string; description: string; inputSchema: Record<string, unknown> }>;
};
if (!server_name || !description || !regTools?.length)
return text("mesh_mcp_register: `server_name`, `description`, and `tools` required", true);
const client = allClients()[0];
if (!client) return text("mesh_mcp_register: not connected", true);
const result = await client.mcpRegister(server_name, description, regTools);
if (!result) return text("mesh_mcp_register: broker did not acknowledge", true);
return text(`Registered MCP server "${result.serverName}" with ${result.toolCount} tool(s). Other peers can now call its tools via mesh_tool_call.`);
}
case "mesh_mcp_list": {
const client = allClients()[0];
if (!client) return text("mesh_mcp_list: not connected", true);
const servers = await client.mcpList();
if (servers.length === 0) return text("No MCP servers registered in the mesh.");
const lines = servers.map((s) => {
const toolList = s.tools.map((t) => ` - **${t.name}**: ${t.description}`).join("\n");
return `- **${s.name}** (hosted by ${s.hostedBy}): ${s.description}\n${toolList}`;
});
return text(`${servers.length} MCP server(s) in mesh:\n${lines.join("\n")}`);
}
case "mesh_tool_call": {
const { server_name: callServer, tool_name: callTool, args: callArgs } = (args ?? {}) as {
server_name?: string;
tool_name?: string;
args?: Record<string, unknown>;
};
if (!callServer || !callTool)
return text("mesh_tool_call: `server_name` and `tool_name` required", true);
const client = allClients()[0];
if (!client) return text("mesh_tool_call: not connected", true);
const callResult = await client.mcpCall(callServer, callTool, callArgs ?? {});
if (callResult.error) return text(`mesh_tool_call error: ${callResult.error}`, true);
return text(typeof callResult.result === "string" ? callResult.result : JSON.stringify(callResult.result, null, 2));
}
case "mesh_mcp_remove": {
const { server_name: rmServer } = (args ?? {}) as { server_name?: string };
if (!rmServer) return text("mesh_mcp_remove: `server_name` required", true);
const client = allClients()[0];
if (!client) return text("mesh_mcp_remove: not connected", true);
await client.mcpUnregister(rmServer);
return text(`Unregistered MCP server "${rmServer}" from the mesh.`);
}
case "grant_file_access": {
const { fileId, to: grantTo } = (args ?? {}) as { fileId?: string; to?: string };
if (!fileId || !grantTo) return text("grant_file_access: `fileId` and `to` required", true);

View File

@@ -609,6 +609,66 @@ export const TOOLS: Tool[] = [
inputSchema: { type: "object", properties: {} },
},
// --- MCP Proxy ---
{
name: "mesh_mcp_register",
description:
"Register an MCP server with the mesh. Other peers can invoke its tools through the mesh without restarting their sessions. Provide the server name, description, and full tool definitions.",
inputSchema: {
type: "object",
properties: {
server_name: { type: "string", description: "Unique name for the MCP server (e.g. 'github', 'jira')" },
description: { type: "string", description: "What this MCP server does" },
tools: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
description: { type: "string" },
inputSchema: { type: "object", description: "JSON Schema for tool arguments" },
},
required: ["name", "description", "inputSchema"],
},
description: "Tool definitions to expose",
},
},
required: ["server_name", "description", "tools"],
},
},
{
name: "mesh_mcp_list",
description:
"List MCP servers available in the mesh with their tools. Shows which peer hosts each server.",
inputSchema: { type: "object", properties: {} },
},
{
name: "mesh_tool_call",
description:
"Call a tool on a mesh-registered MCP server. Route: you -> broker -> hosting peer -> execute -> result back. Timeout: 30s.",
inputSchema: {
type: "object",
properties: {
server_name: { type: "string", description: "Name of the MCP server" },
tool_name: { type: "string", description: "Name of the tool to call" },
args: { type: "object", description: "Tool arguments (JSON object)" },
},
required: ["server_name", "tool_name"],
},
},
{
name: "mesh_mcp_remove",
description:
"Unregister an MCP server you previously registered with the mesh.",
inputSchema: {
type: "object",
properties: {
server_name: { type: "string", description: "Name of the MCP server to remove" },
},
required: ["server_name"],
},
},
// --- Diagnostics ---
{
name: "ping_mesh",