MCP TypeScript SDK
    Preparing search index...

    This page covers cross-cutting protocol mechanics that apply to both clients and servers.

    Both client and server expose a ping() method for health checks. The remote side responds automatically — no handler registration is needed.

    // Client pinging the server:
    await client.ping();

    // With a timeout (milliseconds):
    await client.ping({ timeout: 5000 });

    // Server pinging the client (via the low-level server, no timeout option):
    await server.server.ping();

    Long-running requests can report progress to the caller. The SDK handles progressToken assignment automatically when you provide an onprogress callback.

    Receiving progress (client side):

    const result = await client.callTool({ name: 'long-task', arguments: {} }, CallToolResultSchema, {
    onprogress: progress => {
    // progress has: { progress: number, total?: number, message?: string }
    console.log(`${progress.progress}/${progress.total}: ${progress.message}`);
    },
    timeout: 30000,
    resetTimeoutOnProgress: true
    });

    Sending progress (server side, from a tool handler):

    server.registerTool(
    'count',
    {
    description: 'Count to N with progress updates',
    inputSchema: { n: z.number() }
    },
    async ({ n }, extra) => {
    for (let i = 1; i <= n; i++) {
    if (extra._meta?.progressToken !== undefined) {
    await extra.sendNotification({
    method: 'notifications/progress',
    params: {
    progressToken: extra._meta.progressToken,
    progress: i,
    total: n,
    message: `Counting: ${i}/${n}`
    }
    });
    }
    await new Promise(resolve => setTimeout(resolve, 100));
    }
    return { content: [{ type: 'text', text: `Counted to ${n}` }] };
    }
    );

    For a runnable example, see progressExample.ts.

    Requests can be cancelled by the caller using an AbortSignal. The SDK sends a notifications/cancelled message to the remote side and aborts the handler via its signal.

    Client cancelling a request:

    const controller = new AbortController();

    const resultPromise = client.callTool({ name: 'slow-tool', arguments: {} }, CallToolResultSchema, { signal: controller.signal });

    // Cancel after 5 seconds:
    setTimeout(() => controller.abort('User cancelled'), 5000);

    Server handler responding to cancellation:

    server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
    for (let i = 0; i < 100; i++) {
    if (extra.signal.aborted) {
    return { content: [{ type: 'text', text: 'Cancelled' }], isError: true };
    }
    await doWork();
    }
    return { content: [{ type: 'text', text: 'Done' }] };
    });

    All list methods (listTools, listPrompts, listResources, listResourceTemplates) support cursor-based pagination. Pass cursor from the previous response's nextCursor to fetch the next page.

    let cursor: string | undefined;
    const allTools: Tool[] = [];

    do {
    const result = await client.listTools({ cursor });
    allTools.push(...result.tools);
    cursor = result.nextCursor;
    } while (cursor);

    The same pattern applies to listPrompts, listResources, and listResourceTemplates.

    Both client and server declare their capabilities during the initialize handshake. The SDK enforces these — attempting to use an undeclared capability throws an error.

    Client capabilities are set at construction time:

    const client = new Client(
    { name: 'my-client', version: '1.0.0' },
    {
    capabilities: {
    roots: { listChanged: true },
    sampling: {},
    elicitation: { form: {} }
    }
    }
    );

    After connecting, inspect what the server supports:

    await client.connect(transport);

    const caps = client.getServerCapabilities();
    if (caps?.tools) {
    const tools = await client.listTools();
    }
    if (caps?.resources?.subscribe) {
    // server supports resource subscriptions
    }

    Server capabilities are inferred from registered handlers. When using McpServer, capabilities are set automatically based on what you register (tools, resources, prompts). With the low-level Server, you declare them in the constructor.

    The SDK automatically negotiates protocol versions during initialize. The client sends LATEST_PROTOCOL_VERSION and the server responds with the highest mutually supported version.

    Supported versions are defined in SUPPORTED_PROTOCOL_VERSIONS (currently 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07). If the server responds with an unsupported version, the client throws an error.

    Version negotiation is handled automatically by client.connect(). After connecting, you can inspect the result:

    await client.connect(transport);

    const serverVersion = client.getServerVersion();
    // { name: 'my-server', version: '1.0.0' }

    const serverCaps = client.getServerCapabilities();
    // { tools: { listChanged: true }, resources: { subscribe: true }, ... }

    MCP uses JSON Schema 2020-12 for tool input and output schemas. When using McpServer with Zod, schemas are converted to JSON Schema automatically:

    server.registerTool(
    'calculate',
    {
    description: 'Add two numbers',
    inputSchema: { a: z.number(), b: z.number() }
    },
    async ({ a, b }) => ({
    content: [{ type: 'text', text: String(a + b) }]
    })
    );

    With the low-level Server, you provide JSON Schema directly:

    {
    name: 'calculate',
    inputSchema: {
    type: 'object',
    properties: {
    a: { type: 'number' },
    b: { type: 'number' }
    },
    required: ['a', 'b']
    }
    }

    The SDK validates tool outputs against outputSchema (when provided) using a pluggable JSON Schema validator. The default validator uses Ajv; a Cloudflare Workers-compatible alternative is available via CfWorkerJsonSchemaValidator.