conductor
A gateway that aggregates multiple MCP servers into a single endpoint with authentication, group-based access control, and audit logging for AI agents.
README
<div align="center">
conductor
A tool gateway for AI agents — one MCP endpoint, many upstreams, real auth, real audit.
Highlights • Quick start • Configuration • Architecture • Writing a provider • Development
</div>
conductor speaks Model Context Protocol to your agent and aggregates tools from any number of backends behind bearer-token auth, group-based access control, and a full call-by-call audit log. Agents stop caring how a tool is implemented; you stop pasting API keys into agent configs.
Backends implement a common ToolProvider interface. Today: MCP upstream servers over stdio. Planned: OpenAPI, GraphQL, HTTP tools, and sandboxed code & CLI execution via OpenShell — install your CLI in the sandbox image and call it through sandbox_exec instead of writing a one-off MCP wrapper.
Why this exists
Connecting many MCP servers directly to an agent is a context-tax trap. Each upstream's tool list and schemas get pre-loaded into the LLM's context window before the user has typed a word — the official GitHub MCP alone is ~50K tokens, and model accuracy starts dropping past ~100K ("Lost in the Middle"). Conductor sits in front of N upstreams as a single MCP endpoint and gives agents lazy, governed discovery: see only what the caller's role permits, list providers and tools on demand instead of all-at-once, audit and rate-limit every call, and (planned) pre-filter tools by intent so the model never sees the other 95%. The CLI-vs-MCP debate misses the point — use a local CLI when you want context-cheap and personal; use conductor when you need governance, audit, RBAC, and one endpoint across teams.
┌──────────────┐ MCP/HTTP ┌───────────────────────────┐ stdio/… ┌─────────────┐
│ MCP client │ ──────────▶ │ conductor │ ──────────▶ │ upstream │
│ (agent/IDE) │ │ auth · groups · audit │ │ MCP server │
└──────────────┘ │ tool namespacing (__) │ └─────────────┘
└───────────────────────────┘ also: OpenAPI · GraphQL · …
Highlights
- One endpoint, many tools. Upstreams are abstracted behind a
ToolProvidercontract; tools are exposed namespaced as<provider>__<tool>. - Lazy tool discovery. Built-in
conductor__list_providersandconductor__list_toolsmeta-tools let agents discover capabilities on demand instead of pre-loading every tool's schema into context. Provider-level descriptions and instructions are surfaced from each upstream'sserverInfo/initializepayload. - Real auth. SHA-256 hashed API keys, timing-safe comparison. Plaintext keys never appear in config.
- Group-based access control. Users belong to groups; groups grant providers (or
"*"for all). - Audit everything. Every tool call records user, provider, tool, redacted args, duration, status, and request id.
- Observability built in. Structured JSON logs; OpenTelemetry spans per call when
OTEL_EXPORTER_OTLP_ENDPOINTis set. - Graceful lifecycle. Clean LIFO shutdown: HTTP → sessions → providers → audit → telemetry.
- Zero-ceremony start. Ships a working config with a dummy key — clone, install, run, connect.
Quick start
[!NOTE] Requires Node.js ≥ 20.11 and pnpm ≥ 9.
pnpm install
pnpm build
CONDUCTOR_CONFIG=examples/conductor.json pnpm dev
The bundled examples/conductor.json boots with user alice (API key changeme, hash baked in) and one upstream provider (everything via npx @modelcontextprotocol/server-everything).
Connect any MCP client:
URL: http://127.0.0.1:18080/mcp
Header: Authorization: Bearer changeme
13 tools appear, namespaced as everything__*. Sanity check:
curl http://127.0.0.1:18080/health
# {"ok":true,"sessions":0,"providers":["everything"]}
Use your own API key
pnpm hash-key my-secret-key
# sha256:7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069
Copy examples/conductor.json, paste the hash into users[].apiKeyHash, point CONDUCTOR_CONFIG at your copy.
Configuration
A single JSON file, validated by Zod at load time. Schema lives at packages/server/src/conductor-config.ts. Unknown keys are rejected.
{
"server": { "host": "127.0.0.1", "port": 18080, "maxSessions": 100 },
"users": [
{ "name": "alice", "apiKeyHash": "sha256:…", "groups": ["admins"] }
],
"groups": [{ "name": "admins", "providers": ["*"] }],
"providers": [
{
"type": "mcp",
"name": "everything",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"],
"env": {}
}
],
"audit": { "type": "console" },
"telemetry": { "serviceName": "conductor", "otlpEndpoint": "" }
}
Top-level fields
| Field | Purpose |
|---|---|
server |
host, port, maxSessions — bind address and session LRU cap. |
users[] |
name, apiKeyHash (sha256:<64 hex>), groups[]. |
groups[] |
name, providers[] (provider names, or ["*"] for all). |
providers[] |
Discriminated on type. Today: "mcp" (stdio). More types incoming. |
audit |
Currently { "type": "console" } with optional bufferSize. |
telemetry |
serviceName + otlpEndpoint. Traces are a no-op if the endpoint is unset. |
MCP provider options
| Field | Default | Notes |
|---|---|---|
command, args, env |
— | How to spawn the upstream. |
initialListTimeoutMs |
15000 |
Deadline for the first tools/list after connect. |
callTimeoutMs |
60000 |
Per-call timeout forwarded as an AbortSignal. |
reconnect.maxAttempts |
10 |
Exponential backoff between initialDelayMs and maxDelayMs. |
reconnect.initialDelayMs |
1000 |
|
reconnect.maxDelayMs |
30000 |
Environment variables
| Var | Meaning | Default |
|---|---|---|
CONDUCTOR_CONFIG |
Path to conductor.json. |
./conductor.json |
PORT |
Overrides server.port after config load. |
— |
LOG_LEVEL |
debug, info, warn, error. |
info |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP HTTP base URL. Traces disabled if unset. | — |
[!IMPORTANT] Never commit plaintext API keys. Run
pnpm hash-key <plaintext>and store only the resultingsha256:…digest.
Architecture
conductor is a pnpm workspace with strict dependency layering — core has no sibling deps, providers and the gateway depend only on core, and server wires it all together.
| Package | Role |
|---|---|
@mcp-conductor/core |
ToolProvider interface, ProviderRegistry, audit & config stores, logger, OTel bootstrap, shutdown registry. No sibling deps. |
@mcp-conductor/provider-mcp |
ToolProvider that spawns an upstream MCP server over stdio. Handles timeouts and reconnect. |
@mcp-conductor/provider-openshell |
ToolProvider for NVIDIA OpenShell (gRPC). Protos vendored; regenerate with pnpm update-openshell-protos. Currently a stub. |
@mcp-conductor/gateway |
The HTTP MCP server. Auth, groups, audit wrapping, namespacing, session manager. Accepts any ToolProvider[]. |
@mcp-conductor/server |
The CLI (conductor). Loads conductor.json, wires providers into the gateway, owns signals. |
Request lifecycle
- Authenticate. Bearer token → timing-safe SHA-256 compare against each user's
apiKeyHash. Bad token →401. - Resolve access. User's groups → set of reachable providers (
"*"expands to all configured). - Session. New session = MCP
initializerequest. The gateway assembles a per-session MCP server that only advertises tools the caller may see. - Namespace. Tools are registered as
<provider>__<tool>. The separator__is reserved; provider names must not contain it. - Call. The handler decodes
<provider>__<tool>, forwards to the audit-wrapped provider with{ user, requestId, signal }. Providers must honoursignalfor cancellation. - Audit + trace. One
AuditStore.insertCallper invocation with redacted args, status, duration, request id. One OTel span per call. - Shutdown. LIFO: HTTP server → sessions → providers → audit → telemetry.
Repository layout
packages/
core/ foundation: ToolProvider, stores, logger, OTel, lifecycle
provider-mcp/ stdio MCP upstream adapter
provider-openshell/ OpenShell gRPC provider (stub; protos vendored)
gateway/ HTTP MCP server, auth, groups, audit, namespacing
server/ CLI binary: config + wiring
examples/
conductor.json working example config (alice / changeme)
scripts/
hash-api-key.ts prints sha256:<hex> for an API key
docs/
plans/ roadmap and implementation plans
Writing a provider
Implement ToolProvider from @mcp-conductor/core:
import type {
ToolProvider,
ToolSpec,
ToolCallContext,
ToolCallResult,
} from "@mcp-conductor/core";
export class MyProvider implements ToolProvider {
readonly name = "my-provider";
async connect(): Promise<void> { /* … */ }
async close(): Promise<void> { /* … */ }
async listTools(): Promise<ToolSpec[]> { /* … */ }
async callTool(
name: string,
args: unknown,
ctx: ToolCallContext,
): Promise<ToolCallResult> {
// honour ctx.signal for cancellation
}
}
Register it with the ProviderRegistry — or, for config-driven loading, add a branch to packages/server/src/provider-factory.ts and extend ProviderEntrySchema.
Development
pnpm build # tsc -p tsconfig.build.json per package
pnpm typecheck # tsc --noEmit (includes tests)
pnpm test # vitest — 255 tests across all packages
pnpm test:watch
pnpm clean # rm -rf dist/ .tsbuildinfo
Run a single test file or test name:
pnpm test -- packages/gateway/tests/auth.test.ts
pnpm test -- packages/gateway/tests/auth.test.ts -t "rejects invalid bearer"
[!IMPORTANT] This is ESM-only (
"type": "module", NodeNext resolution). Local imports must use the.jsextension — TypeScript compiles to ESM.jsand NodeNext will not resolve extensionless imports.
Protocol notes
- Transport. Streamable HTTP (
@modelcontextprotocol/sdk). Oneinitializerequest opens a session; subsequent requests must carrymcp-session-id. Non-initialize traffic without a session gets400. - Tool schema. The gateway passes upstream JSON Schema through as
z.unknown()per property. Upstream remains the source of truth for validation — the gateway never rewrites tool inputs. - Correlation. Every request gets an
X-Request-Id(echoed on the response and threaded throughToolCallContextand the audit log).
[!TIP] See
docs/plans/for the roadmap — OpenAPI, GraphQL, HTTP tools, and OpenShell sandboxed execution are next.
推荐服务器
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 模型以安全和受控的方式获取实时的网络信息。