@ratel-ai/mcp-server
Exposes a Ratel tool catalog over MCP with two tools (search_tools and invoke_tool), and includes a CLI for managing multi-scope MCP configurations, OAuth flows, and telemetry.
README
<div align="center"> <h1>@ratel-ai/mcp-server</h1> <h4>Expose a Ratel catalog over MCP — and manage your MCP scopes from one CLI.</h4>
<p> <a href="https://github.com/ratel-ai/ratel">Ratel core</a> • <a href="https://github.com/ratel-ai/ratel/blob/main/docs/roadmap.md">Roadmap</a> • <a href="https://discord.gg/hdKpx69NR">Discord</a> </p>
<p> <a href="https://www.npmjs.com/package/@ratel-ai/mcp-server"><img src="https://img.shields.io/npm/v/@ratel-ai/mcp-server?label=npm&color=cb3837" alt="npm" /></a> <a href="https://github.com/ratel-ai/ratel-mcp/stargazers"><img src="https://img.shields.io/github/stars/ratel-ai/ratel-mcp?style=social" alt="GitHub stars" /></a> <a href="https://discord.gg/hdKpx69NR"><img src="https://img.shields.io/discord/1478702964003705015?logo=discord&logoColor=white&color=7289da&label=discord" alt="Discord" /></a> <a href="./LICENSE.md"><img src="https://img.shields.io/badge/license-ELv2-blue" alt="license" /></a> </p> </div>
@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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。