# connectable.sh > connectable.sh is an MCP gateway. Add it to Claude Code, Codex or Cursor as one server, and your agent gets your MCP servers, APIs, databases and web pages as tools, with policy, rate limits, approvals and an audit log in front. Built on the Model Context Protocol by linesofcode. Install: `npm install -g connectable.sh`. Version 0.1.0 (beta). ## How a call travels Your client connects to one URL with one key. The gateway checks each call against the endpoint that key belongs to, dials the upstream with credentials the agent never sees, and writes the call to the audit log. 1. Describe your tools: List connectors in `connectors.json`, or add them in the dashboard. `connectable check` validates the file before anything starts. 2. Create an endpoint and a key: An endpoint decides which connectors a key reaches, with its own rate limit and agent instructions. `connectable endpoints` manages them from a terminal. 3. Add one URL to your client: `claude mcp add --transport http` with the URL and the key. Tools arrive namespaced, like `files__read_file`. ## Wrap a REST or GraphQL API Set `transport` to `openapi` or `graphql` and point `specUrl` at the document. Only the operations you list in `include` become tools, and leaving it out gives you none. Learn more in [Wrapping a REST or GraphQL API](/reference#wrapping-a-rest-or-graphql-api). ```json { "connectors": [ { "name": "stripe", "transport": "openapi", "specUrl": "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json", "headers": { "authorization": "Bearer sk_test_…" }, "include": ["GetCustomers", "GetCharges", "GetChargesCharge"] }, { "name": "linear", "transport": "graphql", "specUrl": "https://api.linear.app/graphql", "headers": { "authorization": "…" }, "include": ["Query.issues", "Query.issue"] } ] } ``` ## Serve a web page as a tool A `web` connector turns each page you name into a read-only tool that returns Markdown, narrowed by `selector`. The origin is fixed, so a `{param}` fills a path or query and never the host. ```json { "connectors": [ { "name": "docs", "transport": "web", "pages": [ { "tool": "changelog", "url": "https://bun.com/blog", "selector": "main" }, { "tool": "wikipedia", "url": "https://en.wikipedia.org/wiki/{title}", "params": [{ "name": "title", "description": "Article title" }], "selector": "#mw-content-text", "maxChars": 8000 } ] } ] } ``` ## Query a database read-only Set `"driver"` to `postgres`, `mysql`, `sqlite`, `duckdb` or `s3`. Every SQL driver runs one read-only statement per call with a timeout and a `maxRows` cap that is reported when it bites. `execute` exists only with `allowWrites`. ```json { "connectors": [ { "name": "warehouse", "transport": "driver", "driver": "postgres", "options": { "connectionString": "{{ env.WAREHOUSE_URL }}", "schemas": ["analytics"], "maxRows": 200 } }, { "name": "exports", "transport": "driver", "driver": "s3", "options": { "bucket": "acme-exports", "prefix": "reports/", "accessKeyId": "{{ env.AWS_ACCESS_KEY_ID }}", "secretAccessKey": "{{ env.AWS_SECRET_ACCESS_KEY }}" } } ] } ``` ## All of MCP, not only tools Resources, prompts, sampling and elicitation go through the same governed path as `tools/call`. A resource is re-published as `connectable:///…`, so two connectors never hand out one URI. | Capability | Through the gateway | | --- | --- | | `tools/list`, `tools/call` | Namespaced `connector__tool`. Policy, aliases, pins, approvals, caps, quotas, audit. | | `outputSchema`, `structuredContent` | Passed through unchanged. Which tools declare one, and why the rest do not: [`output-schemas.md`](output-schemas.md). | | `resources/*` | Re-published under `connectable:///`. Reads are governed. | | `prompts/list`, `prompts/get` | Namespaced `connector__prompt`, governed on the same path. | | `sampling/createMessage` | Forwarded from the upstream to the client whose call provoked it. Needs a deployment that holds sessions; see below. | | `elicitation/create` | Same. | | `*/list_changed`, `resources/updated` | Forwarded, so a client re-lists instead of trusting a stale one. | ## Tell the agent what's where An agent picks tools by name, and `db1__query` says nothing about what `db1` holds. A top-level `instructions` string in `connectors.json` reaches the model at `initialize`. Hosted, each endpoint carries its own. ```json { "instructions": "Orders are in lake (DuckDB, one Parquet file per day under ~/data/orders). Support guides are in notes. Never write to bucket.", "connectors": [ ... ] } ``` ## Credentials stay in the store Connectors reference `{{ secrets.NAME }}`, and the value is encrypted with `CREDENTIAL_ENCRYPTION_KEY` and resolved just before the upstream is dialed. Rotating a key shared by three connectors is one `connectable secrets set`. ```bash connectable secrets set STRIPE_API_KEY sk_live_… connectable connectors create stripe --transport http --url https://mcp.stripe.test/mcp \ --header 'Authorization=Bearer {{ secrets.STRIPE_API_KEY }}' ``` ## Three rate-limit tiers Each API key, endpoint and connector has its own limit, and a call is charged against every tier that applies. `0` means no limit at that tier. A call refused by a later tier refunds the earlier ones. | Tier | Keyed by | Set on | What it is for | | --- | --- | --- | --- | | **Connection** | API key | Each key, under **Endpoints → Keys** | Throttle one misbehaving client without touching the others | | **Endpoint** | Endpoint | The endpoint's settings | The customer quota: fairness and cost control across all its keys | | **Connector** | Connector | The connector's settings | Self-protection: we will not overrun an upstream, whoever is asking | ## Approvals reach a person Subscribe a URL to `approval.requested` and a waiting call reaches whoever can decide it. Each delivery is signed with `x-connectable-signature` and carries names and ids, never arguments or credentials. ```bash connectable webhooks create pagerduty https://hooks.example.com/connectable \ --events approval.requested,approval.expired,kill_switch.engaged # secret whsec_… (shown once — store it where the receiver can read it) connectable webhooks ping $WEBHOOK_ID # one delivery, right now connectable webhooks deliveries $WEBHOOK_ID # what was sent, and what came back ``` ## Refusals an agent can read Every error carries `data.category` (`policy`, `quota`, `auth`, `upstream`, `gateway` or `request`) and a conservative `retryable`. An agent tells "wait" from "never" without parsing English. ```json { "code": -32603, "message": "rate limit exceeded for this endpoint (60/min); retry in 12s", "data": { "category": "quota", "code": "rate_limited", "retryable": true, "detail": { "scope": "endpoint", "retryAfterMs": 12000, "limit": 60 } } } ``` ## An audit log you can check Rows are hash-chained, each one hashed with the row before it. Store the head from `connectable audit chain` somewhere this deployment does not control, and `connectable audit verify` fails on an edit, a gap or a cut from the end. ```bash connectable audit chain # the current head — record this somewhere else connectable audit verify # re-derive the whole chain; exits non-zero on any break ``` ## Connect any MCP client A client gets one URL and one key. The dashboard prints these per client under **Connect a client**, and `connectable import-mcp` reads the servers you already have configured. ```bash claude mcp add --transport http connectable http://localhost:3333/mcp \ --header "Authorization: Bearer $CONNECTABLE_API_KEY" ``` ```bash # Codex reads the key from the environment codex mcp add connectable --url http://localhost:3333/mcp --bearer-token-env-var CONNECTABLE_API_KEY ``` ```json { "mcpServers": { "connectable": { "type": "http", "url": "http://localhost:3333/mcp", "headers": { "Authorization": "Bearer " } } } } ``` ## Local tools, hosted gateway `connectable link pair` connects your laptop to a hosted deployment over an outbound connection, with no open port and no tunnel. The local `connectable.link.json` is final: the gateway can call only what it lists. ```bash connectable link pair --url https://connectable.sh # prints a code; approve it in the dashboard connectable link ./connectable.link.json # serve what that file describes ``` ## Your coding agent as a tool The `agent` driver turns Claude Code, Codex or opencode in a directory into one tool, `run_task`. `permission` is `plan` or `edit`, `maxBudgetUsd` caps a run, and there is no bypass mode. ```json { "name": "claude-code", "transport": "driver", "driver": "agent", "options": { "runner": "claude", "cwd": "~/code/app", "permission": "plan", "maxBudgetUsd": 2 } } ``` ## Run the gateway yourself `connectable data-plane` runs the gateway on your machine and keeps the hosted dashboard. Config comes down without a credential, and audit heads go up signed by a key from `connectable keygen` that never leaves your host. ```bash # On the control plane, once: connectable deployments create prod-gateway # prints a cbd_… token, shown once # On your own machine, with the self-host binary (One binary, below): connectable keygen > deployment.key # Ed25519, never leaves this host export CONNECTABLE_DEPLOYMENT_TOKEN=cbd_… connectable data-plane --url https://app.connectable.sh --key deployment.key ``` ## Plans for the hosted dashboard Hosted plans are a flat monthly base with seats above it. Calls are unlimited and never charged for on any plan, and `stdio` connectors start at Team. | Plan | Price | Seats | Connectors, endpoints, agents, calls | Retention | stdio | | --- | --- | --- | --- | --- | --- | | Free | $0 | 1 | 5 connectors, 2 endpoints, unlimited calls and agents | 1 day | no | | Team | $50/mo, 5 seats included, then $10/seat | unlimited | unlimited | 180 days | yes | | Enterprise | Same list price, negotiated terms | unlimited | unlimited | unlimited | yes | ## Questions ### What is an MCP gateway? One MCP server that sits between your agents and every tool they use. A client connects to it once, and it forwards each call to the right upstream with policy, limits and approvals applied on the way. ### Can I use the MCP servers I already have? `connectable import-mcp claude-desktop` reads the servers you already configured and writes them into `connectors.json`, with credentials moved into `.env.connectable`. `cursor`, `windsurf`, `vscode`, `codex` and `claude-code` work the same way. ### Does the agent ever see my API keys? No. Connectors reference `{{ secrets.NAME }}`, the value is resolved just before the upstream is dialed, and a value an upstream echoes back reaches the model as `[secret:NAME]`. ### Can an agent write to my database? Only with `allowWrites`. Without it, every SQL driver runs one read-only statement per call and the `execute` tool does not exist. ### Do I have to self-host it? No. Use the hosted dashboard at connectable.sh, or run the self-host binary with connectors in a JSON file. `connectable data-plane` does both: the gateway on your machine, the dashboard hosted. ### Which clients work? Any MCP client that speaks HTTP. The dashboard prints a setup snippet for Claude Code, Codex, Cursor, Claude Desktop, Windsurf and opencode. ### What does it cost? The hosted Free plan is $0 for one seat, five connectors and two endpoints. Team is $50 a month with five seats, then $10 a seat. Calls are never charged for. ## Docs - [Reference](https://connectable.sh/reference): the README in full - [Guide](https://connectable.sh/guide): USAGE.md in full, from local setup to hosted agents - [AGENTS.md](https://connectable.sh/AGENTS.md): how an agent should set this up - [Landing page as Markdown](https://connectable.sh/index.md) - [Repository](https://github.com/TimMikeladze/connectable)