Skip to main content
Home

Truly Useful Anti-Patterns: Client-Tool-Execution MCP Server

This package works with Node.js, Deno, Bun, BrowsersIt is unknown whether this package works with Cloudflare Workers
It is unknown whether this package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Bun
This package works with Browsers
JSR Score
70%
Published
16 hours ago (0.0.3)

Client-Tool-Execution MCP Server 🚀

JSR

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

  1. Install the package: Add @mcpc/cmcp to your project
  2. Start the server demo: deno run --allow-net --allow-read --allow-env server.ts
  3. 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 🔄

  1. Server: Starts as an empty proxy with no predefined tools
  2. Client Connect: Client establishes SSE connection to server
  3. Tool Registration: Client sends tool definitions (schema only) via client/register_tools
  4. Server Registry: Server updates its tool registry with client's tool schemas
  5. MCP Call: External system discovers and calls tools via server
  6. Proxy Call: Server proxies call to appropriate client via notification
  7. Client Execution: 🔥 Tool runs on CLIENT side with full access to client environment
  8. Response: Result flows back through server to caller
  9. 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!

New Ticket: Report package

Please provide a reason for reporting this package. We will review your report and take appropriate action.

Please review the JSR usage policy before submitting a report.

Add Package

deno add jsr:@mcpc/cmcp

Import symbol

import * as cmcp from "@mcpc/cmcp";
or

Import directly with a jsr specifier

import * as cmcp from "jsr:@mcpc/cmcp";

Add Package

pnpm i jsr:@mcpc/cmcp
or (using pnpm 10.8 or older)
pnpm dlx jsr add @mcpc/cmcp

Import symbol

import * as cmcp from "@mcpc/cmcp";

Add Package

yarn add jsr:@mcpc/cmcp
or (using Yarn 4.8 or older)
yarn dlx jsr add @mcpc/cmcp

Import symbol

import * as cmcp from "@mcpc/cmcp";

Add Package

vlt install jsr:@mcpc/cmcp

Import symbol

import * as cmcp from "@mcpc/cmcp";

Add Package

npx jsr add @mcpc/cmcp

Import symbol

import * as cmcp from "@mcpc/cmcp";

Add Package

bunx jsr add @mcpc/cmcp

Import symbol

import * as cmcp from "@mcpc/cmcp";