Composio MCP Bridge
by AhmadTheTech
README.md
# Composio MCP Bridge
A remote MCP (Model Context Protocol) server for [Poke](https://poke.com) (or
any MCP client) that proxies tool calls straight through to **the caller's
own Composio account** — bring-your-own-key, not a shared quota.
```
Poke agent --(your Composio API key, every request)--> this server --(fresh Composio client)--> Composio --> Gmail/Slack/Sheets/etc
```
There is no "connect your account to us" step and no shared Composio project.
Each person supplies their own Composio API key on every single request. The
server builds a brand new Composio SDK client for that one request, uses it,
and throws it away — nothing about the key is ever logged, cached, or
written to disk.
## Auth flow
1. **You** sign up at [composio.dev](https://composio.dev) and grab an API
key from Settings → API Keys. That's your own Composio account, your own
quota, your own connected apps.
2. When setting up the custom MCP integration in Poke, set the **Server URL**
to:
```
https://<your-deployment>/mcp?composio_api_key=YOUR_COMPOSIO_KEY
```
(Query param, because Poke's custom MCP integrations can't reliably send
custom HTTP headers. If your MCP client *can* send headers, `Authorization:
Bearer <key>` or `x-composio-api-key: <key>` both work too — checked in
that order, header wins if both are present.)
3. From here on, every tool call this server makes runs against *your*
Composio account:
- `list_toolkits` — see what apps Composio supports (Gmail, Slack,
GitHub, Google Sheets, …).
- `connect_toolkit` — get a **Composio Connect Link** (a hosted Google/
Slack/etc sign-in URL) for a toolkit you haven't connected yet. Show
that link in chat; once the user finishes sign-in, the connection lives
in your Composio account.
- `list_connections` — see what's already connected.
- `get_tools` — list the exact Composio tool slugs available for a
toolkit (and what arguments they expect).
- `execute_tool` — actually run one, e.g. `tool_slug: "GMAIL_SEND_EMAIL"`.
If the toolkit isn't connected yet, this automatically returns a fresh
Connect Link instead of a raw error, so the agent can hand it straight
to the user without a second round trip.
4. **Multiple people under one key** (optional): pass an `x-composio-user-id`
header or `user_id` query param to separate multiple end users inside the
same Composio account/project. If you don't set one, everything uses a
single `default` user id — the common case for one person automating
their own accounts.
## Why this shape
- **No server-side secrets to steal.** The server's own `.env` has zero
Composio credentials in it — see `.env.example`. If this deployment were
ever compromised, there's no shared master key sitting in it to leak.
- **True multi-tenant for free.** Anyone with their own Composio key can
point their own Poke integration at the same deployed URL and get their
own isolated set of connections — no per-user database, no OAuth app
registered under your name.
- **Real MCP SDK, not hand-rolled JSON-RPC.** Built on
`@modelcontextprotocol/sdk`'s `StreamableHTTPServerTransport` in stateless
mode — a fresh `McpServer` + transport per request, torn down right after.
This means it correctly speaks `initialize`, `tools/list`, `tools/call`,
and `ping` per spec, instead of reimplementing the protocol by hand.
## Security
- **Never logged, cached, or persisted.** The API key only ever exists in
request-scoped memory, for the lifetime of the one call it arrived with.
- **Format validation.** Keys are checked against a safe length/charset
pattern before ever being used, so obviously malformed input gets a clean
400 instead of hitting Composio.
- **Rate limiting per key.** An in-memory limiter keyed by a SHA-256
fingerprint of the API key (never the raw key itself) caps requests per
rolling minute (`RATE_LIMIT_PER_MINUTE`, default 30). This is best-effort:
fine on a single long-running process (Railway/Fly.io); on serverless
platforms with multiple isolates (Vercel) each isolate keeps its own
counters, so for a hard cross-instance guarantee swap this for a shared
store like Upstash Redis.
- **HTTPS only in production.** Requests arriving over plain HTTP (checked
via `x-forwarded-proto`) get rejected with 400, unless `ALLOW_INSECURE=true`
for local testing.
- **Clean error messages, no stack traces.** Bad/expired keys, 429s from
Composio, and unexpected failures are all mapped to short, safe messages —
see `src/errors.ts`. Internal exceptions never reach the client verbatim.
## Project layout
```
src/
server.ts entrypoint for standalone Express (Railway/Fly.io/local)
app.ts Express app: HTTPS check, key extraction, rate limit, MCP endpoint
tools.ts the 5 MCP tools, all proxying to the per-request Composio client
composio.ts creates a fresh Composio SDK client per request
keyAuth.ts pulls the API key + optional user id off the request
security.ts key fingerprinting, log scrubbing, rate limiter
errors.ts maps Composio/axios errors to clean user-facing messages
api/mcp.ts Vercel serverless adapter (reuses src/app.ts)
```
## Deploying
### Railway or Fly.io (standalone Express)
```bash
npm install
npm run build
npm start # or let the platform run `npm run build && npm start`
```
A `Dockerfile` and `fly.toml` are included for Fly.io (`fly launch`, `fly deploy`).
Railway auto-detects Node from `package.json`, no extra config needed.
Your MCP endpoint will be: `https://<your-app-domain>/mcp`
### Vercel
```bash
npm i -g vercel
vercel
vercel --prod
```
`vercel.json` rewrites `/mcp` → `/api/mcp`, so the endpoint is still just:
`https://<your-vercel-domain>/mcp`
## Environment variables
See `.env.example`. There are no per-user secrets here — only server
behavior config:
| Var | Purpose |
|---|---|
| `PORT` | Port for the standalone server (ignored on Vercel) |
| `NODE_ENV` | Set to `production` to enable the HTTPS-only check |
| `RATE_LIMIT_PER_MINUTE` | Max requests per key per rolling minute (default 30) |
| `ALLOW_INSECURE` | Set `true` only for local HTTP testing |
## A note on tool slugs
`execute_tool` and `get_tools` pass exact Composio tool slugs straight
through (e.g. `GOOGLESHEETS_BATCH_GET`, `GMAIL_SEND_EMAIL`). Always call
`get_tools` for a toolkit first to confirm the current slug names and
expected arguments — Composio's own catalog is the source of truth, and
slugs occasionally get renamed as toolkits evolve.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessSyncing