MCP工具调用客户端库

一个用于Node.js的MCP客户端库,用于连接MCP服务器、发现并调用其工具,适用于脚本运行、多服务器集成和MCP服务器测试。

  • by mcp-z
  • TypeScript
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 mcp-z/client

使用说明

@mcp-z/client

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

Common uses

  • Run MCP tools from scripts
  • Connect to multiple servers in one process
  • Integration tests for MCP servers

Install

npm install @mcp-z/client

Requires Node.js >= 20.

Quick start

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();

Configuration

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.

API overview

Registry

  • createServerRegistry(config, options?)
  • registry.connect(name, options?)
  • registry.searchCapabilities(query, options?)
  • registry.close()

Managed client

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

Response helpers

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().

Examples

Call a tool

const response = await client.callTool('drive-search', { query: 'Q4 Reports' });
const data = response.json();

Get a prompt

const prompt = await client.getPrompt('query-syntax', { service: 'gmail' });
console.log(prompt.text());

Read a resource

const resource = await client.readResource('mcp-pdf://abc123');
console.log(resource.text());

Search capabilities

const results = await registry.searchCapabilities('message send', {
  types: ['tool'],
  servers: ['gmail', 'outlook']
});

createServerRegistry options

  • 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

OAuth (DCR)

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

Protocol version negotiation

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.

Requirements

  • Node.js >= 20

Agent skill

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

Documentation

API Docs