freshcrate
Skin:/
Home > MCP Servers > mcp-ts-core

mcp-ts-core

Agent-native TypeScript framework for building MCP servers. Build tools, not infrastructure.

Why this rank:Strong adoptionRecent releaseHealthy release cadence

Description

Agent-native TypeScript framework for building MCP servers. Build tools, not infrastructure.

README

@cyanheads/mcp-ts-core

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. You write tools, resources, and prompts; the framework handles transports, auth, storage, config, logging, telemetry, and lifecycle.

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. appTool() and appResource() for MCP Apps with interactive HTML UIs. 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, URI template–params alignment, and format-parity (every field in a tool's output must be rendered by format() — verified via sentinel injection, since different MCP clients forward different surfaces to the model and both structuredContent and content[] must carry the same data). 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

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:

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:

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:

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

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.

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
OTEL_ENABLED Enable OpenTelemetry false
OPENROUTER_API_KEY OpenRouter LLM API key

See CLAUDE.md for the full configuration reference.

API overview

Entry points

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

Builders

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

Context

Handlers receive a unified Context object:

Property Type Description
ctx.log ContextLogger Request-scoped logger (auto-correlates requestId, traceId, tenantId)
ctx.state ContextState Tenant-scoped key-value storage
ctx.elicit Function? Ask the user for input (when client supports it)
ctx.sample Function? Request LLM completion from the client
ctx.signal AbortSignal Cancellation signal
ctx.notifyResourceUpdated Function? Notify subscribed clients a resource changed
ctx.notifyResourceListChanged Function? Notify clients the resource list changed
ctx.progress ContextProgress? Task progress reporting (when task: true)
ctx.requestId string Unique request ID
ctx.tenantId string? Tenant ID (from JWT or 'default' for stdio)

Subpath exports

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';
import { fuzzTool, fuzzResource, fuzzPrompt } from '@cyanheads/mcp-ts-core/testing/fuzz';

See CLAUDE.md for the complete exports reference.

Examples

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.elicit for interactive input
template_code_review_sampling ctx.sample for LLM completion
template_image_test Image content blocks
template_async_countdown task: true with ctx.progress
template_data_explorer MCP Apps with linked UI resource via appTool()/appResource() builders

Testing

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.

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.

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 — Framework reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package.
  • CHANGELOG.md — Version history

Development

bun run rebuild        # clean + build (scripts/clean.ts + scripts/build.ts)
bun run devcheck       # lint, format, typecheck, MCP defs, audit, outdated
bun run lint:mcp       # validate MCP definitions against spec
bun run test:all       # vitest (unit + integration)

Contributing

Issues and pull requests welcome. Run checks before submitting:

bun run devcheck
bun run test:all

License

Apache 2.0 — see LICENSE.


Release History

VersionChangesUrgencyDate
v0.12.5server identity, setup() logging, code-fence sizing - Server identity anchors on the served package, not the caller's working directory (#374, #373) - Records logged from a server's setup() hook are no longer dropped (#381) - MarkdownBuilder.codeBlock sizes its fence past the longest backtick run in the content (#375) - init ships bunfig.toml again — Bun's packer had silently dropped it from the tarball (#383) - setup skill's post-init checklist gates on populating publishing identity; skill buHigh9/2/2026
v0.12.3measured region, shutdown paths, and bind failures - Telemetry records a post-handler failure — output-schema validation, `format()`, the enrichment merge, the trailer render — as a failed call, not a successful one (#346) - Stdin EOF runs the shutdown a signal runs, then exits explicitly, so the OTel export leaves the process (#322) - `Logger.close()` bounds each pino flush, so a callback that never arrives no longer hangs shutdown (#342) - An async HTTP bind failure reaches the retry ladder iMedium8/21/2026
v0.11.5server requests across stateful HTTP sessions - `ctx.elicit` completes over stateful Streamable HTTP: server-initiated requests carry a session-unique wire ID, so a response arriving on a later POST reaches the `Server` awaiting it - A terminated or stale HTTP session settles its pending server requests with a `ConnectionClosed` error - `.github/` community-health files (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`) now scaffold with `init` - `bun run test:package` typechecks, builds,High8/13/2026
v0.11.1test kit, fuzz generator fixes - `createMockSession`, `createFetchMock`, and `runToolContract` in `/testing`; `toolContractSuite` plus `session` and `fetchMock` fixtures in `/testing/vitest` - `zodToArbitrary` expands finite nesting in full instead of replacing nodes past depth six with `null` (#319) - `zodToArbitrary` honors `.min()`, `.max()`, `.length()`, and `.nonempty()` on Zod 4 arrays (#320) - `fuzzTool` and `fuzzResource` treat a thrown `McpError` as a handled outcome, still leak-checkeHigh8/2/2026
v0.10.15Retry-After honoring, canonical HTTP error fields, expectedStatuses opt-out - withRetry honors Retry-After (delta-seconds + HTTP-date, RFC 9110 §10.2.3), capped at maxDelayMs (#285) - fetchWithTimeout/httpErrorFromResponse: canonical status/body error fields (legacy aliases kept) + expectedStatuses log opt-out (#256, #279) - fetchWithTimeout no longer double-logs a status-mapped error; 0.10.13 changelog correction (#281); git-wrapup/orchestrations/field-test/tool-defs-analysis skill updates - dHigh7/19/2026
v0.10.14Docker build fix, linter null-entry diagnostic, canvas + doc fixes Five bug fixes; no runtime change for existing servers beyond enriched canvas error data. Fixed: - **Docker build stage** installs with `--ignore-scripts`, matching production, so native dependency postinstalls (better-sqlite3) no longer fail the build; `templates/Dockerfile` too ([#267](https://github.com/cyanheads/mcp-ts-core/issues/267)) - **Definition linter** surfaces a `definition-invalid` diagnostic instead of crashing Medium7/6/2026
v0.10.10js-yaml v5 migration, biome 2.5.1, dependency refresh Breaking: the js-yaml peer dependency moves ^4 → ^5. Servers that use the framework YAML parser must install js-yaml@^5. Changed: - `YamlParser.parse` passes `YAML11_SCHEMA` — preserves YAML 1.1 load semantics (yes/no → boolean, !!timestamp → Date) after v5 made `CORE_SCHEMA` the load() default - `fetch-openapi-spec.ts` switches to a namespace import; v5 dropped the default export - `biome.json` `$schema` 2.5.0 → 2.5.1 Removed: - `@typesHigh6/30/2026
v0.10.9devcheck dep-specifier and plugin-manifest guards Added: - check-dependency-specifiers devcheck step (--no-dep-specifiers): hard-fails on floating specifiers (latest/*/dist-tags) in package.json's four dependency sections and bun.lock's workspaces map, never the packages section. latest fails in every section; */next/beta/canary/rc fail in dependencies/devDependencies but are allowed in peer/optional. Catches bun update --latest writing a latest dist-tag into the lock's workspace map past the High6/20/2026
v0.10.6## Added - `scripts/clean-mcpb.ts` — post-pack bundle cleaner: `mcpb clean` (dev-dep prune + manifest validation), then literal-name strip of dependency-shipped agent docs (`node_modules/**` `skills/`, `.claude/`, `.agents/`, `SKILL.md`) that root-anchored `.mcpbignore` patterns cannot reach; listed in `package.json` `files[]` so init scaffolds it ([#230](https://github.com/cyanheads/mcp-ts-core/issues/230)) - `lint-packaging.ts` check 8 — a built `.mcpb` under `dist/` must contain zero agent-dHigh6/11/2026
v0.10.0outline-on-overflow, stringbool env-boolean parsing, Docker image.version - `outlineOnOverflow()`, `OUTLINE_VARIANT`, `selectSections()`, `formatOutline()`, `DEFAULT_OUTLINE_BUDGET_BYTES` exported from `@cyanheads/mcp-ts-core/utils`. When a document payload exceeds a byte budget, returns a section outline + re-call notice instead of truncating. Workers-portable. (#204) - `techniques` skill: catalog of reusable response/data-shaping patterns; outline-on-overflow reference is the first entry. (#2High6/5/2026
v0.9.16restore AnyToolDefinition assignability for enrichment tools enrichmentTrailer.render is now declared with method syntax (bivariant params), matching format and handler — fixing a 0.9.15 regression that broke createApp({ tools }) typechecking for every tool declaring an enrichment block. Fixed: - enrichmentTrailer.render arrow property → method syntax; concrete enrichment tools stay assignable to AnyToolDefinition under strict + exactOptionalPropertyTypes. Type-only, no runtime change. (#180)High5/30/2026
main@2026-05-23Latest activity on main branchHigh5/23/2026
v0.3.3Latest release: v0.3.3High4/8/2026

Dependencies & License Audit

Loading dependencies...

Similar Packages

mcp-searxngMCP Server for SearXNGv2.2.0
studioOpen-source control plane for your AI agents. Connect tools, hire agents, track every token and dollarnative-v4.345.2
keryxKeryx: The Fullstack TypeScript Framework for MCP and APIsv0.44.0
frontmcpTypeScript-first framework for the Model Context Protocol (MCP). You write clean, typed code; FrontMCP handles the protocol, transport, DI, session/auth, and execution flow.v1.7.0
ntfy-me-mcpAn ntfy MCP server for sending/fetching ntfy notifications to self-hosted or ANY ntfy.sh server from AI Agents 📤 (supports secure token auth & more - use with npx or docker!)v1.4.2

More from cyanheads

clinicaltrialsgov-mcp-serverMCP server for the ClinicalTrials.gov v2 API. Search trials, retrieve study details and results, and match patients to eligible trials.

More in MCP Servers

difyProduction-ready platform for agentic workflow development.
tabularisA lightweight, cross-platform database client for developers. Supports MySQL, PostgreSQL and SQLite. Hackable with plugins. Built for speed, security, and aesthetics.
ai-agents-from-zero 🚀 2026 最系统的 AI Agent 速成指南|智能体实战教程 · 完整学习路径 + 实战项目 + 面试题库 · 对标大模型应用开发工程师岗位 · 覆盖LangChain / LangGraph / Coze / Dify / MCP / skills / LLM / RAG / 提示词 · 企业级部署与微调 · 从0到企业级落地 + 从学习到上线项目 + 面试准备一体化
studioOpen-source control plane for your AI agents. Connect tools, hire agents, track every token and dollar