Custom MCP cookbook

Need an integration Leash doesn't ship — Intercom, an in-house MCP server, a third-party MCP we haven't pre-built? Register the URL and bearer token on the dashboard, fetch the config from your app, and connect. The bearer never lives in your app code.

When to use this

  • The integration speaks MCP (most modern internal tools do, plus Anthropic's and Cloudflare's server registries).
  • Otherwise — if it's plain OAuth — use Custom OAuth providers instead.

1. Register the MCP server on the dashboard

On the org page (/dashboard/organization), the “Custom MCP servers” card asks for:

  • A slug your app uses to look it up (e.g. intercom, acme-tools)
  • The MCP URL (must be HTTPS)
  • Auth: none for public MCPs, or bearer with a token (encrypted at rest with your org's KMS key)

Intercom example

For Intercom's remote MCP, slug = intercom, URL = https://mcp.intercom.com/sse, auth = bearer with your Intercom access token.

2. Fetch the config from your app

GET /api/integrations/mcp-config/<slug> on the platform returns { url, headers } with the bearer Authorization already attached. Plug those two values into whichever MCP client your app uses — Leash isn't in the request path between your app and the MCP.

app/api/sync-intercom/route.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
export async function GET(req: Request) {
// 1. Pull the URL + Authorization header from Leash
const cfgRes = await fetch('https://leash.build/api/integrations/mcp-config/intercom', {
headers: {
'X-API-Key': process.env.LEASH_API_KEY!,
cookie: req.headers.get('cookie') ?? '',
},
})
if (!cfgRes.ok) {
return Response.json({ error: 'mcp_config_unavailable' }, { status: cfgRes.status })
}
const { data: cfg } = await cfgRes.json() as { data: { url: string; headers: Record<string, string> } }
// 2. Connect with the official MCP client
const transport = new StreamableHTTPClientTransport(new URL(cfg.url), {
requestInit: { headers: cfg.headers },
})
const mcp = new Client({ name: 'sync-intercom', version: '1.0.0' })
await mcp.connect(transport)
// 3. Call tools
const result = await mcp.callTool({
name: 'list_conversations',
arguments: { state: 'open', limit: 50 },
})
return Response.json({ conversations: result.content })
}

3. Inject secrets your MCP server needs

For self-hosted MCP servers — say, an internal Python service you built — the deployed Leash app may need extra env vars (a webhook signing secret, a database URL the MCP itself can't reach). Add them to the org's secret sources and declare them in your repo's .env.example:

.env.example
LEASH_API_KEY=
INTERCOM_MCP_URL=
INTERCOM_TOKEN=

The MCP URL and bearer token registered on the dashboard are different from these — they're fetched through /api/integrations/mcp-config/<slug> at runtime so they rotate without redeploys. Use plain env vars only for things the MCP server itself consumes.

How rotation behaves

Rotating a value in your source updates Leash immediately, but when running app code sees the new value depends on how it reads the secret:

Read mechanismRotation behavior
process.env.XBake-time. The value is mounted into the Cloud Run revision at deploy. Re-deploy (or hit “sync now”) to pick up a new value.
await leash.env.get('X')Runtime fetch. The next call after rotation gets the new value (per-instance cache, ~60s); { fresh: true } bypasses the cache.
Custom MCP bearerPer-call resolve. The token is fetched fresh via getCustomMcpConfig on every call — picks up the new value on the next call.

Failure modes

  • HTTP 404 with code: 'unknown_mcp_server' — slug not registered (or revoked). Check the dashboard.
  • HTTP 402 — Custom MCP requires Growth+ or higher.
  • HTTP 401 — missing or invalid LEASH_API_KEY.
  • The platform does not validate that the upstream MCP is up. If it's down, your MCP client surfaces the error directly.

Reference: SDK / Custom MCP covers the response shape and the design rationale (config-handoff instead of proxying). Error handling lists every LeashError code.