by cyanheads
Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.
# Add to your Claude Code skills
git clone https://github.com/cyanheads/mcp-ts-coreLast scanned: 5/30/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-05-30T16:15:39.612Z",
"npmAuditRan": true,
"pipAuditRan": true
}mcp-ts-core is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by cyanheads. Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers. It has 145 GitHub stars.
Yes. mcp-ts-core passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.
Clone the repository with "git clone https://github.com/cyanheads/mcp-ts-core" and add it to your Claude Code skills directory (see the Installation section above).
mcp-ts-core is primarily written in TypeScript. It is open-source under cyanheads on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh mcp-ts-core against similar tools.
No comments yet. Be the first to share your thoughts!
@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.
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, 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.
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 (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:
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:
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,
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.
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, elicitation, cancellation, and task progress.auth: ['scope'] on definitions, checked before dispatch (no wrapper code). Modes: none, jwt, or oauth (local secret or JWKS).task: true for long-running ops; framework manages create/poll/progress/complete/cancel.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.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.