External Clients

Learn how to connect to external MCP servers via HTTP or STDIO and generate fully typed TypeScript clients with autocomplete support.

For the complete documentation index, see llms.txt. Markdown variants of every page are available by appending .md to the URL.

xmcp lets you connect to external MCP servers and generate fully typed clients. The CLI generates TypeScript clients with autocomplete for all tools exposed by HTTP or STDIO-based MCP servers. For production deployments, use the HTTP transport. STDIO is limited to local development because it cannot be deployed in production environments.

Creating the Clients File

Create a src/clients.ts file and export a ClientConnections object. The object keys become the client names:

src/clients.ts
import { ClientConnections } from "xmcp";

export const clients: ClientConnections = {
  context: {
    url: "https://mcp.context7.com/mcp",
    headers: [{ name: "CONTEXT7_API_KEY", env: "CONTEXT7_API_KEY" }],
  },
  playwright: {
    npm: "@playwright/mcp",
  },
};

HTTP clients connect to remote MCP servers over HTTP. This is the recommended transport for production deployments.

Code
type HttpClientConfig = {
  name?: string; // Optional, defaults to object key
  type?: "http"; // Optional, inferred from url
  url: string; // MCP server URL (required)
  headers?: CustomHeaders; // Optional headers array
};

type CustomHeaders = CustomHeader[];

type CustomHeader = StaticHeader | EnvHeader;

// Static value (non-sensitive)
interface StaticHeader {
  name: string;
  value: string;
}

// Environment variable (sensitive values like API keys)
interface EnvHeader {
  name: string;
  env: string; // Environment variable name to read at runtime
}

Example:

Code
{
  context: {
    url: "https://mcp.context7.com/mcp",
    headers: [
      { name: "CONTEXT7_API_KEY", env: "CONTEXT7_API_KEY" },
    ],
  },
}

To make HTTP extra.clientInfo available on stateless tool calls, repeat client identity in the current request headers:

Code
{
  assistant: {
    url: "https://example.com/mcp",
    headers: [
      { name: "x-mcp-client-name", value: "my-client" },
      { name: "x-mcp-client-version", value: "1.0.0" },
      { name: "x-mcp-client-title", value: "My Client" },
    ],
  },
}

STDIO Clients (local servers)

STDIO clients spawn local processes that communicate via standard input/output.

Code
type StdioClientConfig = {
  name?: string; // Optional, defaults to object key
  type?: "stdio"; // Optional, inferred from npm/command
  command?: string; // Command to run (e.g., "npx", "bunx", "node")
  args?: string[]; // Command arguments
  npm?: string; // npm package to run via npx
  npmArgs?: string[]; // Arguments to pass to npm package
  env?: Record<string, string>; // Environment variables
  cwd?: string; // Working directory
  stderr?: "pipe" | "inherit" | "ignore"; // Stderr handling
};

Examples:

Code
// Simple npm package
{ npm: "@playwright/mcp" }

// With arguments
{ npm: "@playwright/mcp", npmArgs: ["--browser", "chromium"] }

// Custom command
{ command: "bunx", args: ["-y", "@upstash/context7-mcp"] }

// With environment variables
{
  npm: "@some/mcp-server",
  env: { DEBUG: "true", LOG_LEVEL: "verbose" }
}

The npm package will be installed automatically if it is not already installed in your project.

Running the Generator

Run the generator from your project root:

Code
npx @xmcp-dev/cli generate

This reads from src/clients.ts and writes generated clients to src/generated/.

Options:

  • -o, --out <path> - Output directory (default: src/generated)
  • -c, --clients <path> - Clients file path (default: src/clients.ts)

Generated Output

For each client defined in clients.ts, the CLI generates a client.{name}.ts file containing:

  • Zod schemas for each tool's arguments
  • Type exports (e.g., GreetArgs)
  • Tool metadata objects
  • createRemoteToolClient() factory function
  • Pre-instantiated client export

An index file (client.index.ts) is always generated with a unified generatedClients object for accessing all clients.

Using Generated Clients

Import generatedClients from the generated index file and call tools directly:

src/tools/get-library-docs.ts
import { InferSchema, type ToolMetadata } from "xmcp";
import { generatedClients } from "../generated/client.index";
import { z } from "zod";

export const schema = {
  libraryName: z.string().describe("The name of the library"),
};

export const metadata: ToolMetadata = {
  name: "get-library-docs",
  description: "Get documentation for a library",
};

export default async function handler({
  libraryName,
}: InferSchema<typeof schema>) {
  const docs = await generatedClients.context.getLibraryDocs({
    context7CompatibleLibraryID: libraryName,
  });
  return (docs.content as any)[0].text;
}

The generated clients provide full autocomplete for all available tools and their arguments.

Example: Browser Navigation

src/tools/browser-navigate.ts
import { InferSchema, type ToolMetadata } from "xmcp";
import { generatedClients } from "../generated/client.index";
import { z } from "zod";

export const schema = {
  url: z.string().describe("The URL to navigate to"),
};

export const metadata: ToolMetadata = {
  name: "browser-navigate",
  description: "Navigate to a URL",
};

export default async function handler({ url }: InferSchema<typeof schema>) {
  await generatedClients.playwright.browserNavigate({ url });
  return `Navigated to: ${url}`;
}

Caveats

  • Server must be available — The CLI connects over HTTP or spawns the STDIO package to fetch tool definitions. Ensure the remote server is reachable or the npm package is installed.
  • Prefer env for secrets — API keys can be provided as CLI args or via the env map. Prefer env for sensitive values.