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:

diagram

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:

Terminal window
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).

Terminal window
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-195d34260092
2024-12-24 15:27:35 INFO waiting for mux server to be ready attempts: 0
2024-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:0
2024-12-24 15:27:35 INFO pending request method found
method: initialize
name: proxy - mcpclient
id: N:0
2024-12-24 15:27:35 INFO init response
name: example-servers/puppeteer
version: 0.1.0
2024-12-24 15:27:35 INFO sending request
name: proxy - mcpclient
request: &{2.0 notifications/initialized <nil> <nil>}
method: notifications/initialized
id: X
2024-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:1
2024-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: 1
2024-12-24 15:27:39 INFO waiting for mux server to be ready attempts: 2
2024-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.

Terminal window
2024-12-24 15:28:59 INFO waiting for mux server to be ready attempts: 42
2024-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:0
2024-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:

Terminal window
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:

Terminal window
gomcp-proxy --delete
2024-12-24 15:53:08 INFO # deleting proxy configuration file
configPath: .../llmcontext/servers/src/puppeteer/gomcp-proxy.json
2024-12-24 15:53:08 INFO # proxy delete file
configPath: .../llmcontext/servers/src/puppeteer/gomcp-proxy.json
2024-12-24 15:53:08 INFO # proxy delete file
toolsFilePath: .../.gomcp/proxy_tools/0d49e877-ec73-459a-b36f-195d34260092.json
2024-12-24 15:53:08 INFO # proxy deleted

When Claude restarts, it won’t be aware anymore of that MCP server or its tools.