mctxdocs
Build Your MCP Server

Framework API Reference

Complete reference for @mctx-ai/mcp — all exports, types, and patterns.

Need help? Connect help.mctx.ai for instant answers.
npm install @mctx-ai/mcp

createServer(options?)

Creates an MCP server instance.

import { createServer } from "@mctx-ai/mcp";
const app = createServer();

Returns an object with .tool(), .resource(), .prompt(), and .fetch() methods.

Options

OptionTypeDescription
instructionsstringRuntime instructions sent to AI clients at handshake.

instructions

A string that tells AI clients what your server does and how to use it effectively. This is part of the MCP specification — it travels to the AI during the initial handshake, before any tool calls happen.

Purpose: The MCP instructions field helps AI clients decide when to use your server and how to use it well. Your tools do the actual work; instructions help the AI understand the context around them.

Best practice: Keep instructions concise. Describe what the server covers, name the key tools, and note any important constraints or input expectations. Let your tool descriptions handle the details.

const app = createServer({
  instructions:
    "Use 'get_weather' for current conditions and 'get_forecast' for 5-day predictions. " +
    "Provide a city name or coordinates. All temperatures are in Celsius.",
});

Maximum length: 2000 characters.

Runtime vs. registry: createServer({ instructions }) controls what AI clients receive at runtime — this is the effective value. The instructions field in package.json is used only when publishing to the MCP Community Registry. See package.json Configuration for details on when to use each.

app.tool(name, handler)

Register a tool that AI clients can call.

const greet = ({ name, greeting }) => `${greeting}, ${name}!`;
greet.description = "Greets a person";
greet.input = {
  name: T.string({ required: true }),
  greeting: T.string({ default: "Hello" }),
};
app.tool("greet", greet);

Handler contract:

  • Receives parsed arguments as first parameter
  • Second parameter ask is a function when the client advertises sampling capability, or null when the client does not support sampling — always guard before calling
  • Third parameter ctx is an McpContext object with request-scoped platform values
  • Returns string, object (auto-serialized), or MCP content array
  • Attach .description, .input, and .annotations as properties on the function
  • Errors are caught and returned as tool error responses with secrets redacted

For details on sampling, see Sampling. For details on structured logging, see Logging.

Binary content types (ImageContent, AudioContent per MCP spec) are planned for a future release.

handler.annotations

Optional hints that tell AI clients how safe and consequential the tool is. All fields are optional booleans.

FieldTypeDefaultDescription
readOnlyHintbooleanfalseTool only reads data — no writes, creates, or deletes
destructiveHintbooleantrueTool can permanently destroy data
openWorldHintbooleantrueTool calls external systems (HTTP APIs, databases, files)
idempotentHintbooleanfalseCalling the tool multiple times with the same input produces the same result

Defaults are pessimistic. Always set all four explicitly so clients can make accurate permission decisions.

const listRecords = ({ table }) => db.query(`SELECT * FROM ${table}`);
listRecords.description = "List all records in a table";
listRecords.input = { table: T.string({ required: true }) };
listRecords.annotations = {
  readOnlyHint: true,
  destructiveHint: false,
  openWorldHint: false,
};
app.tool("list_records", listRecords);

See Tool annotations for the decision checklist and common patterns.

app.resource(uri, handler)

Register a resource. Use exact URIs for static resources, URI templates for dynamic ones.

// Static
const readme = () => "Content here";
readme.mimeType = "text/plain";
app.resource("docs://readme", readme);

// Dynamic (RFC 6570 Level 1 template)
const user = ({ userId }) => JSON.stringify({ id: userId });
user.mimeType = "application/json";
app.resource("user://{userId}", user);

app.prompt(name, handler)

Register a prompt template. Return a string for single-message prompts, or use conversation() for multi-message.

// Single message
const review = ({ code }) => `Review: ${code}`;
review.input = { code: T.string({ required: true }) };
app.prompt("code-review", review);

// Multi-message
const debug = ({ error }) =>
  conversation(({ user, ai }) => [user.say(`Debug: ${error}`), ai.say("Analyzing...")]);
debug.input = { error: T.string({ required: true }) };
app.prompt("debug", debug);

app.fetch(request, env, ctx)

The fetch handler. Compatible with Cloudflare Workers and mctx platform.

export default { fetch: app.fetch };

T (Type System)

Defines input schemas for tools and prompts. Produces JSON Schema.

import { T } from "@mctx-ai/mcp";

T.string(options?)

OptionTypeDescription
requiredbooleanMark as required
descriptionstringHuman-readable description
enumstring[]Allowed values
defaultstringDefault value
minLengthnumberMinimum length
maxLengthnumberMaximum length
patternstringRegex pattern
formatstringFormat hint (email, uri, etc.)

T.number(options?)

OptionTypeDescription
requiredbooleanMark as required
descriptionstringHuman-readable description
minnumberMinimum value
maxnumberMaximum value
defaultnumberDefault value

T.boolean(options?)

OptionTypeDescription
requiredbooleanMark as required
descriptionstringHuman-readable description
defaultbooleanDefault value

T.array(options?)

T.array({ items: T.string() }); // string array
T.array({ items: T.number(), required: true }); // required

T.object(options?)

T.object({
  properties: {
    name: T.string({ required: true }),
    age: T.number(),
  },
});

buildInputSchema(input)

Compiles a fn.input object into a JSON Schema inputSchema with required array. Used internally by the framework — available if you need raw schema generation.

import { buildInputSchema } from "@mctx-ai/mcp";

const schema = buildInputSchema({
  name: T.string({ required: true }),
  age: T.number(),
});
// { type: 'object', properties: { name: { type: 'string' }, age: { type: 'number' } }, required: ['name'] }

conversation()

Builds multi-message prompt responses.

import { conversation } from "@mctx-ai/mcp";

conversation(({ user, ai }) => [
  user.say("Hello"), // text message
  user.attach(data, "application/json"), // embedded data
  user.embed("image://logo"), // embedded resource
  ai.say("How can I help?"), // AI message
]);

createProgress(total?)

Creates a step function for generator-based tools.

import { createProgress } from "@mctx-ai/mcp";

const task = function* ({ data }) {
  const step = createProgress(3);
  yield step();
  yield step();
  yield step();
  return "Done";
};

Call createProgress() without arguments for indeterminate progress. Progress steps are tracked internally. In the current HTTP transport, progress is tracked but not streamed -- the final result is returned when the generator completes.


log

Structured logging with RFC 5424 severity levels.

import { log } from "@mctx-ai/mcp";

log.debug("Detailed info");
log.info("Informational");
log.notice("Significant event");
log.warning("Warning");
log.error("Error");
log.critical("Critical");
log.alert("Immediate action needed");
log.emergency("System unusable");

Logs are buffered internally. In the current HTTP transport, buffered logs are discarded after each request and are not visible in the dashboard. To write logs that appear in the real-time dashboard logs viewer, use console.log(), console.warn(), or console.error() instead.

See Logging for details on log levels, the buffer API, and how logs surface in the dev server and production dashboard.


Sampling (ask)

Tool, resource, and prompt handlers receive a second parameter named ask for LLM sampling.

  • ask is a function when the connected client advertises sampling capability during the MCP handshake.
  • ask is null when the client does not support sampling.

Always guard before calling ask:

const smart = async ({ question }, ask) => {
  if (!ask) {
    return `Answer: ${question}`;
  }
  const result = await ask(`Answer this question: ${question}`);
  return result;
};

See Sampling for advanced usage, SamplingOptions, transport requirements, and patterns.


McpContext

The third argument on all handler functions. Provides request-scoped platform values injected by the mctx dispatch worker.

const whoami = (args, ask, ctx) => {
  return ctx.userId ?? "anonymous";
};
PropertyTypeDescription
userIdstring | undefinedStable, per-server-isolated subscriber identity. Undefined in local dev and unauthenticated requests.

userId is platform-injected via the X-Mctx-User-Id request header. It cannot be forged by the caller. See User Identity for usage patterns and what "per-server-isolated" means.


Security

The framework applies security protections automatically:

  • Error sanitization — redacts AWS keys, JWTs, connection strings, Bearer tokens, API keys
  • Size limits — prevents DoS via large request/response bodies
  • URI validation — blocks file://, javascript:, data: schemes
  • Path traversal — detects ../ sequences including encoded variants
  • Prototype pollution — strips __proto__, constructor, prototype keys

These protections are internal and applied automatically. You don't need to configure them.


See Also

  • Quickstart — Build your first MCP server in 5 minutes
  • Tools — Practical examples and patterns for tools
  • Resources — How to expose data for AI context
  • Prompts — How to build prompt templates
  • Sampling — LLM-in-the-loop with the ask parameter
  • Logging — Structured logging and dashboard visibility
  • User Identity — Working with ctx.userId
  • Deploy Your MCP Server — Package structure and deployment checklist

See something wrong? Report it or suggest an improvement — your feedback helps make these docs better.