Model Context Protocol multiplexer
The Model Context Protocol is a very powerful mechanism to extend the capabilities of the Claude application by easily inserting dynamic data in the context of your prompts.
After only a few weeks after the announcement, there are already tens (hundreds?) of MCP servers available:
- the official list of reference servers is available here
- the Awesome MCP servers repo
- a search engine is already available here
- another repository , Glama, for Open-Source MCP servers.
To install and test a MCP server is simple but requires to edit the claude_desktop_config.json
file to add the command line required to start the MCP server and to restart Claude.
That’s not very difficult but if you want to test a lots of MCP servers that may become a bit tedious.
There is already a smart MCP server that allows you to add a MCP server from Claude itself.
As part of my gomcp , I am trying to do offer something a bit different to make it easier to test multiple go mcp servers at once.
The basic architecture is as follow:
In Claude you configure only one MCP server: the mutiplexer:
{ "mcpServers": { "gomcp": { "command": "/usr/local/bin/gomcp", "args": [] } }}
In each directory on your machine where there is a MCP server that you want to test, you run the command from the terminal:
gomcp-proxy <the command to start the MCP server>
Once started, the proxy will act as a MCP client and ask the characteristics of the MCP server and then relay them to the Multiplexer.
From the MCP client perspective (eg. Claude), there will be only one MCP server with potentially a lot of tools/prompts/resources exposed.
The proxy and multiplexer will be connected through a local socket connection and if the client calls a tool, the multiplexer will redirect that call to the proper proxy and the response will be sent back to the client.
A simplified sequence diagram is:
Status
I have a working prototype but this is very much an alpha version.
The documentation, besides that post, is missing.
Demonstration
The MCP server being proxied here is the Puppeteer MCP server.
After a npm install
, the way to start this MCP server is to run: node dist/index.js
.
To proxy that server you then simply run:
gomcp-proxy -- node dist/index.js
The proxy will get the list of tools that the MCP server supports and store that list in a shared directory with the multiplexer.
It will then wait for the multiplexer to start (see the next section for the reason for that behavior).
➜ puppeteer git:(main) gomcp-proxy -- node dist/index.js
2024-12-24 15:27:35 INFO MCP Proxy is starting ├ address: :8090 ├ programName: node └ programArgs: [dist/index.js]2024-12-24 15:27:35 INFO generated new proxy id └ proxyId: 0d49e877-ec73-459a-b36f-195d342600922024-12-24 15:27:35 INFO waiting for mux server to be ready attempts: 02024-12-24 15:27:35 INFO sending request ├ name: proxy - mcpclient ├ request: &{2.0 initialize map[capabilities:map[roots:map[listChanged:false] sampling:map[]] clientInfo:map[name:gomcp-proxy version:0.3.0] protocolVersion:2024-11-05] 0xc000406010} ├ method: initialize └ id: N:02024-12-24 15:27:35 INFO pending request method found ├ method: initialize ├ name: proxy - mcpclient └ id: N:02024-12-24 15:27:35 INFO init response ├ name: example-servers/puppeteer └ version: 0.1.02024-12-24 15:27:35 INFO sending request ├ name: proxy - mcpclient ├ request: &{2.0 notifications/initialized <nil> <nil>} ├ method: notifications/initialized └ id: X2024-12-24 15:27:35 INFO sending request ├ method: tools/list ├ id: N:1 ├ name: proxy - mcpclient └ request: &{2.0 tools/list map[] 0xc00009b370}2024-12-24 15:27:35 INFO pending request method found ├ method: tools/list ├ name: proxy - mcpclient └ id: N:12024-12-24 15:27:35 INFO event mcp tools list response └ tools: [{puppeteer_navigate Navigate to a URL map[properties:map[url:map[type:string]] required:[url] type:object]} {puppeteer_screenshot Take a screenshot of the current page or a specific element map[properties:map[height:map[description:Height in pixels (default: 600) type:number] name:map[description:Name for the screenshot type:string] selector:map[description:CSS selector for element to screenshot type:string] width:map[description:Width in pixels (default: 800) type:number]] required:[name] type:object]} {puppeteer_click Click an element on the page map[properties:map[selector:map[description:CSS selector for element to click type:string]] required:[selector] type:object]} {puppeteer_fill Fill out an input field map[properties:map[selector:map[description:CSS selector for input field type:string] value:map[description:Value to fill type:string]] required:[selector value] type:object]} {puppeteer_select Select an element on the page with Select tag map[properties:map[selector:map[description:CSS selector for element to select type:string] value:map[description:Value to select type:string]] required:[selector value] type:object]} {puppeteer_hover Hover an element on the page map[properties:map[selector:map[description:CSS selector for element to hover type:string]] required:[selector] type:object]} {puppeteer_evaluate Execute JavaScript in the browser console map[properties:map[script:map[description:JavaScript code to execute type:string]] required:[script] type:object]}]2024-12-24 15:27:37 INFO waiting for mux server to be ready attempts: 12024-12-24 15:27:39 INFO waiting for mux server to be ready attempts: 22024-12-24 15:27:41 INFO waiting for mux server to be ready attempts: 3
As soon as the the multiplexer starts, meaning here when Claude starts, the proxy connects to that multiplexer and registered itself.
2024-12-24 15:28:59 INFO waiting for mux server to be ready attempts: 422024-12-24 15:28:59 INFO sending request ├ method: proxy/register ├ id: N:0 ├ name: proxy client - gomcp (mux) └ request: &{2.0 proxy/register map[persistent:false protocolVersion:2024-12-13 proxy:map[args:[dist/index.js] command:node workingDirectory:/Users/pcarion/pierre/llmcontext/servers/src/puppeteer] proxyId:0d49e877-ec73-459a-b36f-195d34260092 serverInfo:map[name:example-servers/puppeteer version:0.1.0]] 0xc000034730}2024-12-24 15:28:59 INFO pending request method found ├ method: proxy/register ├ name: proxy client - gomcp (mux) └ id: N:02024-12-24 15:28:59 INFO event mux proxy registered ├ sessionId: s-002 ├ proxyId: 0d49e877-ec73-459a-b36f-195d34260092 ├ persistent: false └ denied: false
From Claude, you ask to take screenshot:
As usual, Claude will ask for permission. Even though the function
puppeteer_navigate
is implemented by the Puppeteer MCP server, from Claude’s perspective, this tool is available with the gomcp
server.
The proxy and the hub work together to call the function on the Puppeteer server and relay the response back to Claude:
Limitations
For now, the proxy only manages the tools, not the prompts nor the resources.
Even though the protocol supports the possibility to report change in the list of tools it appears that Claude does not support that.
That means that we can’t start a new proxy once Claude have been started: even though the proxy will report the tools it supports, Clude on’t be aware of them before a restart.
To make that process less painful, the proxy records its tools in the ~/.gomcp
directory.
That means that you can start the proxy before the multiplexer is started (and so, before Claude is started).
Implementation details
When the proxy starts, it creates a local file:
➜ puppeteer git:(main) ✗ cat gomcp-proxy.json{ "v": 0, "what_is_that": "configuration file for gomcp-proxy. Do NOT edit this file.", "more_information": "More information about gomcp-proxy can be found at https://github.com/llmcontext/gomcp", "proxy_id": "0d49e877-ec73-459a-b36f-195d34260092", "program_name": "node", "program_args": [ "dist/index.js" ], "last_started": "2024-12-24T15:27:35-08:00"}
This file contains a generated proxy_id
that is used to identify itself with the multiplexer.
We also store the command line arguments: next time you want to run the proxy, you just run gomcp-proxy
(no argument), the proxy will know how to start the MCP server.
You can also have a .env
file if you need to make some environment variables available for the MCP server.
The proxy will also create a file shared with the multiplexer at: $HOME/.gomcp/proxy_tools/0d49e877-ec73-459a-b36f-195d34260092.json
This file contains the tools that the MCP server exposed:
{ "proxyId": "0d49e877-ec73-459a-b36f-195d34260092", "workingDirectory": "/Users/pcarion/pierre/llmcontext/servers/src/puppeteer", "proxyName": "gomcp-proxy", "programName": "node", "programArguments": [ "dist/index.js" ], "tools": [ { "name": "puppeteer_navigate", "description": "Navigate to a URL", "inputSchema": { "properties": { "url": { "type": "string" } }, "required": [ "url" ], "type": "object" } }, { "name": "puppeteer_screenshot", "description": "Take a screenshot of the current page or a specific element", "inputSchema": { "properties": { "height": { "description": "Height in pixels (default: 600)", "type": "number" }, "name": { "description": "Name for the screenshot", "type": "string" }, "selector": { "description": "CSS selector for element to screenshot", "type": "string" }, "width": { "description": "Width in pixels (default: 800)", "type": "number" } }, "required": [ "name" ], "type": "object" } }, { "name": "puppeteer_click", "description": "Click an element on the page", "inputSchema": { "properties": { "selector": { "description": "CSS selector for element to click", "type": "string" } }, "required": [ "selector" ], "type": "object" } }, { "name": "puppeteer_fill", "description": "Fill out an input field", "inputSchema": { "properties": { "selector": { "description": "CSS selector for input field", "type": "string" }, "value": { "description": "Value to fill", "type": "string" } }, "required": [ "selector", "value" ], "type": "object" } }, { "name": "puppeteer_select", "description": "Select an element on the page with Select tag", "inputSchema": { "properties": { "selector": { "description": "CSS selector for element to select", "type": "string" }, "value": { "description": "Value to select", "type": "string" } }, "required": [ "selector", "value" ], "type": "object" } }, { "name": "puppeteer_hover", "description": "Hover an element on the page", "inputSchema": { "properties": { "selector": { "description": "CSS selector for element to hover", "type": "string" } }, "required": [ "selector" ], "type": "object" } }, { "name": "puppeteer_evaluate", "description": "Execute JavaScript in the browser console", "inputSchema": { "properties": { "script": { "description": "JavaScript code to execute", "type": "string" } }, "required": [ "script" ], "type": "object" } } ]}
This file will be used by the multiplexer when the MCP client will ask for the list of tools.
Future evolution
Eventually, the server will be able to start the proxy, with their MCP server, on demand when a tool/prompt/resource request is coming from the MCP client for that proxy…
There won’t be anymore reason to start all the proxies ahead of time.
Cleanup
Once you don’t want to use a given MCP server as a proxy, you can easily delete all its proxy related files with:
✗ gomcp-proxy --delete
2024-12-24 15:53:08 INFO # deleting proxy configuration file └ configPath: .../llmcontext/servers/src/puppeteer/gomcp-proxy.json2024-12-24 15:53:08 INFO # proxy delete file └ configPath: .../llmcontext/servers/src/puppeteer/gomcp-proxy.json2024-12-24 15:53:08 INFO # proxy delete file └ toolsFilePath: .../.gomcp/proxy_tools/0d49e877-ec73-459a-b36f-195d34260092.json2024-12-24 15:53:08 INFO # proxy deleted
When Claude restarts, it won’t be aware anymore of that MCP server or its tools.