cmcp
If you are the rightful owner of cmcp and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.
Client-Tool-Execution MCP Server enables dynamic tool registration and client-side execution, enhancing flexibility and security.
Client-Tool-Execution MCP Server 🚀
Truly Useful Anti-Patterns: Client-Tool-Execution MCP Server
Core Features 🎯
- Dynamic Tool Registration: Clients connect and register their tools automatically
- Client-Side Tool Execution: Tools execute on the client side, not the server - perfect for browser DOM manipulation, local file access, or environment-specific operations
- Transparent Proxy: Server acts as a proxy, routing tool calls to the appropriate client for execution
This enables you to:
- 🔄 Register custom tools dynamically when clients connect
- ⚡ Execute tools directly on the client - not on the server
- 🌐 Build tools that interact with client-specific environments (browser DOM, local files, etc.)
- 🔗 Create flexible, client-driven AI tool ecosystems where execution happens where the data lives
Getting Started 🚀
Installation
# Using Deno (recommended)
import { createClientExecServer, createClientExecClient } from "jsr:@mcpc/cmcp";
# Or add to your deno.json
{
"imports": {
"@mcpc/cmcp": "jsr:@mcpc/cmcp@^0.0.2"
}
}
Basic Setup
- Install the package: Add
@mcpc/cmcp
to your project - Start the server demo:
deno run --allow-net --allow-read --allow-env server.ts
- Run the client demo:
deno run --allow-net client.ts
Complete Example
Here's a minimal working example:
Server Usage 📡
The server acts as a proxy and registry - it has no predefined tools and simply routes execution to clients:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { createClientExecServer } from "@mcpc/cmcp";
// Server is just a proxy - no tools, no execution logic
const server = createClientExecServer(
new Server({ name: "dynamic-mcp-server", version: "1.0.0" }),
"dynamic-server",
);
// Server routes all tool calls to the appropriate client
// All execution happens on the client side
Client Usage 🖥️
Clients register tools with implementations that execute locally on the client:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { type ClientToolDefinition, createClientExecClient } from "@mcpc/cmcp";
const client = createClientExecClient(
new Client({ name: "browser-client", version: "1.0.0" }),
"browser-client-001",
);
// Define tools with LOCAL implementations (executed on client)
const tools: ClientToolDefinition[] = [
{
name: "querySelector",
description: "Query DOM elements using CSS selectors",
inputSchema: {
type: "object",
properties: {
selector: { type: "string", description: "CSS selector to query" },
action: {
type: "string",
description: "Action to perform",
enum: ["getText", "click", "getAttribute"],
},
attribute: { type: "string", description: "Attribute name" },
},
required: ["selector", "action"],
},
// 🔥 Implementation runs on CLIENT side - has access to DOM, local files, etc.
implementation: async (args: Record<string, unknown>) => {
const { selector, action, attribute } = args;
const element = document.querySelector(selector as string);
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
switch (action) {
case "getText":
return element.textContent || "";
case "click":
element.click();
return `Clicked element: ${selector}`;
case "getAttribute":
return element.getAttribute(attribute as string);
default:
throw new Error(`Unknown action: ${action}`);
}
},
},
];
// Register tools (stored locally until connection)
client.registerTools(tools);
// Connect and register tools to server
await client.connect(
new SSEClientTransport(new URL("http://localhost:9000/sse")),
);
console.log("Client connected and tools registered!");
// Client stays connected to handle tool execution requests
Example Tool Call 🔧
// External MCP client connecting to the server
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const mcpClient = new Client({
name: "external-client",
version: "1.0.0",
});
await mcpClient.connect(
new SSEClientTransport(new URL("http://localhost:9000/sse")),
);
// Call tools registered by connected clients
const result = await mcpClient.callTool({
name: "querySelector",
arguments: {
selector: "#my-button",
action: "click",
},
});
console.log(result); // "Clicked element: #my-button"
// ✨ The actual DOM manipulation happened on the CLIENT side!
Why Client-Side Execution? 🤔
Traditional MCP: Tools execute on the server
- ❌ Server needs access to all resources (files, DOM, APIs)
- ❌ Security concerns with server-side execution
- ❌ Limited to server environment capabilities
Client-Tool-Execution MCP: Tools execute on the client
- ✅ Client has natural access to its own environment (DOM, local files, etc.)
- ✅ Better security - no need to expose sensitive resources to server
- ✅ Scalable - each client handles its own execution load
- ✅ Environment-specific - browser clients can manipulate DOM, desktop clients can access files
Architecture Flow 🔄
- Server: Starts as an empty proxy with no predefined tools
- Client Connect: Client establishes SSE connection to server
- Tool Registration: Client sends tool definitions (schema only) via
client/register_tools
- Server Registry: Server updates its tool registry with client's tool schemas
- MCP Call: External system discovers and calls tools via server
- Proxy Call: Server proxies call to appropriate client via notification
- Client Execution: 🔥 Tool runs on CLIENT side with full access to client environment
- Response: Result flows back through server to caller
- Client Disconnect: Server automatically removes client's tools
Key Point: The server never executes tools - it only routes calls to clients where the actual execution happens!