Metadata-Version: 2.5
Name: mcp-switchboard
Version: 0.1.1
Summary: AI Tool Gateway: one MCP connector fronting a registry of downstream MCP servers
Project-URL: Repository, https://github.com/StephenFlavin/mcp-switchboard
Project-URL: Documentation, https://github.com/StephenFlavin/mcp-switchboard/blob/main/docs/quickstart.md
License-Expression: PolyForm-Noncommercial-1.0.0 OR PolyForm-Small-Business-1.0.0
License-File: LICENSE.md
Requires-Python: >=3.11
Requires-Dist: fastapi>=0.115
Requires-Dist: fastmcp==4.1.0
Requires-Dist: httpx>=0.27
Requires-Dist: huggingface-hub>=0.25
Requires-Dist: joserfc>=1.0
Requires-Dist: jsonschema>=4.23
Requires-Dist: numpy>=1.26
Requires-Dist: onnxruntime>=1.19
Requires-Dist: pgvector>=0.3
Requires-Dist: psycopg[binary,pool]>=3.2
Requires-Dist: pydantic>=2.8
Requires-Dist: pyyaml>=6.0
Requires-Dist: ruamel-yaml>=0.19.1
Requires-Dist: tokenizers>=0.20
Requires-Dist: uvicorn[standard]>=0.30
Description-Content-Type: text/markdown

# 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 `none` and `api_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`](docs/poc-results.md). The task list is [`docs/ai-tool-gateway-poc-tasks.md`](docs/ai-tool-gateway-poc-tasks.md); the benchmark is [`eval/README.md`](eval/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`](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](docs/ai-tool-gateway-plan.md): architecture, auth, registry, search pipeline, security, delivery phases
- [Local POC plan](docs/ai-tool-gateway-poc-plan.md): hypotheses H1–H5, mock services, test layers, scenarios S1–S10
- [POC task list](docs/ai-tool-gateway-poc-tasks.md): epics E0–E14 with "done when" checks (ticked as they pass)
- [Benchmark v2 plan](docs/benchmark-v2-plan.md): generated descriptions, build-time enhancement, the 32–512 tool sweep and scenarios S17–S26
- [Benchmark v2 analysis plan](docs/benchmark-v2-analysis-plan.md): how the sweep's results are checked, read and written up, fixed before they arrived
- [POC results](docs/poc-results.md) and the [runbook for manual steps](docs/RUNBOOK.md)
- [Mock services](mocks/README.md), [evaluation results](eval/README.md)

## 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](docs/quickstart.md).

```bash
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`](https://docs.astral.sh/uv/) and Node.js 20+ (for `npx`). Python 3.11 is installed by `uv`.

```bash
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](docs/RUNBOOK.md#0-set-up-and-reproduce-the-automated-results-10-min) 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](docs/auth-plan.md)).
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

```bash
claude mcp add --transport http tool-gateway http://localhost:8080/mcp
```

### Codex

```bash
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:

```toml
[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`](https://www.npmjs.com/package/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:

```bash
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](docs/RUNBOOK.md#8-optional-pi-with-a-local-model-eg-qwen-on-an-rtx-3090).

### Other clients

- **MCP Inspector:** `npx @modelcontextprotocol/inspector`, then connect to `http://localhost:8080/mcp` (streamable HTTP)
- **Claude Desktop, claude.ai, ChatGPT:** see [RUNBOOK.md](docs/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`).

<details>
<summary>Manual setup (what bootstrap does)</summary>

```bash
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
```

</details>

## 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.

```bash
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:

```bash
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](docs/RUNBOOK.md#10-t176-onboarding-a-server). The design is in the [onboarding plan](docs/catalogue-onboarding-plan.md) and the [OpenAPI source plan](docs/openapi-source-plan.md).

## Test and evaluate

```bash
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](LICENSE.md)). Contributions need the [CLA](CLA.md)
([CONTRIBUTING.md](CONTRIBUTING.md)).
