mcp-ts-core logo

mcp-ts-core

Community
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.

Publishercyanheads
Repositorymcp-ts-core
LanguageTypeScript
Forks
24
Stars
138
Available tools
1
Transport typestdio
Categories
LicenseApache-2.0
Links
  • Connect tools to AI workflows

    mcp-ts-core exposes MCP capabilities that can be used by compatible AI clients and agents.

  • 1 available tools

    Browse the callable actions below, including names and descriptions when provided by the server.

  • Ready-to-copy setup

    Use the installation snippets to configure this server in your preferred MCP client.

  • Open source signals

    138 stars and 24 forks from the linked repository.

Version MCP Spec MCP SDK License

TypeScript Bun


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';

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'),
  }),
  errors: [
    {
      reason: 'name_blocked',
      code: JsonRpcErrorCode.Forbidden,
      when: 'The provided name is on the configured block list.',
      recovery: 'Use a different name.',
    },
  ],
  handler: async (input, ctx) => {
    if (isBlocked(input.name)) throw ctx.fail('name_blocked', `"${input.name}" is blocked`);
    return { message: `Hello, ${input.name}!` };
  },
});

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

That's a complete MCP server. 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, Agent Skills, 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

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);
  },
});

And contracts for failure modes — typed at compile time, surfaced to clients with recovery hints the model can act on:

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

export const search = tool('search', {
  // ...input, output as above
  errors: [
    {
      reason: 'no_match',
      code: JsonRpcErrorCode.NotFound,
      when: 'The query returned zero items from the upstream index.',
      recovery: 'Broaden the query — close matches by edit distance are in `data.suggestions`.',
    },
  ],
  async handler(input, ctx) {
    const { items, suggestions } = await doSearch(input.query, input.limit);
    if (items.length === 0) throw ctx.fail('no_match', `No matches for "${input.query}"`, { suggestions });
    return { items };
  },
});

The linter cross-checks errors[] against the handler body, contracts publish in tools/list so clients can preview failure modes, and data.recovery.hint mirrors into the markdown content[] so tool-only clients see it too.

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.

Features

  • Declarative definitionstool(), resource(), prompt() builders with Zod schemas; appTool()/appResource() add interactive HTML UIs.
  • Unified Context — one ctx for logging, tenant-scoped storage, elicitation, sampling, 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 startup. Standalone via lint:mcp or devcheck.
  • 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 with the codebase documented throughout Agent Skills.

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
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 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)
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.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 (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_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 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, { 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.

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.
  • 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 - Directory based for easier parsing by agents. 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

Contributing

Issues and pull requests welcome. Run checks before submitting:

bash
bun run devcheck
bun run test:all

License

Apache 2.0 — see LICENSE.


Installation

TypingMind
Prerequisites:

Node.js 18+

{
  "mcpServers": {
    "mcp-ts-core": {
      "command": "npx",
      "args": [
        "-y",
        "@cyanheads/mcp-ts-core"
      ],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_AUTH_MODE": "none",
        "STORAGE_PROVIDER_TYPE": "in-memory",
        "OTEL_ENABLED": "false"
      }
    }
  }
}

Available Tools

  • greet

    Greet someone by name and return a personalized message

Use mcp-ts-core MCP with multiple AI models

TypingMind connects MCP tools at the workspace level, so once mcp-ts-core is connected, you can use it with different AI models in TypingMind instead of setting it up separately for each model. This MCP runs locally through the TypingMind MCP connector on your device.

Setup guide to use the local connector

Use this when the MCP server needs access to local files, apps, or private resources on your computer.

1

Open the MCP settings

In TypingMind, go to Settings, Advanced Settings, then Model Context Protocol and choose Setup Connector.

  1. Open TypingMind in your browser.
  2. Click the Settings icon.
  3. Go to Advanced Settings.
  4. Open the Model Context Protocol section.
  5. Click Setup Connector and choose This Device.
TypingMind MCP connector setup screen with This Device selected
2

Run the connector command

Choose This Device, copy the command from TypingMind, and run it in Terminal. Keep the process running while you use MCP.

  1. Copy the setup command shown by TypingMind.
  2. Open Terminal on macOS or Windows Terminal on Windows.
  3. Paste and run the command.
  4. Approve the package install if Terminal asks you to proceed.
  5. Keep the Terminal window running while using MCP tools.
3

Add mcp-ts-core as a server

When the connector status is Ready, click Edit Servers and paste the MCP server configuration.

  1. Wait until the connector status shows Ready.
  2. Click Edit Servers.
  3. Paste the mcp-ts-core MCP server configuration.
  4. Save the server list.
  5. Refresh if you want to confirm the connector is still ready.
TypingMind MCP settings showing active server and Edit Servers button
{
  "mcpServers": {
    "mcp-ts-core": {
      "command": "npx",
      "args": [
        "-y",
        "@cyanheads/mcp-ts-core"
      ]
    }
  }
}
4

Use it across models

Save the server list, open Plugins, enable the mcp-ts-core MCP tools, then select any supported AI model in TypingMind and use the tools in chat or assign them to an AI agent.

  1. Open the Plugins page in TypingMind.
  2. Enable the mcp-ts-core MCP tools.
  3. Start a chat and choose the AI model you want to use.
  4. Use the MCP tools in chat or assign them to an AI agent.
  5. Switch to another AI model whenever needed without reconnecting MCP.
TypingMind chat using enabled MCP tools with a selected AI model
Can you use mcp-ts-core to help me with this task?
mcp-ts-core
Sure. I read it.
Here is what I found using mcp-ts-core.

Frequently asked questions

What is the mcp-ts-core MCP server used for?

mcp-ts-core is an MCP server that lets compatible AI clients connect to external tools and context. In TypingMind, you can add this MCP server once and make its tools available in your AI workspace.

Can I use mcp-ts-core MCP with multiple AI models in TypingMind?

Yes. TypingMind connects MCP tools at the workspace level, so you can use mcp-ts-core with different AI models such as Claude, ChatGPT, Gemini, or other models you have configured in TypingMind without setting up the MCP server separately for each model.

Why use mcp-ts-core MCP with TypingMind?

TypingMind is one of the best frontends for LLM chat because it brings multiple AI models, prompts, plugins, AI agents, API keys, and MCP tools into one workspace. With mcp-ts-core connected, you can use its MCP tools across your preferred models while keeping your chat workflow organized in TypingMind.

How do I connect mcp-ts-core MCP to TypingMind?

mcp-ts-core runs through the TypingMind local MCP connector. This is best when the MCP server needs access to local files, desktop apps, command-line tools, or private resources on your computer.

What tools does mcp-ts-core MCP provide in TypingMind?

mcp-ts-core exposes 1 MCP tools that can be enabled from the TypingMind Plugins page and used in chat or assigned to AI agents.

Do I need to share my API keys with TypingMind to use mcp-ts-core MCP?

No. TypingMind is local-first and lets you keep your model providers, API keys, prompts, and MCP configuration under your control. If mcp-ts-core requires authentication, add the required headers, OAuth settings, or local configuration for that MCP server when you create the connection.

Related MCP Servers

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇