Back to Directory/Developer Tools

io.github.cyanheads/mcp-ts-core

Agent-native TypeScript framework for building MCP servers with declarative definitions.

Developer ToolsTypeScriptv0.3.2
<div align="center"> <h1>@cyanheads/mcp-ts-core</h1> <p><b>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 Node.js and Cloudflare Workers.</b></p> </div> <div align="center">

Version MCP Spec MCP SDK License

TypeScript Bun

</div>

What is this?

@cyanheads/mcp-ts-core is the infrastructure layer for TypeScript MCP servers. Install it as a dependency — don't fork it. You write tools, resources, and prompts; the framework handles transports, auth, storage, config, logging, telemetry, and lifecycle.

ts
import { createApp, tool, z } from '@cyanheads/mcp-ts-core';

const greet = tool('greet', {
  description: 'Greet someone by name and return a personalized message.',
  annotations: { readOnlyHint: true },
  input: z.object({ name: z.string().describe('Name of the person to greet') }),
  output: z.object({ message: z.string().describe('The greeting message') }),
  handler: async (input) => ({ message: `Hello, ${input.name}!` }),
});

await createApp({ tools: [greet] });

That's a complete MCP server. Every tool call is automatically logged with duration, payload sizes, memory usage, and request correlation — no instrumentation code needed. createApp() handles config parsing, logger init, transport startup, signal handlers, and graceful shutdown.

Features

  • Declarative definitionstool(), resource(), prompt() builders with Zod schemas. Framework handles registration, validation, and response formatting.
  • Unified Context — handlers receive a single ctx object with ctx.log (request-scoped logging), ctx.state (tenant-scoped storage), ctx.elicit (user prompting), ctx.sample (LLM completion), and ctx.signal (cancellation).
  • Inline authauth: ['scope'] on definitions. No wrapper functions. Framework checks scopes before calling your handler.
  • Task toolstask: true flag for long-running operations. Framework manages the full lifecycle (create, poll, progress, complete/fail/cancel).
  • Definition lintervalidateDefinitions() checks tools, resources, and prompts against MCP spec at startup. Name format, schema structure, .describe() presence, JSON Schema serializability, auth scope validity, annotation coherence, and URI template–params alignment. Also available as a standalone CLI (lint:mcp) and devcheck step.
  • Structured error handling — Handlers throw freely; the framework catches, classifies, and formats. Error factories (notFound(), validationError(), serviceUnavailable(), etc.) for precise control when the code matters. Auto-classification from plain Error messages when it doesn't.
  • Multi-backend storagein-memory, filesystem, Supabase, Cloudflare D1/KV/R2. Swap providers via env var without changing tool logic. Cursor pagination, batch ops, TTL, tenant isolation.
  • Pluggable authnone, jwt, or oauth modes. JWT with local secret or OAuth with JWKS verification.
  • Observability — Pino structured logging with optional OpenTelemetry tracing and metrics. Request IDs, trace correlation, tool execution metrics — all automatic.
  • Local + edge — Same code runs on stdio, HTTP (Hono), and Cloudflare Workers. createApp() for Node, createWorkerHandler() for Workers.
  • Tiered dependencies — Core deps always installed. Parsers, sanitization, scheduling, OTEL SDK, Supabase, OpenAI — optional peers. Install what you use.
  • Agent-first DX — Ships CLAUDE.md with full exports catalog, patterns, and contracts. AI coding agents can build on the framework with zero ramp-up.

Quick start

bash
bunx @cyanheads/mcp-ts-core init my-mcp-server
cd my-mcp-server
bun install

That gives you a working project with CLAUDE.md, skills, config files, and a scaffolded src/ directory. Open it in your editor, start your coding agent, and tell it what tools to build. The agent learns the framework from the included docs and skills — tool definitions, resources, services, testing patterns, all of it.

What you get

Here's what tool definitions look like:

ts
import { tool, z } from '@cyanheads/mcp-ts-core';

export const search = tool('search', {
  description: 'Search for items by query.',
  input: z.object({
    query: z.string().describe('Search query'),
    limit: z.number().default(10).describe('Max results'),
  }),
  output: z.object({ items: z.array(z.string()).describe('Search results') }),
  async handler(input) {
    const results = await doSearch(input.query, input.limit);
    return { items: results };
  },
});

And resources:

ts
import { resource, z } from '@cyanheads/mcp-ts-core';

export const itemData = resource('items://{itemId}', {
  description: 'Retrieve item data by ID.',
  params: z.object({ itemId: z.string().describe('Item ID') }),
  async handler(params, ctx) {
    return await getItem(params.itemId);
  },
});

Everything registers through createApp() in your entry point:

ts
await createApp({
  name: 'my-mcp-server',
  version: '0.1.0',
  tools: allToolDefinitions,
  resources: allResourceDefinitions,
  prompts: allPromptDefinitions,
});

It also works on Cloudflare Workers with createWorkerHandler() — same definitions, different entry point.

Server structure

text
my-mcp-server/
  src/
    index.ts                              # createApp() entry point
    worker.ts                             # createWorkerHandler() (optional)
    config/
      server-config.ts                    # Server-specific env vars
    services/
      [domain]/                           # Domain services (init/accessor pattern)
    mcp-server/
      tools/definitions/                  # Tool definitions (.tool.ts)
      resources/definitions/              # Resource definitions (.resource.ts)
      prompts/definitions/                # Prompt definitions (.prompt.ts)
  package.json
  tsconfig.json                           # extends @cyanheads/mcp-ts-core/tsconfig.base.json
  CLAUDE.md                               # Points to core's CLAUDE.md for framework docs

No src/utils/, no src/storage/, no src/types-global/, no src/mcp-server/transports/ — infrastructure lives in node_modules.

Configuration

All core config is Zod-validated from environment variables. Server-specific config uses a separate Zod schema with lazy parsing.

VariableDescriptionDefault
MCP_TRANSPORT_TYPEstdio or httpstdio
MCP_HTTP_PORTHTTP server port3010
MCP_HTTP_HOSTHTTP server hostname127.0.0.1
MCP_AUTH_MODEnone, jwt, or oauthnone
MCP_AUTH_SECRET_KEYJWT signing secret (required for jwt mode)
STORAGE_PROVIDER_TYPEin-memory, filesystem, supabase, cloudflare-d1/kv/r2in-memory
OTEL_ENABLEDEnable OpenTelemetryfalse
OPENROUTER_API_KEYOpenRouter LLM API key

See CLAUDE.md for the full configuration reference.

API overview

Entry points

FunctionPurpose
createApp(options)Node.js server — handles full lifecycle
createWorkerHandler(options)Cloudflare Workers — returns { fetch, scheduled }

Builders

BuilderUsage
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)

Context

Handlers receive a unified Context object:

PropertyTypeDescription
ctx.logContextLoggerRequest-scoped logger (auto-correlates requestId, traceId, tenantId)
ctx.stateContextStateTenant-scoped key-value storage
ctx.elicitFunction?Ask the user for input (when client supports it)
ctx.sampleFunction?Request LLM completion from the client
ctx.signalAbortSignalCancellation signal
ctx.notifyResourceUpdatedFunction?Notify subscribed clients a resource changed
ctx.notifyResourceListChangedFunction?Notify clients the resource list changed
ctx.progressContextProgress?Task progress reporting (when task: true)
ctx.requestIdstringUnique request ID
ctx.tenantIdstring?Tenant ID (from JWT or 'default' for stdio)

Subpath exports

ts
import { createApp, tool, resource, prompt } from '@cyanheads/mcp-ts-core';
import { createWorkerHandler } from '@cyanheads/mcp-ts-core/worker';
import { McpError, JsonRpcErrorCode, notFound, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
import { checkScopes } from '@cyanheads/mcp-ts-core/auth';
import { markdown, fetchWithTimeout } from '@cyanheads/mcp-ts-core/utils';
import { OpenRouterProvider, GraphService } from '@cyanheads/mcp-ts-core/services';
import { validateDefinitions } from '@cyanheads/mcp-ts-core/linter';
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';

See CLAUDE.md for the complete exports reference.

Examples

The examples/ directory contains a reference server consuming core through public exports, demonstrating all patterns:

ToolPattern
template_echo_messageBasic tool with format, auth
template_cat_factExternal API call, error factories
template_madlibs_elicitationctx.elicit for interactive input
template_code_review_samplingctx.sample for LLM completion
template_image_testImage content blocks
template_async_countdowntask: true with ctx.progress
template_data_explorerMCP Apps with linked UI resource

Testing

ts
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
import { myTool } from '@/mcp-server/tools/definitions/my-tool.tool.js';

const ctx = createMockContext({ tenantId: 'test-tenant' });
const input = myTool.input.parse({ query: 'test' });
const result = await myTool.handler(input, ctx);

createMockContext() provides stubbed log, state, and signal. Pass { tenantId } for state operations, { sample } for LLM mocking, { elicit } for elicitation mocking, { progress: true } for task tools.

Documentation

  • CLAUDE.md — Complete API reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package.
  • CHANGELOG.md — Version history

Development

bash
bun run build          # tsc && tsc-alias
bun run devcheck       # lint, format, typecheck, security
bun run test           # vitest
bun run dev:stdio      # dev mode (stdio)
bun run dev:http       # dev mode (HTTP)

Contributing

Issues and pull requests welcome. Run checks before submitting:

bash
bun run devcheck
bun run test

License

Apache 2.0 — see LICENSE.


<div align="center"> <p> <a href="https://github.com/sponsors/cyanheads">Sponsor this project</a> • <a href="https://www.buymeacoffee.com/cyanheads">Buy me a coffee</a> </p> </div>

Learn More