Overview
xmcp allows your MCP server to act as a client to other MCP servers. This enables:
Service Composition - Combine multiple MCP servers into unified tools
Tool Aggregation - Access tools from external services (Playwright, Context7, etc.)
Data Integration - Pull data from remote MCP sources
Proxy Patterns - Create gateway servers that route to multiple backends
xmcp provides two client creation functions:
packages/xmcp/src/index.ts
export {
createHTTPClient ,
createSTDIOClient ,
listSTDIOClientTools ,
callSTDIOClientTool ,
disconnectSTDIOClient ,
} from "./client" ;
Client Configuration
Define client connections in src/clients.ts:
examples/external-clients/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" ,
},
};
xmcp automatically generates typed client code for all defined connections.
HTTP Clients
Connect to remote MCP servers over HTTP/HTTPS.
Creating HTTP Clients
packages/xmcp/src/client/index.ts
export async function createHTTPClient ({
url ,
headers ,
} : HttpClientOptions ) : Promise < Client < Request , Notification , Result >> {
const client = new Client < Request , Notification , Result >(
CLIENT_IDENTITY ,
CLIENT_CAPABILITIES
);
const headersRecord = headers
? headersToRecord ( headers )
: ({} as Record < string , string >);
const serverUrl = new URL ( url );
const transportOptions : StreamableHTTPClientTransportOptions = {
... ( headers ? { requestInit: { headers: headersRecord } } : {}),
reconnectionOptions: {
maxReconnectionDelay: 30000 ,
initialReconnectionDelay: 1000 ,
reconnectionDelayGrowFactor: 1.5 ,
maxRetries: 2 ,
},
};
const transport = new StreamableHTTPClientTransport (
serverUrl ,
transportOptions
);
await client . connect ( transport );
return client ;
}
HTTP Client Configuration
Full MCP server base URL (e.g., https://host.tld/mcp)
Optional array of HTTP headers for authentication
Use headers for API keys and authentication tokens:
packages/xmcp/src/client/headers.ts
export interface StaticHeader {
name : string ;
value : string ;
}
export interface EnvHeader {
name : string ;
env : string ; // Environment variable name
}
export type CustomHeader = StaticHeader | EnvHeader ;
export type CustomHeaders = CustomHeader [];
Use env for sensitive values like API keys. The string is the environment variable name, and xmcp reads process.env[env] at runtime.
Example HTTP Client Usage
examples/external-clients/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 to get docs for" ),
};
export const metadata : ToolMetadata = {
name: "get-library-docs" ,
description: "Get the docs for a library" ,
};
export default async function handler ({
libraryName ,
} : InferSchema < typeof schema >) {
const libraryDocs = await generatedClients . context . getLibraryDocs ({
context7CompatibleLibraryID: libraryName ,
});
const result = ( libraryDocs . content as any )[ 0 ]. text ;
return `Library docs: ${ result } ` ;
}
STDIO Clients
Connect to local MCP servers that communicate via stdin/stdout.
Creating STDIO Clients
packages/xmcp/src/client/index.ts
export async function createSTDIOClient (
options : StdioClientOptions
) : Promise < StdioClientConnection > {
const { onStderrData , ... serverParams } = options ;
const client = new Client < Request , Notification , Result >(
CLIENT_IDENTITY ,
CLIENT_CAPABILITIES
);
const transport = new StdioClientTransport ({
... serverParams ,
stderr: serverParams . stderr ?? "pipe" ,
});
const stderrStream = transport . stderr ;
if ( stderrStream ) {
const stderrHandler =
onStderrData ??
(( data : Buffer ) => {
console . error ( `[STDIO MCP Server stderr]: ${ data } ` );
});
stderrStream . on ( "data" , stderrHandler );
}
await client . connect ( transport );
return {
client ,
transport ,
};
}
STDIO Client Configuration
Command to execute (e.g., "node", "python")
Command arguments (e.g., ["server.js"])
NPM package to execute (alternative to command)
Environment variables for the subprocess
Working directory for the subprocess
stderr
'pipe' | 'inherit' | 'ignore'
default: "pipe"
How to handle stderr from the subprocess
Custom handler for stderr data
STDIO Helper Functions
Convenience functions for STDIO clients:
packages/xmcp/src/client/index.ts
export async function listSTDIOClientTools (
connection : StdioClientConnection
) : Promise < string []> {
try {
const result = await connection . client . listTools ();
return result . tools . map (( tool ) => tool . name );
} catch ( error ) {
console . error ( "Failed to list stdio MCP tools:" , error );
return [];
}
}
export async function callSTDIOClientTool (
connection : StdioClientConnection ,
toolName : string ,
args : Record < string , unknown >
) : Promise < unknown > {
try {
return await connection . client . callTool ({
name: toolName ,
arguments: args ,
});
} catch ( error ) {
console . error ( `Failed to call stdio MCP tool " ${ toolName } ":` , error );
throw error ;
}
}
export async function disconnectSTDIOClient (
connection : StdioClientConnection
) : Promise < void > {
try {
await connection . transport . close ();
await connection . client . close ();
} catch ( error ) {
console . error ( "Error disconnecting from stdio MCP server:" , error );
throw error ;
}
}
Example STDIO Client Usage
examples/external-clients/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 } ` ;
}
Generated Client Code
xmcp generates typed client wrappers for all connections:
examples/external-clients/src/generated/client.index.ts
function proxyPromise < T extends object >( clientPromise : Promise < T >) : T {
return new Proxy ({} as T , {
get ( _target , prop ) {
return async ( ... args : unknown []) => {
const client = await clientPromise ;
const method = ( client as Record < string | symbol , unknown >)[ prop ];
if ( typeof method === "function" ) {
return method . call ( client , ... args );
}
throw new Error ( `Property " ${ String ( prop ) } " is not a function` );
};
},
});
}
export const generatedClients = {
"context" : proxyPromise < ClientContextClient >( clientContext ),
"playwright" : proxyPromise < ClientPlaywrightClient >( clientPlaywright ),
} as const ;
Generated clients are automatically typed and provide full IntelliSense support.
Client Types
packages/xmcp/src/client/types.ts
export type HttpClientConfig = {
name ?: string ;
type ?: "http" ;
url : string ;
headers ?: CustomHeaders ;
};
export type StdioClientConfig = {
name ?: string ;
type ?: "stdio" ;
command ?: string ;
args ?: string [];
npm ?: string ;
npmArgs ?: string [];
env ?: Record < string , string >;
cwd ?: string ;
stderr ?: StdioIOStrategy ;
};
export type ClientConnectionEntry =
| string
| HttpClientConfig
| StdioClientConfig
| ClientDefinition ;
export type ClientConnections =
| Record < string , ClientConnectionEntry >
| ClientConnectionEntry [];
Project Configuration
Enable client generation in your xmcp config:
examples/external-clients/xmcp.config.ts
import { XmcpConfig } from "xmcp" ;
const config : XmcpConfig = {
http: true ,
paths: {
tools: "./src/tools" ,
prompts: false ,
resources: false ,
},
typescript: {
skipTypeCheck: true ,
},
};
export default config ;
Use Cases
Documentation Lookup Query Context7 or other doc services from your tools
Browser Automation Control Playwright for web scraping and testing
Service Mesh Create gateway servers that route to specialized backends
Tool Aggregation Combine tools from multiple MCP servers into one interface
Best Practices
Use Environment Variables Store API keys and secrets in env vars, not in code
Handle Errors Always wrap client calls in try-catch for network failures
Connection Pooling Reuse generated clients instead of creating new connections
Timeout Configuration Set appropriate timeouts for remote calls to prevent hangs
Client Identity
All xmcp clients identify themselves to servers:
packages/xmcp/src/client/index.ts
const packageJson = require ( "../../package.json" );
export const CLIENT_IDENTITY = {
name: packageJson . name ,
version: packageJson . version ,
};
const CLIENT_CAPABILITIES = {
capabilities: {
sampling: {},
elicitation: {},
roots: { listChanged: true },
},
} as const ;
Next Steps
Tools Learn how to create tools that use client connections
Transports Understand HTTP vs STDIO transports
Authentication Secure client connections with auth headers
API Reference Detailed client API documentation