MCP server library + ratel-mcp CLI: expose a Ratel tool catalog as a Model Context Protocol server. Manage upstream MCP servers, OAuth, and Claude Code import from one binary.
@ratel-ai/mcp-server
Expose a Ratel catalog over MCP — and manage your MCP scopes from one CLI.
Ratel core • Roadmap • Discord
@ratel-ai/mcp-server is two things in one package:
- a library that takes a Ratel
ToolCatalogand exposes it as a Model Context Protocol server — the MCP client (Claude Desktop, an agent framework, an@modelcontextprotocol/sdkClient) seessearch_tools+invoke_toolinstead of every upstream's full tool list; - a CLI (
ratel-mcp) that drops the gateway between an MCP host (Claude Code, Cursor, ChatGPT) and an arbitrary set of upstream MCP servers — with Claude-compatible config UX, three-scope hierarchy, OAuth 2.1 / PKCE for HTTP+SSE upstreams, and a one-shotmcp importwizard for migrating an existing Claude Code MCP setup.
This is the inverse of @ratel-ai/sdk's registerMcpServer, which ingests an upstream MCP server's tools into a catalog. createMcpServer exposes a catalog as an MCP server.
Install
# CLI (global install)
pnpm add -g @ratel-ai/mcp-server
# Library (in a TS/Node project)
pnpm add @ratel-ai/mcp-server @ratel-ai/sdk @modelcontextprotocol/sdk
Or skip the install and run the CLI on-the-fly:
npx -y @ratel-ai/mcp-server --help
CLI quickstart
ratel-mcp mirrors claude mcp add's flag layout — any invocation that works against Claude Code's CLI works here unchanged.
# Add an upstream (stdio)
ratel-mcp mcp add --scope user airtable -e API_KEY=xyz -- npx -y airtable-mcp-server
# Add an upstream (HTTP, with OAuth)
ratel-mcp mcp add --scope user stripe https://mcp.stripe.com --transport http
# List what's configured
ratel-mcp mcp list
# Import your existing Claude Code MCP setup into ratel-mcp's scopes, then point Claude at ratel-mcp
ratel-mcp mcp import
# Start the gateway over stdio (this is what Claude Code spawns after `import`)
ratel-mcp serve --config ~/.ratel/config.json
Run ratel-mcp <group> for the verbs in a group:
| Group | Verbs |
|---|---|
| mcp | add, remove, list, get, edit, import, link, auth |
| backup | list, undo |
| (top-level) | serve |
ratel-mcp mcp add — Claude-compatible
ratel-mcp mcp add [flags] <name> -- <command> [args...] # stdio
ratel-mcp mcp add [flags] <name> <url> # http / sse
| Flag | Meaning |
|---|---|
| --transport stdio\|http\|sse | Force a transport. Inferred otherwise (URL → http, -- → stdio). |
| --scope user\|project\|local | Which scope to write to. Defaults to user. |
| --env KEY=VALUE / -e KEY=VALUE | Env var for stdio entries. Repeatable. |
| --header "Name: Value" | HTTP header for http/sse entries. Repeatable. |
| --client-id <id> / --client-secret <s> / --callback-port <n> / --oauth-scope <s> | OAuth client config for http/sse entries. DCR is preferred — pass --client-id only when the upstream doesn't support it. |
| --description <text> | Human description of the server. Wins over the auto-fetched upstream instructions. |
| --no-fetch-description | Skip the auto-probe — no connect, no description fetch, no OAuth flow. |
| --force | Overwrite an existing entry of the same name in the chosen scope. |
By default, mcp add connects to the upstream and stores its server-level instructions (per the MCP spec) as the entry's description. For http/sse upstreams it drives the OAuth 2.1 / PKCE flow inline (browser opens, tokens persist at ~/.ratel/oauth/<name>.json).
Three-scope hierarchy
ratel-mcp mirrors Claude Code's MCP scoping with three logical configs:
| Scope | Path | Notes |
|---|---|---|
| user | ~/.ratel/config.json | Per-user, applies everywhere. |
| project | <root>/.ratel/config.json | Committed alongside the repo. |
| local | <root>/.ratel/config.local.json | Per-user-per-project; add to your project's .gitignore. |
When you run ratel-mcp serve --config a.json --config b.json --config c.json, the configs are merged in order — last wins on mcpServers key collisions. The import wizard wires the right --config chain into Claude Code at each scope.
OAuth flow
HTTP and SSE upstreams that require OAuth authorization run through ratel-mcp's loopback PKCE flow. From the CLI:
ratel-mcp mcp add --scope user my-upstream https://mcp.example/mcp [--client-id <id>] [--callback-port <n>] [--oauth-scope "<s>"]— records the entry and drives the OAuth flow inline.ratel-mcp mcp auth my-upstream— refresh-first. If arefresh_tokenis on disk, rotates silently (no browser). Falls back to PKCE only when refresh fails.ratel-mcp mcp auth --check— read-only status report: tokens present, refresh availability, time-to-expiry.ratel-mcp mcp list— shows a single-line auth column per entry:ok/expired/needs auth/n/a.
When the gateway boots, every HTTP/SSE upstream with stored tokens runs through a proactive refresh. A 401 during a live invoke_tool returns { error: "needs_auth", upstream } so the agent can branch and call the auth MCP tool to recover.
Telemetry
ratel-mcp serve writes one JSON line per event to ~/.ratel/telemetry/<project-slug>/<ISO-ts>-<short>.jsonl by default — every search, invoke, gateway call, upstream MCP call, and OAuth event flows through the same JSONL (ADR 0009). Best-effort, sampleable, lossy on backpressure — query-log shaped, not oplog.
| Flag | Env | Purpose |
|---|---|---|
| --telemetry off | RATEL_TELEMETRY=off | Disable telemetry for this run. |
| --telemetry-file <path> | — | Override the JSONL path verbatim (no slugging). |
| — | RATEL_TELEMETRY_DIR | Override the default telemetry root. |
For summarizing the resulting JSONL stream, see @ratel-ai/cli's ratel inspect — it shares the on-disk format.
Backups & undo
Every import, link, add, edit, and remove snapshots the files it touches into ~/.ratel/backups/<ISO>/ with a manifest.json. ratel-mcp backup list shows what's available; ratel-mcp backup undo (deliberately hidden from --help) restores the most recent set.
Library quickstart
import { ToolCatalog } from "@ratel-ai/sdk";
import { createMcpServer } from "@ratel-ai/mcp-server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const catalog = new ToolCatalog();
catalog.register({
id: "read_file",
name: "read_file",
description: "Read a file from local disk.",
inputSchema: { type: "object", properties: { path: { type: "string" } } },
outputSchema: { type: "object", properties: { contents: { type: "string" } } },
execute: async ({ path }) => ({ contents: await fs.readFile(path, "utf8") }),
});
const handle = await createMcpServer(catalog, {
name: "my-gateway",
version: "0.1.0",
transport: new StdioServerTransport(),
});
// later, on shutdown:
await handle.close();
The MCP client connected to the other end will see exactly two tools: search_tools and invoke_tool. The catalog's tools are reachable through invoke_tool, never listed directly — that's the whole point (see ADR 0003 in ratel-ai/ratel).
buildGatewayFromConfig
Higher-level entrypoint that takes a parsed Ratel config (an mcpServers map mirroring Claude Code's shape) and spins up an upstream MCP Client per entry, registers each upstream's tools into a fresh catalog, and returns the catalog plus per-upstream metadata.
import { buildGatewayFromConfig, parseConfig } from "@ratel-ai/mcp-server";
const config = parseConfig(JSON.parse(await fs.readFile("./ratel-config.json", "utf8")));
const gateway = await buildGatewayFromConfig(config, {
logger: (m) => console.error(m),
});
// gateway.catalog -> ToolCatalog with every upstream tool registered
// gateway.upstreamServers -> [{ name, description?, toolCount }] for the search-tools description block
// await gateway.close() -> tears down every upstream client
If any single upstream fails to start, buildGatewayFromConfig logs the failure and the rest still register — the gateway stays available. The handle exposes runAuthFlow() (refresh-first; PKCE fallback) for HTTP/SSE upstreams marked needsAuth, and setListChangedNotifier() so the MCP server can re-list after a successful flow.
Config shape
The config mirrors Claude Code's .claude.json mcpServers shape:
{
"mcpServers": {
"ev": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"],
"description": "filesystem & shell utilities"
},
"remote": {
"type": "http",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer xyz" }
}
}
}
type defaults to "stdio" when absent. description is optional metadata — used to seed the agent's awareness of each upstream via search_tools's description, never sent over the upstream transport. stdio and http are wired up by defaultTransportFactory; sse and unknown types are accepted by parseConfig but skipped at runtime by the default factory (provide your own factory for sse).
Result wrapping
Every tools/call response carries the gateway's return value as a JSON-serialized text block; plain-object returns are also surfaced as structuredContent:
{
"content": [{ "type": "text", "text": "{\"foo\":1}" }],
"structuredContent": { "foo": 1 }
}
Arrays (e.g. the hits returned by search_tools) only travel in content[0].text, since MCP requires structuredContent to be a JSON object.
When invoke_tool drives a tool that was itself registered via registerMcpServer, the upstream's MCP-shaped result ({ content, structuredContent }) is nested inside our structuredContent one level deeper.
invokeToolTool's wrapped error payload ({ error: "..." } for unknown ids or executor throws) flows through as an ordinary structured result rather than an MCP isError: true — clients can branch on the field.
Examples
examples/claude-with-ratel/— Claude Code session fronted byratel-mcpas the only MCP server.
Build & test
pnpm install
pnpm build # tsc → dist/
pnpm typecheck
pnpm lint # biome
pnpm test # vitest
CI runs all of the above on every PR.
License
Elastic License 2.0, with a grant making it free for OSI-approved open-source projects. Non-OSS / commercial production use requires a commercial license. See LICENSE.md.
Related
@ratel-ai/sdk— the TypeScript SDK withToolCatalog,searchToolsTool,invokeToolTool,registerMcpServer. Bundlesratel-ai-core(BM25 retrieval) via NAPI-RS.@ratel-ai/cli— the long-term Ratel artifacts CLI (telemetry inspection today).ratel-ai/ratel— overview, roadmap, ADRs, benchmark links.