io.github.cyanheads/mcp-ts-template

编码与调试

by cyanheads

面向生产环境的 TypeScript 模板,用于构建可扩展的 MCP servers,并内置 observability 能力。

想把 MCP Server 真正做到可上线,这套 TypeScript 生产模板能帮你快速搭好可扩展架构,连可观测性都提前配齐。

什么是 io.github.cyanheads/mcp-ts-template

面向生产环境的 TypeScript 模板,用于构建可扩展的 MCP servers,并内置 observability 能力。

README

<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 Bun/Node/Cloudflare Workers.</b></p> </div> <div align="center">

Version License MCP Spec

MCP SDK TypeScript Bun

Framework

</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. 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. Define your domain logic with the builders and let the framework take care of the rest.

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

const search = tool('search', {
  description: 'Search the catalog and return ranked matches.',
  annotations: { readOnlyHint: true },
  input: z.object({
    query: z.string().describe('Search terms'),
    limit: z.number().default(10).describe('Max results'),
  }),
  output: z.object({
    items: z.array(z.string()).describe('Matching item names, best first'),
  }),
  enrichment: {
    effectiveQuery: z.string().describe('Query as the server parsed it'),
    totalCount: z.number().describe('Total matches before the limit'),
    notice: z.string().optional().describe('Guidance when nothing matched'),
  },
  errors: [
    {
      reason: 'index_unavailable',
      code: JsonRpcErrorCode.ServiceUnavailable,
      when: 'The upstream search index is unreachable.',
      retryable: true,
      recovery: 'Retry in a few seconds — the index may be briefly unavailable.',
    },
  ],
  handler: async (input, ctx) => {
    const res = await runSearch(input.query, input.limit);
    if (!res) throw ctx.fail('index_unavailable'); // genuine failure → typed error contract
    ctx.enrich({ effectiveQuery: res.parsed, totalCount: res.total });
    if (res.items.length === 0) {
      ctx.enrich({ notice: `No matches for "${input.query}". Try broader terms.` }); // empty result → notice, not a throw
    }
    return { items: res.items }; // enrichment never rides in the domain return
  },
});

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

That's a complete MCP server, showing both flagship contracts. enrichment carries the context an agent reasons with — the parsed query, the true total, an empty-result notice — which the framework merges into structuredContent and mirrors 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). The linter cross-checks both against the handler body, and both publish in tools/list so clients preview a tool's success and failure shapes. Every tool call is automatically logged with duration, payload sizes, and request correlation — no instrumentation code needed; createApp() handles config parsing, logger init, transport startup, signal handlers, and graceful shutdown.

Quick start

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

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 (i.e. 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.

What you get

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:

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

export const itemSearch = tool('item_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 };
  },
  format: (result) => [
    { type: 'text', text: result.items.map((name) => `- ${name}`).join('\n') },
  ],
});

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,
  instructions: 'Brief composition hints for the model.', // optional, sent on every `initialize`
});

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

Features

  • Declarative definitionstool(), resource(), prompt() builders with Zod schemas; appTool()/appResource() add interactive HTML UIs.
  • Server-level orientationinstructions on createApp/createWorkerHandler rides every initialize for the model. Cross-tool composition hints, regional notes, scope guidance — without leaking text into every tool description.
  • Server identity — optional title, websiteUrl, description, icons (SEP-973) on createApp/createWorkerHandler flow to initialize serverInfo, the /.well-known/mcp.json server card, and the landing page.
  • Unified Context — one ctx for logging, tenant-scoped storage, elicitation, cancellation, and task progress.
  • Authauth: ['scope'] on definitions, checked before dispatch (no wrapper code). Modes: none, jwt, or oauth (local secret or JWKS).
  • Task toolstask: true for long-running ops; framework manages create/poll/progress/complete/cancel.
  • Definition linter — validates names, schemas, auth scopes, annotations, format-parity, and cross-vendor JSON Schema portability at build time. Run via lint:mcp or devcheck — not invoked at server startup.
  • Typed error contracts — declare 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.
  • Multi-backend storagein-memory, filesystem, Supabase, Cloudflare D1/KV/R2. Swap via env var; handlers don't change.
  • DataCanvas (optional) — Tier 3 SQL/analytical workspace backed by DuckDB. Register tabular data from upstream APIs, run SQL across registered tables, export CSV/Parquet/JSON. Token-sharing model (opaque canvas_id) for multi-agent collaboration; sliding TTL + per-tenant scoping. Opt-in via CANVAS_PROVIDER_TYPE=duckdb; fails closed on Workers.
  • Observability — Pino logging + optional OpenTelemetry traces/metrics. Request correlation and tool metrics automatic.
  • Tiered dependencies — parsers, OTEL SDK, Supabase, OpenAI as optional peers. Install what you use.
  • Agent-first DX — ships 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.

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 / AGENTS.md                   # Point to core's CLAUDE.md / AGENTS.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
CANVAS_PROVIDER_TYPEnone or duckdb (Tier 3, optional peer dep @duckdb/node-api)none
OTEL_ENABLEDEnable OpenTelemetryfalse
OPENROUTER_API_KEYOpenRouter LLM API key

See CLAUDE.md/AGENTS.md for the full configuration reference.

API overview

Entry points

FunctionPurpose
createApp(options)Node.js server — handles full lifecycle
createWorkerHandler(options)Cloudflare Workers — returns an ExportedHandler

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

Context

Handlers receive a unified Context object:

PropertyTypeDescription
ctx.logContextLoggerRequest-scoped logger (auto-correlates requestId, traceId, tenantId)
ctx.stateContextStateTenant-scoped key-value storage
ctx.elicitElicitFn?Ask the user for input — form schema, or .url() for an external link (when client supports it)
ctx.fail(reason, msg?, data?) => McpErrorTyped error throw — reason checked against errors[] contract at compile time
ctx.signalAbortSignalCancellation signal
ctx.notifyResourceUpdatedFunction?Notify subscribed clients a resource changed
ctx.notifyResourceListChangedFunction?Notify clients the resource list changed
ctx.notifyPromptListChangedFunction?Notify clients the prompt list changed
ctx.notifyToolListChangedFunction?Notify clients the tool list changed
ctx.progressContextProgress?Task progress reporting (when task: true)
ctx.requestIdstringUnique request ID
ctx.tenantIdstring?Tenant ID (JWT tid claim, or 'default' for stdio and HTTP+MCP_AUTH_MODE=none)

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 type { DataCanvas, CanvasInstance } from '@cyanheads/mcp-ts-core/canvas';
import { validateDefinitions } from '@cyanheads/mcp-ts-core/linter';
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
import { fuzzTool, fuzzResource, fuzzPrompt } from '@cyanheads/mcp-ts-core/testing/fuzz';

See CLAUDE.md/AGENTS.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_image_testImage content blocks
template_async_countdowntask: true with ctx.progress
template_data_explorerMCP Apps with linked UI resource via appTool()/appResource() builders

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, { elicit } for elicitation mocking, { progress: true } for task tools.

Fuzz testing

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.

ts
import { fuzzTool } from '@cyanheads/mcp-ts-core/testing/fuzz';

const report = await fuzzTool(myTool, { numRuns: 100 });
expect(report.crashes).toHaveLength(0);
expect(report.leaks).toHaveLength(0);
expect(report.prototypePollution).toBe(false);

Also exports fuzzResource, fuzzPrompt, zodToArbitrary, and ADVERSARIAL_STRINGS for custom property-based tests.

Documentation

  • CLAUDE.md/AGENTS.md — Framework reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package and is auto-accessible in your project after init.
  • docs/telemetry/ — OpenTelemetry: full catalog of spans, metrics, and attributes the framework emits (observability.md), plus an example Grafana dashboard and vendor-agnostic query recipes for Datadog, New Relic, Honeycomb (dashboards.md).
  • CHANGELOG.md — Version history. Each entry includes a summary, migration notes, and links to commits/issues.

Development

bash
bun run rebuild        # clean + build (scripts/clean.ts + scripts/build.ts)
bun run devcheck       # full gate: lint/format, typecheck, MCP defs, framework antipatterns, docs/skills/changelog sync, tests, audit, outdated, secrets/TODO scan
bun run lint:mcp       # validate MCP definitions against spec
bun run test:all       # vitest: unit + Workers pool + integration

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>

常见问题

io.github.cyanheads/mcp-ts-template 是什么?

面向生产环境的 TypeScript 模板,用于构建可扩展的 MCP servers,并内置 observability 能力。

相关 Skills

前端设计

by anthropics

Universal
热门

面向组件、页面、海报和 Web 应用开发,按鲜明视觉方向生成可直接落地的前端代码与高质感 UI,适合做 landing page、Dashboard 或美化现有界面,避开千篇一律的 AI 审美。

想把页面做得既能上线又有设计感,就用前端设计:组件到整站都能产出,难得的是能避开千篇一律的 AI 味。

编码与调试
未扫描157.8k

网页应用测试

by anthropics

Universal
热门

用 Playwright 为本地 Web 应用编写自动化测试,支持启动开发服务器、校验前端交互、排查 UI 异常、抓取截图与浏览器日志,适合调试动态页面和回归验证。

借助 Playwright 一站式验证本地 Web 应用前端功能,调 UI 时还能同步查看日志和截图,定位问题更快。

编码与调试
未扫描157.8k

网页构建器

by anthropics

Universal
热门

面向复杂 claude.ai HTML artifact 开发,快速初始化 React + Tailwind CSS + shadcn/ui 项目并打包为单文件 HTML,适合需要状态管理、路由或多组件交互的页面。

在 claude.ai 里做复杂网页 Artifact 很省心,多组件、状态和路由都能顺手搭起来,React、Tailwind 与 shadcn/ui 组合效率高、成品也更精致。

编码与调试
未扫描157.8k

相关 MCP Server

GitHub

编辑精选

by GitHub

热门

GitHub 是 MCP 官方参考服务器,让 Claude 直接读写你的代码仓库和 Issues。

这个参考服务器解决了开发者想让 AI 安全访问 GitHub 数据的问题,适合需要自动化代码审查或 Issue 管理的团队。但注意它只是参考实现,生产环境得自己加固安全。

编码与调试
88.0k

by Context7

热门

Context7 是实时拉取最新文档和代码示例的智能助手,让你告别过时资料。

它能解决开发者查找文档时信息滞后的问题,特别适合快速上手新库或跟进更新。不过,依赖外部源可能导致偶尔的数据延迟,建议结合官方文档使用。

编码与调试
58.5k

by tldraw

热门

tldraw 是让 AI 助手直接在无限画布上绘图和协作的 MCP 服务器。

这解决了 AI 只能输出文本、无法视觉化协作的痛点——想象让 Claude 帮你画流程图或白板讨论。最适合需要快速原型设计或头脑风暴的开发者。不过,目前它只是个基础连接器,你得自己搭建画布应用才能发挥全部潜力。

编码与调试
48.5k

评论