The SDK provides a high-level Client class that connects to MCP servers over different transports:
StdioClientTransport – for local processes you spawn.StreamableHTTPClientTransport – for remote HTTP servers.SSEClientTransport – for legacy HTTP+SSE servers (deprecated).Runnable client examples live under:
simpleStreamableHttp.tsstreamableHttpWithSseFallbackClient.tsssePollingClient.tsmultipleClientsParallel.tsparallelToolCallsClient.tsA typical flow:
Client with name, version and capabilities.client.connect(transport).listTools, callToollistPrompts, getPromptlistResources, readResourceSee simpleStreamableHttp.ts for an interactive CLI client that exercises these methods and shows how to handle notifications, elicitation and tasks.
To support both modern Streamable HTTP and legacy SSE servers, use a client that:
StreamableHTTPClientTransport.SSEClientTransport on a 4xx response.Runnable example:
For OAuth-secured MCP servers, the client auth module exposes:
ClientCredentialsProviderPrivateKeyJwtProviderStaticPrivateKeyJwtProviderExamples:
simpleOAuthClient.tssimpleOAuthClientProvider.tssimpleClientCredentials.tsdemoInMemoryOAuthProvider.ts (tests live under test/examples/server/demoInMemoryOAuthProvider.test.ts)These examples show how to:
Use StdioClientTransport to connect to a server that runs as a local child process:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
const transport = new StdioClientTransport({
command: 'node',
args: ['server.js'],
env: { NODE_ENV: 'production' },
cwd: '/path/to/server'
});
const client = new Client({ name: 'my-client', version: '1.0.0' });
await client.connect(transport);
// connect() calls transport.start() automatically, spawning the child process
The transport communicates over the child process's stdin/stdout using JSON-RPC. The stderr option controls where the child's stderr goes (defaults to 'inherit').
Roots let a client expose filesystem locations to the server, so the server knows which directories or files are relevant. Declare the roots capability and register a handler:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { ListRootsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
const client = new Client({ name: 'my-client', version: '1.0.0' }, { capabilities: { roots: { listChanged: true } } });
client.setRequestHandler(ListRootsRequestSchema, async () => {
return {
roots: [
{ uri: 'file:///home/user/project', name: 'My Project' },
{ uri: 'file:///home/user/data', name: 'Data Directory' }
]
};
});
When the set of roots changes, notify the server so it can re-query:
await client.sendRootsListChanged();
Root URIs must use the file:// scheme. The listChanged: true capability flag is required to send change notifications.