mcp-switchboard
A single MCP connector that business users add to Claude or ChatGPT, fronting a catalogue of internal MCP servers. Instead of exposing hundreds of downstream tools, the gateway exposes three meta-tools and finds, authorises and runs the right tool on demand, as the signed-in user.
Status: proof of concept complete, concept proven. Built and tested locally on MCP sources, with auth types
noneandapi_key, against mock data. Measured against the same models connected to the servers directly: through the switchboard, cost and accuracy per scenario stay flat as the tool count grows to ~500, and writes wait for the user's agreement. Direct connections carry every tool definition on every request. Findings and the ordered next steps:docs/poc-results.md. The task list isdocs/ai-tool-gateway-poc-tasks.md; the benchmark iseval/README.md.
How it works
| Meta-tool | Purpose |
|---|---|
search_tools |
Find tools for one need: permission filter, hybrid vector + keyword search, resolver tools for required IDs; abstains with no_confident_match |
call_tool |
Run a tool: schema validation, role policy, per-source auth strategy, result shaping and handles, confirmation for writes (token, or MCP elicitation with GATEWAY_CONFIRM), prompt-injection flags, structured errors, audit |
list_categories |
Browse what exists |
Registration is declarative: scripts/catalogue.yaml lists the auth profiles, sources and reviewed tool descriptions, and the gateway applies it at startup (scripts/register_all.sh re-applies it). Listing a tool approves it; tools a server adds later stay out of search until they're added to the file. Postgres holds the search index and runtime state (handles, confirmations, logs).
The chat model plans and fills arguments. The gateway handles search, auth, policy, execution and audit. Downstream auth is pluggable per source through auth profiles (none, api_key today; entra_obo, service_mesh, … later).
Docs
- Full design: architecture, auth, registry, search pipeline, security, delivery phases
- Local POC plan: hypotheses H1–H5, mock services, test layers, scenarios S1–S10
- POC task list: epics E0–E14 with "done when" checks (ticked as they pass)
- Benchmark v2 plan: generated descriptions, build-time enhancement, the 32–512 tool sweep and scenarios S17–S26
- Benchmark v2 analysis plan: how the sweep's results are checked, read and written up, fixed before they arrived
- POC results and the runbook for manual steps
- Mock services, evaluation results
Quick start
To run your own gateway, no clone needed: one command installs switchboard and creates a deployment repo (Dockerfile, catalogue, sign-in), then switchboard add onboards servers with Claude Code. Follow docs/quickstart.md.
curl -LsSf https://github.com/StephenFlavin/mcp-switchboard/releases/latest/download/install.sh | sh -s my-switchboard
The rest of this section sets up the POC for development and the evaluations. Requires Docker, uv and Node.js 20+ (for npx). Python 3.11 is installed by uv.
scripts/bootstrap.sh # .env with fresh secrets, deps, mock data, containers, gateway on :8080, catalogue
Bootstrap also copes with corporate TLS proxies and a local Postgres on port 5432; see
RUNBOOK.md if it stops.
Afterwards, scripts/gateway.sh start|stop|restart|status manages the gateway.
The gateway serves MCP (streamable HTTP) at http://localhost:8080/mcp with three tools:
search_tools, call_tool and list_categories. Point your agent at it, then try: "What's the
total value of stock in the Hastings warehouse?"
Signing in. The gateway is its own OAuth authorization server (auth plan).
When a client connects, it opens a browser at the gateway's login page. Out of the box there is
one account, testUserOnly / doNotUse!, for local testing only: the gateway refuses to
start with it when GATEWAY_ENV=production or GATEWAY_PUBLIC_HOSTS is set, unless
GATEWAY_ALLOW_DEFAULT_USER=true. Add your own users and SSO providers (Entra ID, Google, Auth0,
any OIDC provider) under inbound_auth: in the catalogue. Clients without OAuth support (the pi
adapter, the eval harness) need GATEWAY_DEV_AUTH=true: no sign-in, and every caller is
poc-user. A .env from an older bootstrap still has true, so set it to false to sign in.
Claude Code
claude mcp add --transport http tool-gateway http://localhost:8080/mcp
Codex
codex mcp add tool-gateway --url http://localhost:8080/mcp
codex mcp list # tool-gateway should be listed as enabled
This writes the following to ~/.codex/config.toml, which you can also edit by hand:
[mcp_servers.tool-gateway]
url = "http://localhost:8080/mcp"
pi coding agent
pi has no built-in MCP support, so install the community pi-mcp-adapter
package (third-party: review it before installing; it needs pi ≥ 0.84, published as
@earendil-works/pi-coding-agent), then configure the server for your project:
pi install npm:pi-mcp-adapter
mkdir -p .pi && cat > .pi/mcp.json <<'JSON'
{
"mcpServers": {
"tool-gateway": { "url": "http://localhost:8080/mcp", "directTools": true }
}
}
JSON
pi # from the repo root; trust the project server when asked
/mcp shows the server status.
directTools: true exposes the gateway's three meta-tools to pi directly. Without it, the adapter
puts its own search proxy in front of the gateway's search, so the model has to search twice.
Use ~/.config/mcp/mcp.json instead of .pi/mcp.json to make it available in every project.
To run with a local model and score it on S1–S10, see
RUNBOOK.md §8.
Other clients
- MCP Inspector:
npx @modelcontextprotocol/inspector, then connect tohttp://localhost:8080/mcp(streamable HTTP) - Claude Desktop, claude.ai, ChatGPT: see RUNBOOK.md. Web clients need a public URL (
scripts/tunnel.sh, mock data only).
The admin API is at /admin. Every route except /admin/health needs Authorization: Bearer $GATEWAY_ADMIN_TOKEN, or a signed-in user's token with a role in inbound_auth.adminRoles (default gateway-admin).
Manual setup (what bootstrap does)
cp .env.example .env # set GATEWAY_ADMIN_TOKEN and PRICING_API_KEY
uv sync
uv run python mocks/seed.py # mocks/mock.db + eval/expected.json
docker compose up -d --wait # Postgres (:5433) + the mock services
scripts/decision.sh up # optional: the decision model for reranking (DECISION_PROVIDER,
# default Laya, built here; jev/clm/systemone skip that)
uv run python scripts/fetch_model.py # embedding model
scripts/gateway.sh start # or: uv run uvicorn gateway.main:app --port 8080
# the gateway applies scripts/catalogue.yaml at startup; after editing it: scripts/register_all.sh
Onboarding a source
catalogue add takes a new MCP server, or a REST API with an OpenAPI spec, to a pull request: it probes the source, asks what only a person can answer, has an enricher write descriptions and example questions, and writes the catalogue, lock and enhanced files. The enricher is the template (the default) or a coding harness you already have installed and logged in to: no API key. Today that is Claude Code.
uv run python -m gateway.registry.catalogue add # asks; add --help lists every flag
The same run without prompts, for a REST API, with Claude Code writing the wording:
uv run python -m gateway.registry.catalogue add --no-input \
--type openapi --spec https://erp.internal/openapi.json --base-url https://erp.internal/api \
--allow --id erp --auth api_key --key-ref ERP_API_KEY --roles ops \
--default-categories hr-records --harness claude --model sonnet --yes
Steps, flags, the answers file and troubleshooting are in RUNBOOK.md, section 10. The design is in the onboarding plan and the OpenAPI source plan.
Test and evaluate
uv run pytest # unit + integration (needs Postgres)
uv run ruff check && uv run ruff format --check
uv run python eval/search_eval.py --engine embeddings # recall@k, abstain, latency
uv run python scripts/synth_catalogue.py # +422 search-only tools, then:
uv run python eval/search_eval.py --catalogue synthetic
uv run python eval/token_report.py # S1 tokens: handles off/on, composite
uv run python eval/run_scenarios.py # S1-S10 through headless Claude Code
uv run python eval/run_scenarios.py --client pi --model <provider/id> --label pi-local # or pi
uv run python eval/run_scenarios.py --client pi --model <provider/id> --label pi-local-direct --direct # no gateway
scripts/eval_scale.sh <label>=<provider/model> # switchboard vs direct MCP at 32-512 tools
uv run python -m gateway.registry.catalogue describe # render the gateway's own descriptions
scripts/build_catalogue.sh # build-time step: enhance descriptions, check
Licence
Use it under the PolyForm Noncommercial 1.0.0 or the PolyForm Small Business 1.0.0 licence, whichever fits you; commercial use that neither covers needs a commercial licence from the copyright holder (LICENSE.md). Contributions need the CLA (CONTRIBUTING.md).
Metadata
Release files for mcp-switchboard 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcp_switchboard-0.1.1.tar.gz | 243.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_switchboard-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 499.7 kB
Release files / mcp_switchboard-0.1.1.tar.gz
| Download URL | mcp_switchboard-0.1.1.tar.gz |
|---|---|
| Size | 243.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d6021fdc6845caa9a0ff7b38593637ab8caedbc7f8ac9e228863c25902b05cd0
|
|
BLAKE2b-256 checksum How to use checksums |
82f40859f29d7c687a548f1fd5d10a14cd310af0ff66b47765caf7258e7937bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / mcp_switchboard-0.1.1-py3-none-any.whl
| Download URL | mcp_switchboard-0.1.1-py3-none-any.whl |
|---|---|
| Size | 256.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
96ffb06a32ed930484ae9756b9531fede7575393f5ab51316a6030b66bb87ab1
|
|
BLAKE2b-256 checksum How to use checksums |
25323cd7f06c07d74e9a5c56cf91562b55600c270156ce5a2e42a281fe2aa518
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log