Skip to main content
Glama
niradler
by niradler

conductor

A tool gateway for AI agents — one MCP endpoint, many upstreams, real auth, real audit.

Node.js pnpm TypeScript MCP License

HighlightsQuick startConfigurationArchitectureWriting a providerDevelopment

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 · …

Related MCP server: mcp-hub

Highlights

  • One endpoint, many tools. Upstreams are abstracted behind a ToolProvider contract; tools are exposed namespaced as <provider>__<tool>.

  • Lazy tool discovery. Built-in conductor__list_providers and conductor__list_tools meta-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's serverInfo / initialize payload.

  • 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_ENDPOINT is 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

RequiresNode.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. Runpnpm hash-key <plaintext> and store only the resulting sha256:… 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

  1. Authenticate. Bearer token → timing-safe SHA-256 compare against each user's apiKeyHash. Bad token → 401.

  2. Resolve access. User's groups → set of reachable providers ("*" expands to all configured).

  3. Session. New session = MCP initialize request. The gateway assembles a per-session MCP server that only advertises tools the caller may see.

  4. Namespace. Tools are registered as <provider>__<tool>. The separator __ is reserved; provider names must not contain it.

  5. Call. The handler decodes <provider>__<tool>, forwards to the audit-wrapped provider with { user, requestId, signal }. Providers must honour signal for cancellation.

  6. Audit + trace. One AuditStore.insertCall per invocation with redacted args, status, duration, request id. One OTel span per call.

  7. 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 .js extension — TypeScript compiles to ESM .js and NodeNext will not resolve extensionless imports.

Protocol notes

  • Transport. Streamable HTTP (@modelcontextprotocol/sdk). One initialize request opens a session; subsequent requests must carry mcp-session-id. Non-initialize traffic without a session gets 400.

  • 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 through ToolCallContext and the audit log).

TIP

Seedocs/plans/ for the roadmap — OpenAPI, GraphQL, HTTP tools, and OpenShell sandboxed execution are next.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    A gateway that aggregates multiple MCP servers into a single endpoint, namespacing their tools and forwarding calls, so an agent connects to one MCP to access the entire stack.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    A gateway that connects MCP servers and REST APIs, allowing you to curate tools into groups and expose them as virtual MCP servers for agents.
    5
    1
    MIT