| name | mcp-client-ts |
| description | Build TypeScript MCP clients with composable code snippets. Includes agentic pattern with LLM integration, server-initiated requests (sampling, elicitation), and dynamic discovery. Use when creating applications that connect to MCP servers. |
TypeScript MCP Client Builder
Build MCP (Model Context Protocol) clients in TypeScript using code snippets and patterns from the official SDK.
Now includes advanced capability support: sampling handlers, elicitation, roots, dynamic discovery, resource subscriptions, and task support with persistence.
How It Works
- Browse the snippet catalog below or in
snippets/
- Copy the snippets you need into your project
- Customize the copied code for your use case
- Connect to your MCP server and use tools/resources/prompts
Quick Start Decision Tree
Which Snippet Should I Start With?
Building an agentic app where an LLM decides when to use tools?
└─> Use client-with-llm snippet
Includes Claude integration, tool calling loop, interactive chat
Just need direct MCP server access (no LLM)?
└─> Use client-setup snippet
Basic connection, manual tool/resource/prompt calls
Which Transport Should I Use?
Connecting to a local process (CLI tool, desktop app)?
└─> Use StdioClientTransport
Examples: Claude Desktop, CLI applications, local dev servers
Connecting to a remote HTTP server?
└─> Use StreamableHTTPClientTransport
Examples: Cloud-hosted MCP servers, web services
Need to support legacy SSE-only servers?
└─> Use StreamableHTTP with SSE fallback pattern
See transport-http snippet for fallback example
Phase 1: Research
1.1 Identify Your Server
Before writing code, understand:
- What MCP server are you connecting to?
- How is the server started? (local process vs remote URL)
- Does the server require authentication?
- What tools/resources/prompts does it expose?
1.2 Browse Available Snippets
| Snippet | Description | Best For |
|---|
client-setup | Basic Client with connection pattern | Direct MCP access without LLM |
client-with-llm | Agentic client with Claude (API/Bedrock/Vertex/Azure) | LLM-powered tool calling apps |
transport-stdio | StdioClientTransport examples | Local/subprocess servers |
transport-http | StreamableHTTPClientTransport examples | Remote HTTP servers |
sampling-handler | Handle sampling requests with tool support (MCP 2025-11-25) | Servers that need Claude completions |
elicitation-handler | Handle user input requests (form/URL) | OAuth, confirmations, data collection |
roots-handler | Expose filesystem directories | IDE integrations, file tools |
list-changed | React to dynamic capability changes | Real-time tool/resource updates |
subscriptions | Subscribe to resource updates | Live data feeds, monitoring |
logging | Receive server log messages with level filtering | Debugging, monitoring |
completions | Argument autocomplete with interactive picker | IDE-like experiences |
tasks | Task support for long-running operations with streaming | Servers with async/long-running tools |
persistent-task-store | File-based task persistence with TTL | Resumable task tracking |
Phase 2: Implement
2.1 Initialize Project
mkdir my-mcp-client && cd my-mcp-client
npm init -y
npm install @modelcontextprotocol/sdk @anthropic-ai/sdk dotenv
npm install -D typescript @types/node
npx tsc --init
mkdir src
Note: The above installs the direct Anthropic API SDK. For other providers, see the provider options in the client-with-llm snippet.
Update tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"]
},
"include": ["src/**/*"]
}
Update package.json:
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Create .env for API keys (choose based on your provider):
ANTHROPIC_API_KEY=your-api-key
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_REGION=us-central1
ANTHROPIC_FOUNDRY_API_KEY=your-api-key
ANTHROPIC_FOUNDRY_RESOURCE=your-resource.azure.anthropic.com
Create .gitignore:
node_modules/
dist/
build/
.env
*.log
2.2 Add Snippets
Copy snippets from this skill's snippets/ directory into your project:
snippets/
├── client/
│ ├── index.ts # Basic client setup
│ └── with-llm.ts # Agentic client with Claude
└── transports/
├── stdio.ts # Local process transport
└── http.ts # Remote HTTP transport
Note: The client-with-llm snippet requires a Claude provider SDK:
npm install dotenv
npm install @anthropic-ai/sdk
npm install @anthropic-ai/bedrock-sdk
npm install @anthropic-ai/vertex-sdk
npm install @anthropic-ai/foundry-sdk
Copy snippets to your project:
cp snippets/client/index.ts /path/to/my-mcp-client/src/
cp snippets/transports/stdio.ts /path/to/my-mcp-client/src/
2.3 Customize and Connect
Each snippet includes:
- Source URL linking to official documentation
- Working code ready to customize
Modify the copied code:
- Update client name and version
- Configure the appropriate transport
- Add your application logic
- Handle tool results and resources
Phase 3: Test
3.1 Build
npm run build
3.2 Test Connection
Required: The client-with-llm snippet requires ANTHROPIC_API_KEY (or equivalent provider credentials) in your .env file before testing. Without it, you'll get: Could not resolve authentication method.
node dist/index.js path/to/server.js
node dist/index.js @modelcontextprotocol/server-everything
node dist/index.js @modelcontextprotocol/server-everything "add 5 and 3"
3.3 Quality Checklist
Available Snippets Catalog
Client
| Name | Description |
|---|
client-setup | Basic MCP Client class with connection, tools, prompts, and resources methods |
client-with-llm | Agentic client with Claude (supports API, Bedrock, Vertex, Azure) |
multi-server | Connect to multiple MCP servers with tool aggregation and automatic routing |
Transports
| Name | Description |
|---|
transport-stdio | StdioClientTransport for Node.js, Python, and npx-based servers |
transport-http | StreamableHTTPClientTransport for remote servers with OAuth and fallback patterns |
Handlers (Server-Initiated Requests)
| Name | Description |
|---|
sampling-handler | Handle sampling/createMessage requests with tool support (MCP 2025-11-25) |
elicitation-handler | Handle elicitation/create requests for user input (form and URL modes) |
roots-handler | Expose filesystem roots to servers with change notifications |
Features (Dynamic Discovery & Monitoring)
| Name | Description |
|---|
list-changed | React to dynamic tool/prompt/resource changes with listChanged handlers |
subscriptions | Subscribe to resource updates with reactive patterns |
logging | Receive server log messages with level filtering |
completions | Argument autocomplete for prompts and resources with interactive picker |
annotations | Filter, sort, and display content with audience, priority, and timestamp annotations |
tool-annotations | Interpret tool behavior hints for UI warnings and confirmations |
Capabilities
| Name | Description |
|---|
tasks | Task support for long-running tool operations with progress streaming |
tasks-receiver | Receiver-side task support for handling task-augmented requests (bidirectional tasks) |
Stores
| Name | Description |
|---|
persistent-task-store | File-based task store with persistence and TTL cleanup |
Quick Reference
Core Imports
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
Basic Client Pattern
const client = new Client(
{ name: "my-client", version: "1.0.0" },
{ capabilities: {} }
);
const transport = new StdioClientTransport({
command: "node",
args: ["path/to/server.js"],
});
await client.connect(transport);
const tools = await client.listTools();
const result = await client.callTool({ name: "tool-name", arguments: {} });
await client.close();
Multi-Server Pattern
import {
connectToAllServers,
aggregateTools,
callTool,
disconnectAll,
} from './multi-server.js';
const clients = await connectToAllServers({
"time": { url: "http://localhost:3001/mcp" },
"weather": { url: "http://localhost:3002/mcp" },
});
const tools = await aggregateTools(clients);
const result = await callTool(clients, "weather__get-forecast", { city: "Seattle" });
await disconnectAll(clients);
Server Instructions
MCP servers can provide instructions that describe how to use their tools effectively. These are returned during initialization and should be included in LLM system prompts:
const instructions = client.getInstructions();
if (instructions) {
console.log("Server instructions:", instructions);
}
The client-with-llm snippet automatically includes server instructions in the Claude system prompt. If you want to add a base system prompt along with server instructions:
mcpClient.setSystemPrompt("You are a helpful assistant with access to MCP tools.");
Related Skills
This skill focuses on building MCP clients with full protocol support. See also:
| Skill | Use When |
|---|
| mcp-server-ts | Building MCP servers that clients connect to |
| claude-agent-sdk-ts | Building Claude agents (limited MCP: tools + resources only) |
Why use mcp-client-ts instead of Claude Agent SDK?
- Need full MCP protocol (prompts, sampling, roots, logging, subscriptions, tasks)
- Building non-agent applications (IDEs, services, custom integrations)
- Need direct control over MCP client behavior
Reference Files
For deeper guidance, load these reference documents:
MCP Documentation Server
For up-to-date protocol details, use the MCP docs server:
{
"mcpServers": {
"mcp-docs": {
"type": "http",
"url": "https://modelcontextprotocol.io/mcp"
}
}
}
Query the docs server for:
- "Example clients" - includes examples of both open source and closed source MCP clients
- Client feature support
- Protocol specification updates
External Resources
Building More Capable Clients
Most MCP clients only implement basic features. To build a more capable client that stands out, implement these advanced capabilities:
Capability Declaration
Declare capabilities during client initialization to enable server-initiated requests:
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
capabilities: {
sampling: { tools: {} },
elicitation: {
form: {},
url: {}
},
roots: {
listChanged: true
},
tasks: {
list: {},
cancel: {},
requests: {
sampling: { createMessage: {} },
elicitation: { create: {} },
},
}
}
}
);
Server-Initiated Requests
Sampling - Servers can request LLM completions:
import { CreateMessageRequestSchema } from '@modelcontextprotocol/sdk/client/index.js';
client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
return { role: 'assistant', content: { type: 'text', text: response }, model: 'claude-...' };
});
Elicitation - Servers can request user input:
import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/client/index.js';
client.setRequestHandler(ElicitRequestSchema, async (request) => {
return { action: 'accept', content: { confirm: true } };
});
Dynamic Discovery
React to server capability changes in real-time:
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
capabilities: {},
listChanged: {
tools: {
autoRefresh: true,
onChanged: (err, tools) => {
console.log('Tools updated:', tools?.map(t => t.name));
}
}
}
}
);
Resource Subscriptions
Subscribe to resources for live updates:
const caps = client.getServerCapabilities();
if (caps?.resources?.subscribe) {
await client.subscribeResource({ uri: 'file:///data.json' });
client.setNotificationHandler(
{ method: 'notifications/resources/updated' },
async (notification) => {
const content = await client.readResource({ uri: notification.params.uri });
console.log('Resource updated:', content);
}
);
}
Task Support
Handle long-running tool operations with progress tracking:
import {
serverSupportsTasks,
callToolWithTaskSupport,
callToolAuto,
listTasks,
getTask,
cancelTask,
} from './capabilities/tasks.js';
if (serverSupportsTasks(client)) {
const result = await callToolWithTaskSupport(client, 'long-running-tool', { arg: 'value' }, {
onTaskCreated: (task) => console.log('Task started:', task.taskId),
onTaskStatusUpdate: (task) => console.log('Status:', task.status, task.statusMessage),
});
const tools = await client.listTools();
const tool = tools.tools.find(t => t.name === 'my-tool');
const autoResult = await callToolAuto(client, tool, { arg: 'value' });
const tasks = await listTasks(client);
const task = await getTask(client, 'task-id');
await cancelTask(client, 'task-id');
}
For persistent task storage across restarts:
import { createPersistentTaskStore } from './stores/persistent-task-store.js';
import { join } from 'node:path';
import { homedir } from 'node:os';
const taskStore = createPersistentTaskStore({
dataDir: join(homedir(), '.my-app', 'data'),
persistenceEnabled: true,
onLog: (msg) => console.log(msg),
});
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{ capabilities: { tasks: {} }, taskStore }
);
For handling task-augmented requests when the client is the receiver (bidirectional tasks):
import {
createReceiverTaskManager,
setupReceiverTaskHandlers,
getReceiverTaskCapabilities,
emitTaskStatusNotification,
} from './capabilities/tasks-receiver.js';
const taskManager = createReceiverTaskManager({
ttl: 300000,
onLog: (msg) => console.log(msg),
});
setupReceiverTaskHandlers(client, taskManager);
const taskCapabilities = getReceiverTaskCapabilities({
sampling: true,
elicitation: true,
});
client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
if (request.params.task) {
const record = taskManager.createTask({
ttl: request.params.task.ttl,
initialStatus: 'working',
statusMessage: 'Processing sampling request...',
});
return { task: record.task };
}
});
Use Case Examples
Beyond chatbots, capable MCP clients can power:
- IDE Integrations: Expose project roots, handle file operations, provide completions
- Workflow Automation: Orchestrate multi-tool workflows, handle confirmations via elicitation
- Monitoring Dashboards: Subscribe to resources for live data, receive server logs
- AI-Powered CLI Tools: Use sampling for intelligent command suggestions
See the MCP Reference Client for a complete implementation.