The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the MCP Ts Template listing page.
Agent-native TypeScript framework for building MCP servers. Build tools, not infrastructure. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.
@cyanheads/mcp-ts-core is the infrastructure layer for TypeScript MCP servers. Install it as a dependency — don't fork it. Your agent collaborates with you to design and build the tools, resources, and prompts for your server.
The framework handles the plumbing: transports, auth, config, logging, telemetry, & more.
That's a complete MCP server, and it shows both of the framework's core contracts.
enrichment carries the context an agent reasons with (the parsed query, the true total, an empty-result notice); the framework merges it into structuredContent and mirrors it into content[], so structuredContent-only clients (Claude Code) and content[]-only clients (Claude Desktop) both see it, no format() needed. The typed errors[] contract handles genuine failures (an empty result is a notice, not a throw), and the linter cross-checks both against the handler body. Both publish in tools/list, so clients preview a tool's success and failure shapes.
The rest is automatic: every tool call is logged with duration, payload sizes, and request correlation, and createApp() handles config parsing, logger init, transport startup, signal handlers, and graceful shutdown.
You get a scaffolded project with CLAUDE.md/AGENTS.md, Agent Skills, plugin metadata (Codex + Claude Code), and a src/ tree ready for your tools. Infrastructure (transports, auth, storage, telemetry, lifecycle, linting) lives in node_modules. What's left is domain: which APIs to wrap, which workflows to expose.
Start your coding agent (e.g. Claude Code, Codex) and describe what you want. The agent knows what to do from there. The included Agent Skills cover the full cycle: setup, design-mcp-server, scaffolding, testing, security-pass, release-and-publish, maintenance, & more.
The headline tool returns structured output. Clients that read structuredContent (Claude Code) get it directly. To also render markdown for clients that read content[] (Claude Desktop), add a format(). The format-parity linter checks it renders every output field, so the two surfaces never drift:
And resources:
Everything registers through createApp() in your entry point:
It also works on Cloudflare Workers with createWorkerHandler() — same definitions, different entry point.
tool(), resource(), prompt() builders with Zod schemas; appTool()/appResource() add interactive HTML UIs.instructions on createApp/createWorkerHandler rides every initialize for the model. Cross-tool composition hints, regional notes, scope guidance — without leaking text into every tool description.title, websiteUrl, description, icons (SEP-973) on createApp/createWorkerHandler flow to initialize serverInfo, the /.well-known/mcp.json server card, and the landing page.ctx for logging, tenant-scoped storage, multi-round-trip input collection, and cancellation. Context extends RequestContext, so a handler's ctx goes straight into any service or storage call.auth: ['scope'] on definitions, checked before dispatch (no wrapper code). Modes: none, jwt, or oauth (local secret or JWKS)._meta envelope, no session) and the initialize-negotiated 2025 era (sessionful, identity-bound). Handlers are written once; the SDK's legacy shim fulfils multi-round-trip input for 2025-era clients.returns ctx.requestInput(...) for a confirmation, a sampling call, or the client's roots, and is re-entered with the answers on ctx.inputs.lint:mcp or devcheck — not invoked at server startup.errors: [{ reason, code, when, recovery, retryable? }] and handlers get a typed ctx.fail(reason, …). Contracts publish in tools/list so clients preview failure modes; the linter cross-checks the handler. Factories (notFound(), httpErrorFromResponse(), …) cover ad-hoc throws; plain Error auto-classifies.in-memory, filesystem, Supabase, Cloudflare D1/KV/R2. Swap via env var; handlers don't change.canvas_id) for multi-agent collaboration; sliding TTL + per-tenant scoping. Opt-in via CANVAS_PROVIDER_TYPE=duckdb; fails closed on Workers.CLAUDE.md / AGENTS.md and Agent Skills that give your coding agent full framework knowledge — it can scaffold tools, write tests, run security audits, and ship releases without you writing the boilerplate.No src/utils/, no src/storage/, no src/types-global/, no src/mcp-server/transports/ — infrastructure lives in node_modules.
All core config is Zod-validated from environment variables. Server-specific config uses a separate Zod schema with lazy parsing.
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE | stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_HOST | HTTP server hostname | 127.0.0.1 |
MCP_AUTH_MODE | none, jwt, or oauth | none |
MCP_AUTH_SECRET_KEY | JWT signing secret (required for jwt mode) | — |
STORAGE_PROVIDER_TYPE | in-memory, filesystem, supabase, cloudflare-d1/kv/r2 | in-memory |
CANVAS_PROVIDER_TYPE | none or duckdb (Tier 3, optional peer dep @duckdb/node-api) | none |
OTEL_ENABLED | Enable OpenTelemetry | false |
OPENROUTER_API_KEY | OpenRouter LLM API key | — |
See CLAUDE.md/AGENTS.md for the full configuration reference.
| Function | Purpose |
|---|---|
createApp(options) | Node.js server — handles full lifecycle |
createWorkerHandler(options) | Cloudflare Workers — returns an ExportedHandler |
| Builder | Usage |
|---|---|
tool(name, options) | Define a tool with handler(input, ctx) |
resource(uriTemplate, options) | Define a resource with handler(params, ctx) |
prompt(name, options) | Define a prompt with generate(args) |
appTool(name, options) | Define an MCP Apps tool with auto-populated _meta.ui |
appResource(uriTemplate, options) | Define an MCP Apps HTML resource with the correct MIME type and _meta.ui mirroring for read content |
Handlers receive a unified Context object:
| Property | Type | Description |
|---|---|---|
ctx.log | ContextLogger | Request-scoped logger (auto-correlates requestId, traceId, tenantId); also mirrored to the client as notifications/message |
ctx.state | ContextState | Tenant-scoped key-value storage |
ctx.requestInput | (spec) => never | Suspend and ask the caller for more input; the handler is re-entered with the answers |
ctx.inputs | ContextInputs | Reader over a retried request's responses — .accepted(), .view(), .state(), .dropped |
ctx.fail | (reason, msg?, data?) => McpError | Typed error throw — reason checked against errors[] contract at compile time |
ctx.signal | AbortSignal | Cancellation signal |
ctx.notifyResourceUpdated | Function? | Notify subscribed clients a resource changed |
ctx.notifyResourceListChanged | Function? | Notify clients the resource list changed |
ctx.notifyPromptListChanged | Function? | Notify clients the prompt list changed |
ctx.notifyToolListChanged | Function? | Notify clients the tool list changed |
ctx.requestId | string | Unique request ID |
ctx.tenantId | string? | Tenant ID (JWT tid claim, or 'default' for stdio and HTTP+MCP_AUTH_MODE=none) |
See CLAUDE.md/AGENTS.md for the complete exports reference.
The examples/ directory contains a reference server consuming core through public exports, demonstrating all patterns:
| Tool | Pattern |
|---|---|
template_echo_message | Basic tool with format, auth |
template_cat_fact | External API call, error factories |
template_madlibs_elicitation | ctx.requestInput / ctx.inputs for multi-round-trip input |
template_image_test | Image content blocks |
template_data_explorer | MCP Apps with linked UI resource via appTool()/appResource() builders |
createMockContext() provides a recording log, a working state, and a signal. State runs on a real StorageService over an in-memory provider — the same key validation and TTL expiry a deployed server applies — scoped to tenant 'default' unless { tenantId } says otherwise. Pass { errors: myTool.errors } for a typed ctx.fail matching the definition's contract, and { inputResponses, requestState } to drive a multi-round-trip handler into its second round.
/testing also exports createMockSession() for session-bound contexts, createFetchMock() for upstream HTTP boundaries, and runToolContract() to drive a definition through schema, handler, formatting, and error-envelope checks. /testing/vitest adds the mcpTest fixtures (ctx, session, fetchMock, storage) and toolContractSuite().
Schema-aware fuzz testing via fast-check. Generates valid inputs from Zod schemas and adversarial payloads (prototype pollution, injection strings, type confusion) to verify handler invariants.
Also exports fuzzResource, fuzzPrompt, zodToArbitrary, and ADVERSARIAL_STRINGS for custom property-based tests.
init.Apache 2.0 — see LICENSE.