mctxdocs
Build Your MCP Server

TypeScript

Use the framework's TypeScript types to get autocompletion, type-checked inputs, and compile-time errors on your handler code.

Need help? Connect help.mctx.ai for instant answers.

@mctx-ai/mcp ships full TypeScript definitions. Handler argument types are inferred from your T schema definitions, and the return types, context shape, and options interfaces are all typed.

tsconfig setup

The scaffolded project from npm create mctx-app includes a working tsconfig.json. If you are adding TypeScript to an existing project, the minimum recommended configuration is:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true
  }
}

strict: true enables the full suite of TypeScript checks, including strictNullChecks. This is important for ask (which is AskFunction | null) and ctx (which is McpContext | undefined). Without strict mode, null guards compile but carry no enforcement.

Type imports

Import handler types from @mctx-ai/mcp:

import type {
  ToolHandler,
  GeneratorToolHandler,
  ResourceHandler,
  PromptHandler,
  McpContext,
} from "@mctx-ai/mcp";
TypeUse for
ToolHandlerStandard (non-generator) tool handlers
GeneratorToolHandlerGenerator tools that yield progress notifications
ResourceHandlerResource handlers (static and URI template)
PromptHandlerPrompt handlers returning strings or conversations
McpContextThe ctx parameter type if you need it explicitly

Typed handler example

Annotate a handler with ToolHandler and TypeScript enforces the call signature, including the null check on ask:

import { createServer, T, log } from "@mctx-ai/mcp";
import type { ToolHandler } from "@mctx-ai/mcp";

const server = createServer();

const greet: ToolHandler = (args, ask, ctx) => {
  // args is Record<string, any> — cast to your expected shape
  const { name } = args as { name: string };

  log.info({ name, userId: ctx?.userId });

  // ask is AskFunction | null | undefined — TypeScript requires the guard
  if (ask) {
    // ask() is callable here — TypeScript narrows the type
  }

  return `Hello, ${name}!`;
};
greet.description = "Greet a person by name";
greet.input = {
  name: T.string({ required: true, description: "Name to greet" }),
};
greet.annotations = {
  readOnlyHint: true,
  destructiveHint: false,
  openWorldHint: false,
  idempotentHint: true,
};

server.tool("greet", greet);

export default { fetch: server.fetch };

TypeScript infers that ask is AskFunction | null | undefined, so calling ask(prompt) without the guard is a type error. The same applies to ctx?.userIdctx is optional, so the optional chain is required.

Handler type generics

The handler types accept a generic for the args shape, which avoids the cast inside the handler body:

// Without generic — cast required inside
const myTool: ToolHandler = (args) => {
  const { query } = args as { query: string };
  return query;
};

// With explicit cast at declaration — the type is narrow from the start
type MyArgs = { query: string };
const myTool: ToolHandler = (args: MyArgs) => {
  return args.query; // no cast needed
};

Both approaches compile. The explicit parameter type is preferred for longer handlers where the cast at the top would be far from the usage.

buildInputSchema

buildInputSchema is a low-level export that compiles a T schema map into a JSON Schema object. The framework calls it internally when you set handler.input. You do not need it in normal handler code.

It is useful if you are building tools programmatically or need the raw JSON Schema for another purpose:

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

const schema = buildInputSchema({
  name: T.string({ required: true }),
  age: T.number({ min: 0, max: 150 }),
});
// => { type: "object", properties: { name: {...}, age: {...} }, required: ["name"] }

Next steps


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