@ratel-ai/mcp-server
OfficialClick on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@ratel-ai/mcp-serversearch for tools related to database"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@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/sdkOr skip the install and run the CLI on-the-fly:
npx -y @ratel-ai/mcp-server --helpCLI 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.jsonRun ratel-mcp <group> for the verbs in a group:
Group | Verbs |
|
|
|
|
(top-level) |
|
ratel-mcp mcp add — Claude-compatible
ratel-mcp mcp add [flags] <name> -- <command> [args...] # stdio
ratel-mcp mcp add [flags] <name> <url> # http / sseFlag | Meaning |
| Force a transport. Inferred otherwise (URL → http, |
| Which scope to write to. Defaults to |
| Env var for stdio entries. Repeatable. |
| HTTP header for http/sse entries. Repeatable. |
| OAuth client config for http/sse entries. DCR is preferred — pass |
| Human description of the server. Wins over the auto-fetched upstream |
| Skip the auto-probe — no connect, no description fetch, no OAuth flow. |
| 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 |
| Per-user, applies everywhere. |
project |
| Committed alongside the repo. |
local |
| Per-user-per-project; add to your project's |
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 |
|
| Disable telemetry for this run. |
| — | Override the JSONL path verbatim (no slugging). |
— |
| 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 clientIf 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 # vitestCI 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.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ratel-ai/ratel-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server