Skip to content

cybershape/mcp-smart-proxy

Repository files navigation

MSP (MCP Smart Proxy)

msp is a small Rust CLI that lets an AI work with many MCP servers through one proxy server.

Instead of exposing every downstream MCP tool directly, msp exposes a small built-in proxy toolset. This keeps the upstream tool list small, reduces prompt noise, and avoids wasting tokens on tools the agent will never use.

The installed binary name is msp. Running msp without arguments shows the top-level help.

Why use it

  • Reduce the number of tools your agent sees (reduce the token cost without losing any tool).
  • Cache downstream MCP tool metadata and summaries.
  • Proxy both local stdio MCP servers and remote Streamable HTTP MCP servers.
  • Reuse your existing Codex, OpenCode, Claude Code, or GitHub Copilot CLI MCP setup instead of rebuilding everything from scratch.

How it works

msp does three things:

  1. Connects to each configured MCP server and caches its tool metadata.
  2. Generates a short summary for each server by using a configured provider: codex, opencode, or claude, or uses a manually configured server description.
  3. Starts a stdio MCP proxy that exposes these proxy tools:
  • activate_additional_mcps
  • activate_tools_in_additional_mcp
  • call_tool_in_additional_mcp
  • eval_lua_script

Agents first inspect the cached server index, optionally inspect one tool definition, and then call the downstream tool through the proxy.

The built-in eval_lua_script tool runs Lua 5.5 scripts inside the proxy through mlua. Scripts can call any configured downstream MCP tool through call_mcp_tool(mcp_name, tool_name, args), where args is a Lua table that maps to a JSON object.

When a host starts msp mcp, msp auto-starts one background daemon for that config file. That daemon owns downstream MCP communication and periodic self-update checks. Later msp mcp processes that use the same config reuse the same Unix socket daemon, even when they pass different --provider values. The daemon exits after 1 hour with no requests.

The default daemon socket lives under ~/.cache/mcp-smart-proxy/ and uses a short hash of the config path so it stays within Unix socket path limits on macOS and Linux.

Requirements

  • curl or wget, plus tar, for installation
  • The codex CLI when using --provider codex
  • The opencode CLI when using --provider opencode
  • The claude CLI when using --provider claude
  • The copilot CLI when using --provider copilot
  • A browser session for remote MCP servers that require OAuth login

Install

Install the latest release:

curl -fsSL https://raw.githubusercontent.com/cybershape/mcp-smart-proxy/master/install.sh | bash

Install to a custom directory:

curl -fsSL https://raw.githubusercontent.com/cybershape/mcp-smart-proxy/master/install.sh | INSTALL_DIR=/tmp/msp/bin bash

Install a specific version:

curl -fsSL https://raw.githubusercontent.com/cybershape/mcp-smart-proxy/master/install.sh | VERSION=v0.0.19 bash

By default the installer writes msp to:

  • macOS on Apple Silicon: /opt/homebrew/bin
  • macOS on Intel: /usr/local/bin
  • Linux as root: /usr/local/bin
  • Linux as a regular user: ~/.local/bin

After installation:

msp

Quick Start

Fastest path for Codex users

Import your existing Codex MCP servers into msp, replace Codex's MCP entries with the proxy, and keep a backup:

msp import codex --replace

If you want to restore the original Codex MCP servers later:

msp restore codex

To add a new server after that:

msp add --provider codex github npx -y @modelcontextprotocol/server-github

To add a new server with a manual summary and no AI provider:

msp add --description "Use this for GitHub workflows." github npx -y @modelcontextprotocol/server-github

To add a remote server that needs headers up front:

msp add --provider codex --url https://example.com/mcp --header Authorization='Bearer ${DEMO_TOKEN}' remote-demo

Fastest path for OpenCode, Claude Code, or GitHub Copilot CLI

Import existing servers:

msp import opencode
msp import claude
msp import copilot

Install the proxy into the host:

msp install opencode
msp install claude
msp install copilot

Install the bundled pi extension globally:

msp install pi

Replace existing host MCP entries and keep a backup:

msp install opencode --replace
msp install claude --replace
msp install copilot --replace

Restore the original host config later if needed:

msp restore opencode
msp restore claude
msp restore copilot

Remove the global pi extension later if needed:

msp restore pi

Start from scratch

Add a server:

msp add --provider codex github npx -y @modelcontextprotocol/server-github

Install msp into your host:

msp install codex

From that point, the host launches msp mcp as its MCP server entrypoint. Pass --provider <provider> when you want msp to regenerate server summaries with that provider.

The first launch starts the shared daemon automatically. Later launches for the same config reuse it.

You can inspect or control that shared process directly:

msp daemon status
msp daemon stop
msp daemon restart

Common Tasks

Add a server

msp add --provider codex github npx -y @modelcontextprotocol/server-github

Or skip AI summarization and persist a manual description instead:

msp add --description "Use this for GitHub workflows." github npx -y @modelcontextprotocol/server-github

If the command is a single http:// or https:// URL, msp stores it as a native remote server:

msp add --provider codex remote-demo https://example.com/mcp

If the server needs headers, env values, forwarded env vars, or an initial enabled state, pass them during add so the first cache refresh uses the final connection settings:

msp add --provider codex --url https://example.com/mcp --header Authorization='Bearer ${DEMO_TOKEN}' --env DEMO_REGION=global --env-var DEMO_TOKEN --enabled false remote-demo

add requires either --provider or --description. With --provider, msp summarizes the fetched tools immediately. With --description, msp stores that text in the local config and uses it as the cached server summary without calling any AI provider. If both are passed, the manual description wins. The command succeeds only when both config persistence and the initial cache refresh succeed; if cache generation fails, msp rolls back the new server entry instead of leaving partial config behind. When you use config flags with add, place them before the server name so the trailing command remains untouched. You can still refresh later with msp reload, or let the shared msp mcp daemon refresh enabled servers in the background after startup.

List servers

msp list

msp list shows each configured server, whether it is enabled, and when its cache was last refreshed.

Enable or disable a server

msp disable github
msp enable github

Disabled servers stay in the config and keep their cache files, but bulk reload and daemon-managed mcp startup skip them.

Show or update one server

Show current config:

msp config github

Update a stdio server:

msp config github --cmd uvx --clear-args --arg demo-server --env DEMO_REGION=global --env-var DEMO_TOKEN --enabled false

Update a remote server:

msp config remote-demo --url https://example.com/mcp --clear-headers --header Authorization='Bearer ${DEMO_TOKEN}'

Forward one shell variable into a remote server config:

msp config remote-demo --env-var DEMO_TOKEN

msp config can update transport, enabled, command or URL fields, headers, static env values, and forwarded env var names.

Reload cached tools

Reload one server:

msp reload --provider codex github

Reload every enabled server:

msp reload --provider codex

reload fetches the downstream tool list, compares it to the cache, and only regenerates the summary when the tool list changed. If you omit --provider, reload still updates tools and keeps the existing cached summary unless the server has a manual description in config.

Run Lua automation through the proxy

The built-in eval_lua_script MCP tool is useful when the host wants lightweight programmable orchestration without exposing every downstream tool directly in the prompt.

Example Lua script:

local docs = call_mcp_tool("context7", "query-docs", {
  libraryId = "/websites/rs_crate_mlua",
  query = "create_async_function examples",
})

return {
  isError = docs.isError,
  structured = docs.structuredContent,
}

eval_lua_script returns structured JSON. A script that returns no values produces null, one value produces that value, and multiple return values are encoded as a JSON array.

Log in or out of a remote server

Start OAuth login:

msp login remote-demo

Clear cached OAuth credentials:

msp logout remote-demo

OAuth metadata is discovered from the remote MCP server at runtime. Credentials are cached under ~/.cache/mcp-smart-proxy/oauth/.

Unsupported Figma remote MCP server

msp does not support Figma's hosted MCP endpoint at https://mcp.figma.com/mcp.

The proxy rejects that URL during msp add ..., msp config --url, and local config load with a clear error instead of letting setup continue into a broken OAuth flow.

During msp import ... and msp install ... --replace, Figma hosted MCP entries are skipped for import and left in the original host config instead of being deleted.

msp add --provider codex figma https://mcp.figma.com/mcp

Expected result:

server `figma` uses unsupported remote MCP URL `https://mcp.figma.com/mcp`; msp does not support Figma's hosted MCP endpoint

Remove a server

msp remove github

This removes the server from the config and deletes its cached tool file if one exists.

Update msp itself

msp update

This checks GitHub Releases, downloads the newest build for the current platform, and replaces the current executable in place when a newer release exists.

When the daemon is running, it is also responsible for periodic background self-update checks for msp mcp traffic.

Manage the shared daemon

Check whether the daemon for the current config is running:

msp daemon status

Stop the daemon and remove its socket and pid state:

msp daemon stop

Restart the daemon for the current config:

msp daemon restart

All three commands also accept --socket <path> when you need to target a custom daemon socket.

Keep custom socket paths short enough for Unix domain socket limits.

The daemon writes a runtime log next to its socket, for example ~/.cache/mcp-smart-proxy/msp-<scope>.sock.log.

Detached daemon startup also writes ~/.cache/mcp-smart-proxy/msp-<scope>.sock.startup.log until the new daemon answers a status probe. If startup fails or the new daemon becomes unresponsive before it can serve status, that startup log is kept for diagnosis.

If the daemon socket accepts a connection but never replies, msp daemon status, stop, and restart fail quickly instead of hanging indefinitely. stop and restart also fall back to force-stopping the unresponsive daemon by pid-file state so the socket can recover without manual cleanup.

Concurrent daemon refresh requests for the same provider are coalesced into one shared reload. Slow cache-lock waits, MCP tool discovery, and summary subprocesses also fail with timeouts instead of blocking the daemon forever.

Install Into a Host

Install the proxy into Codex, OpenCode, Claude Code, or GitHub Copilot CLI:

msp install codex
msp install opencode
msp install claude
msp install copilot

Install the bundled global pi extension:

msp install pi

This writes the embedded extension source to ~/.pi/agent/extensions/msp.ts.

With --replace, msp first imports the host's current MCP servers into msp, backs them up, removes them from the host config, and then installs the proxy:

msp install codex --replace
msp install opencode --replace
msp install claude --replace
msp install copilot --replace

Backup files:

  • Codex: $CODEX_HOME/config.msp-backup.toml or ~/.codex/config.msp-backup.toml
  • OpenCode: ~/.config/opencode/opencode.msp-backup.json
  • Claude Code: ~/.claude.msp-backup.json

Restore host config from backup:

msp restore codex
msp restore opencode
msp restore claude
msp restore copilot

Remove the bundled global pi extension:

msp restore pi

Import Existing Servers

msp can import MCP servers from:

  • Codex: msp import codex
  • OpenCode: msp import opencode
  • Claude Code: msp import claude

Provider selection works like this:

  • import codex defaults to provider codex
  • import opencode defaults to provider opencode
  • import claude defaults to provider claude
  • --provider ... overrides the default summary provider

Examples:

msp import codex
msp import --provider opencode codex
msp import opencode
msp import claude

Import behavior:

  • Existing names are skipped after normalization.
  • Entries that launch msp mcp are skipped.
  • Only supported MCP config shapes are imported.
  • If refresh fails during the import batch, msp rolls back the servers added in that run.

Configuration

Default config path:

~/.config/mcp-smart-proxy/config.toml

Override it with --config <PATH>.

The default daemon socket path is derived from this config path with a short stable hash, so different config files still get distinct daemons without exceeding Unix socket length limits.

Example:

[servers.github]
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
description = "Use this for GitHub workflows."
enabled = true
env_vars = ["GITHUB_TOKEN"]

[servers.github.env]
GITHUB_API_URL = "https://api.github.com"
GITHUB_ENTERPRISE_MODE = "false"

[servers.test]
transport = "remote"
url = "https://example.com/mcp"
enabled = false
env_vars = ["DEMO_TOKEN", "DEMO_REGION"]

[servers.test.headers]
Authorization = "Bearer ${DEMO_TOKEN}"
X-Region = "${DEMO_REGION:-global}"

[servers.filesystem]
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]

Notes:

  • The config file stores managed servers only.
  • Servers are enabled by default unless enabled = false.
  • transport is optional. If omitted, msp infers it from command or url.
  • stdio servers use command plus args.
  • remote servers use a Streamable HTTP url plus optional headers.
  • description is an optional manual summary that overrides AI-generated summaries for that server.
  • env stores static variables for the downstream server.
  • env_vars lists variables that msp forwards from its own environment.

Run the Proxy

msp mcp --provider codex

msp mcp is a stdio MCP server entrypoint, not an interactive shell command. Start it from an MCP host such as Codex, OpenCode, or Claude Code, or install it with msp install ....

If you want downstream tools that return structuredContent to expose compact TOON text instead, start the proxy with --output-toon. In that mode, call_tool_in_additional_mcp converts the downstream structured JSON to TOON text, replaces the text content with that TOON payload, and clears structuredContent before returning the result upstream.

When the proxy starts, msp mcp serves the currently cached toolsets immediately and asks the shared daemon to refresh every enabled configured server in the background. If you pass --provider ..., that refresh also regenerates summaries with the selected provider. If you omit --provider, the daemon still refreshes tools and keeps each server's manual description or existing cached summary. The current stdio session keeps using the startup cache snapshot; refreshed cache is used by later sessions or explicit reloads. Background refresh failures are logged by the daemon and do not block MCP readiness.

Inspect and Call Cached MCP Tools from the Terminal

msp cli gives you an interactive terminal view over the same cached MCP inventory that the proxy uses.

List cached MCP servers and their summaries:

msp cli -h

List cached tools for one MCP server:

msp cli github -h

Show one tool's argument help:

msp cli github search_repositories -h

Call one downstream MCP tool through the shared daemon:

msp cli github search_repositories --query rust --page 1

If you want terminal output in TOON when a downstream tool returns structuredContent, add --output-toon before the MCP name:

msp cli --output-toon github search_repositories --query rust --page 1

msp cli starts or reuses the shared daemon, loads the current cached MCP snapshot through the daemon protocol, and routes the final downstream tool call through the daemon as well.

Use the Bundled pi Extension

This repository also includes a project-local pi extension in ./pi-extension.

It detects whether msp is available on the local system, runs msp cli -h once per pi session, caches that output, and appends the cached inventory plus short MSP usage guidance to pi's systemPrompt. That gives pi a lightweight snapshot of which cached MCP servers are currently reachable through msp cli.

The same extension source is embedded in the msp binary, so you can install it globally without copying files out of this repository:

msp install pi

That writes ~/.pi/agent/extensions/msp.ts. If that file already exists, msp install pi overwrites it.

Try the project-local copy without installing anything:

pi -e ./pi-extension/msp.ts

You can still copy or symlink ./pi-extension into .pi/extensions/ or ~/.pi/agent/extensions/ for local development if you want pi to auto-discover it directly from the repository.

If msp is missing or msp cli -h fails, the extension shows one warning and then skips prompt injection for that session.

When your MSP inventory changes, run /reload or start a new pi session so the extension refreshes its cached msp cli -h snapshot. If you installed the global extension with msp install pi, later msp self-updates also refresh that installed ~/.pi/agent/extensions/msp.ts automatically when the bundled extension changes.

Background Self-Update

When msp mcp is running, it checks GitHub Releases every 30 minutes.

  • If a newer build exists for the current platform, it downloads and atomically replaces the current msp binary.
  • It writes a sibling latest-version record next to the binary.
  • If the running process sees that it is older than that record, it restarts itself into the updated binary.
  • If ~/.pi/agent/extensions/msp.ts already exists, the restarted binary refreshes that installed global pi extension automatically when the bundled extension changed.
  • Lock files prevent concurrent updates from racing on the same executable path.

Background self-update requires write access to the installed msp binary path.

Proxy Tool Contract

activate_additional_mcps

Input:

{
  "external_mcp_names": ["github", "filesystem"]
}

Output:

[github]
example_tool: Example description
another_tool: Another description that is longer but still fits in the preview

[filesystem]
read_file: Read the contents of a file

Each section starts with [mcp_name]. Within a section, each output line is tool_name: description-preview. If a tool has no description, the line is just tool_name.

activate_tools_in_additional_mcp

Input:

{
  "external_mcp_name": "github",
  "tool_names": ["example_tool", "another_tool"]
}

Output:

{
  "tools": [
    {
      "name": "example_tool",
      "title": "Example Tool",
      "description": "Example description",
      "input_schema": {}
    },
    {
      "name": "another_tool",
      "title": "Another Tool",
      "description": "Another example",
      "input_schema": {}
    }
  ]
}

call_tool_in_additional_mcp

Input:

{
  "external_mcp_name": "github",
  "tool_name": "example_tool",
  "args_in_json": "{\"owner\":\"octo-org\",\"repo\":\"demo\"}"
}

args_in_json must decode to a JSON object or null.

Limitations

  • Downstream MCP servers must use either stdio or Streamable HTTP transport.
  • Remote OAuth currently assumes an interactive browser-based authorization code flow.
  • Tool discovery depends on metadata cached by reload.
  • The proxy exposes a fixed activation-and-call interface instead of dynamically re-exporting downstream tools as first-class proxy tools.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors