@openrouter/mcp
v1.1.1
Published
Expose remote MCP server tools (Streamable HTTP / SSE) as tools for @openrouter/agent's callModel, with serializable caching and pluggable auth.
Downloads
1,314
Readme
@openrouter/mcp
[!NOTE] This package is a migration-only compatibility facade. New code should import the canonical
@openrouter/agent/mcpsubpath. Existing@openrouter/mcproot and subpath imports remain functional and would only be removed in a future breaking release after migration notice.
Expose the tools of a remote Model Context Protocol server
(Streamable HTTP or SSE) as tools you can pass straight into
@openrouter/agent's callModel.
- Connect to a non-stdio MCP server, authenticate once, and reuse that auth for tool discovery and every tool call.
- Faithful JSON Schema → Zod conversion so the model sees real parameters.
- Serializable, rehydratable cache so you can skip re-listing (and, opt-in, re-authenticating).
- Progress streaming,
tools/list_changedauto-refresh, cancellation, resources, and elicitation.
stdio servers are intentionally out of scope.
Install
For new code, install the agent plus the optional MCP peer:
pnpm add @openrouter/agent @modelcontextprotocol/clientExisting applications can keep installing only the compatibility package; it retains @modelcontextprotocol/client as a dependency, so the prior transitive-install behavior is unchanged:
pnpm add @openrouter/mcpThe agent package is marked sideEffects: false, and MCP code is exposed only
through explicit /mcp exports. Root and /tool-set imports do not statically
load MCP modules; the MCP SDK is not installed transitively for base agent users.
The /mcp entry point also loads the optional SDK lazily: importing it is safe
without the peer, while the first connection attempt throws an actionable
MCPMissingPeerDependencyError when the SDK has not been installed.
Compatibility subpaths
| Existing facade | Canonical replacement |
| --- | --- |
| @openrouter/mcp | @openrouter/agent/mcp |
| @openrouter/mcp/create-mcp-tools | @openrouter/agent/mcp/create-mcp-tools |
| @openrouter/mcp/types | @openrouter/agent/mcp/types |
| @openrouter/mcp/schema | @openrouter/agent/mcp/schema |
| @openrouter/mcp/cache | @openrouter/agent/mcp/cache |
Quick start
import { OpenRouter } from '@openrouter/agent';
import { callModel } from '@openrouter/agent/call-model';
import { createMCPTools } from '@openrouter/agent/mcp';
const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
const mcp = await createMCPTools({
url: 'https://mcp.example.com/mcp',
auth: { kind: 'bearer', token: process.env.MCP_TOKEN },
});
const result = callModel(client, {
model: 'anthropic/claude-opus-4-8',
input: 'What are my three most recently updated issues?',
tools: mcp.tools,
});
console.log(await result.getText());
await mcp.close();Authentication
Auth is supplied once and reused for discovery and every call:
// Static bearer token
auth: { kind: 'bearer', token }
// Arbitrary headers
auth: { kind: 'headers', headers: { 'X-API-Key': key } }
// Pluggable OAuth (you own token refresh/storage)
auth: { kind: 'oauth', provider }Prefer an OAuthClientProvider over caching static tokens — the transport refreshes through it
automatically.
Caching & rehydration
Persist a snapshot and rebuild later without a listTools() round-trip:
import { createMCPTools, rehydrateMCPTools } from '@openrouter/agent/mcp';
const mcp = await createMCPTools({ url, auth, cacheCredentials: true });
const snapshot = await mcp.serialize(); // plain JSON — store anywhere
await mcp.close();
const mcp2 = await rehydrateMCPTools({ snapshot, auth });Or let a store manage it (rehydrate on hit, connect + write on miss):
import { InMemoryMCPCacheStore } from '@openrouter/agent/mcp';
const store = new InMemoryMCPCacheStore(); // or your own Redis/DB-backed MCPCacheStore
const mcp = await createMCPTools({
url,
auth,
cache: { store, key: `mcp:${userId}` },
staleness: { maxAgeMs: 60 * 60 * 1000 },
});Security:
cacheCredentialsisfalseby default. When enabled, snapshots contain bearer tokens/headers — treat the store as a secret store and namespace cache keys by principal in multi-tenant setups.
Multiple servers
const [github, linear] = await Promise.all([
createMCPTools({ url: githubUrl, auth: gh, toolNamePrefix: 'github_' }),
createMCPTools({ url: linearUrl, auth: ln, toolNamePrefix: 'linear_' }),
]);
const result = callModel(client, {
model,
input: 'Find the Linear issue linked to GitHub PR #42.',
tools: [...github.tools, ...linear.tools],
});Options
| Option | Description |
| --- | --- |
| url | Remote MCP server endpoint. |
| transport | 'streamableHttp' (default, falls back to SSE) or 'sse'. |
| auth | Bearer token, headers, or an OAuthClientProvider. |
| toolNamePrefix | Prefix every wrapped tool name. |
| includeTools / excludeTools | Allow/deny lists by MCP tool name. |
| onUnconvertibleSchema | 'looseLeaf' (default) or 'throw' for exotic JSON Schema. |
| cache / cacheCredentials / staleness | Caching controls. |
| resources | Expose synthetic list_resources / read_resource tools (default on). |
| emitProgress | Stream MCP progress as generator-tool events (default on). |
| autoRefreshOnListChanged | Re-list on tools/list_changed (default on). |
| onElicitation | Handle server elicitation requests; auto-declines when omitted. |
| signal | Abort signal threaded into every tool call. |
License
Apache-2.0
