@mcp-z/client
    Preparing search index...

    @mcp-z/client

    @mcp-z/client

    Programmatic MCP client library for Node.js - connect, discover, and call tools on Model Context Protocol servers.

    • Run MCP tools from scripts
    • Connect to multiple servers in one process
    • Integration tests for MCP servers
    npm install @mcp-z/client
    

    Requires Node.js >= 20.

    Create a local stdio server that exposes an echo tool:

    npm install @modelcontextprotocol/sdk zod
    

    Save this as echo-server.mjs:

    import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
    import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
    import { z } from 'zod';

    const server = new McpServer({ name: 'echo', version: '1.0.0' });
    server.registerTool('echo', { inputSchema: { message: z.string() } }, async ({ message }) => ({
    content: [{ type: 'text', text: message }]
    }));
    await server.connect(new StdioServerTransport());

    Connect to it and print the returned text:

    import { createServerRegistry } from '@mcp-z/client';

    const registry = createServerRegistry({ echo: { command: 'node', args: ['echo-server.mjs'] } });
    const client = await registry.connect('echo');
    const response = await client.callTool('echo', { message: 'hello MCP' });
    console.log(response.text()); // hello MCP
    await registry.close();

    MCP supports stdio and HTTP.

    Stdio

    {
    echo: {
    command: 'node',
    args: ['server.js'],
    env: { LOG_LEVEL: 'info' }
    }
    }

    HTTP

    {
    todoist: {
    type: 'http',
    url: 'https://ai.todoist.net/mcp',
    headers: { Authorization: 'Bearer token' }
    }
    }

    HTTP with start block (extension)

    Use dialects: ['start'] to spawn HTTP servers with start blocks.

    const registry = createServerRegistry(
    {
    api: {
    type: 'http',
    url: 'http://localhost:3000/mcp',
    start: {
    command: 'node',
    args: ['server.js'],
    stop: {
    command: 'node',
    args: ['request-server-stop.js', 'http://localhost:3000/admin/shutdown']
    }
    }
    }
    },
    { dialects: ['start'] }
    );

    start.stop is an optional application-specific cooperative shutdown command for a server started by this registry. It runs once during registry.close(), before the registry waits for the owned process to close. The stop command must use a mechanism implemented by that server; MCP HTTP has no server shutdown operation. Commands use argument arrays without a shell. timeoutMs bounds each cooperative wait for the stop command and owned process (five seconds by default); emergency termination has its own bounded wait. Without a stop command there is no portable HTTP shutdown protocol: POSIX requests the configured signal, while Windows waits for the direct child and then uses emergency termination. External HTTP servers and client lease closure do not run the stop command.

    Stdio shutdown sends EOF to the owned process first. If it remains open, POSIX sends the requested signal and waits again; Windows proceeds to bounded termination of the direct child. POSIX shutdown tracks the detached process group. Windows descendant ownership is not verified, so a wrapper that exits while leaving descendants may leave those processes running. timedOut reports a missed cooperative window, and killedCount counts direct-child or process-group emergency terminations.

    • createServerRegistry(config, options?)
    • registry.connect(name, options?)
    • registry.searchCapabilities(query, options?)
    • registry.close()
    • client.callTool(name, args)
    • client.getPrompt(name, args)
    • client.readResource(uri)
    • client.listTools() / client.listResources() / client.listPrompts()

    client.nativeClient is the original SDK instance. The managed client is a separate view of the same connection; stdio clients also have registry leases. Closing a stdio lease releases its reference, while closing the native instance closes the connection directly.

    Tool, prompt, and resource calls return wrappers with:

    • json() - Parse structured content
    • text() - First text result

    Responses expose typed MCP fields: tools have content, structuredContent, and isError; prompts have description and messages; resources have contents. Each response exposes _meta. These properties retain non-text messages, roles, binary data, MIME types, and all entries. JSON.stringify(response) serializes the original MCP result, including server extensions. json() continues to convert content rather than return the protocol envelope.

    An awaited managed tool call rejects an MCP isError result with ToolResponseError; its response retains the complete error result. Protocol and transport errors also reject. Conversion failures occur when calling json() or text().

    const response = await client.callTool('drive-search', { query: 'Q4 Reports' });
    const data = response.json();
    const prompt = await client.getPrompt('query-syntax', { service: 'gmail' });
    console.log(prompt.text());
    const resource = await client.readResource('mcp-pdf://abc123');
    console.log(resource.text());
    const results = await registry.searchCapabilities('message send', {
    types: ['tool'],
    servers: ['gmail', 'outlook']
    });
    • cwd - Working directory for spawned processes (default: process.cwd())
    • env - Base env for all servers (if set, process.env is not merged)
    • dialects - Which servers to spawn: ['servers'] (stdio), ['start'] (HTTP start blocks), or both

    If an HTTP server supports DCR, pass a token store via dcrAuthenticator:

    import Keyv from 'keyv';
    import { createServerRegistry } from '@mcp-z/client';

    const registry = createServerRegistry({
    todoist: { type: 'http', url: 'https://ai.todoist.net/mcp' }
    });

    const client = await registry.connect('todoist', {
    dcrAuthenticator: { tokenStore: new Keyv() }
    });

    By default a connect performs the plain 2025 MCP connect sequence. Pass versionNegotiation to negotiate the protocol revision instead:

    // Probe the server first; connect at the newest revision it offers,
    // falling back to the 2025 sequence when it cannot serve the modern era
    const client = await registry.connect('modern-server', {
    versionNegotiation: { mode: 'auto' }
    });

    // Require the 2026-07-28 revision; a server that cannot serve it fails
    // the connect with SdkErrorCode.EraNegotiationFailed
    const pinned = await registry.connect('strict-server', {
    versionNegotiation: { mode: { pin: '2026-07-28' } }
    });

    After connecting, client.getProtocolEra() returns 'modern' or 'legacy' and client.getNegotiatedProtocolVersion() the revision the server settled on.

    With mode: 'auto' against a stdio server, a legacy server that never answers the server/discover probe costs the full 60-second request timeout before the client falls back to the 2025 sequence. Any reply, including a "method not found" error, ends the probe.

    • Node.js >= 20

    If a coding agent will use this package, install its agent guidance globally:

    npx skills add https://github.com/mcp-z/client.git -g -s mcp-z-client
    

    API Docs