TypeScript SDK for implementing the Model Context Protocol, enabling developers to build MCP servers and clients with support for multiple transports, tools, resources, prompts, and authentication
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Advanced capabilities: sampling (LLM completions), elicitation (user input), dynamic capability management, and low-level protocol access.
Servers can request LLM completions from clients, enabling tools to use AI capabilities.
async createMessage(
params: {
messages: SamplingMessage[]; // Messages for the LLM
maxTokens?: number; // Maximum tokens to generate
modelPreferences?: ModelPreferences; // Model preferences
systemPrompt?: string; // System prompt
includeContext?: 'none' | 'thisServer' | 'allServers';
temperature?: number; // Temperature (0-1)
stopSequences?: string[]; // Stop sequences
metadata?: Record<string, unknown>; // Request metadata
},
options?: RequestOptions
): Promise<CreateMessageResult>;
interface SamplingMessage {
role: 'user' | 'assistant';
content: TextContent | ImageContent | AudioContent;
}
interface ModelPreferences {
hints?: ModelHint[]; // Prioritized model hints
costPriority?: number; // 0-1, higher = prefer cheaper
speedPriority?: number; // 0-1, higher = prefer faster
intelligencePriority?: number; // 0-1, higher = prefer smarter
}
interface CreateMessageResult {
role: 'assistant';
content: TextContent | ImageContent | AudioContent;
model: string;
stopReason?: 'endTurn' | 'stopSequence' | 'maxTokens';
_meta?: Record<string, unknown>;
}import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
const server = new McpServer({ name: 'sampling-server', version: '1.0.0' });
server.registerTool('summarize', {
title: 'Summarize Text',
description: 'Summarize text using an LLM',
inputSchema: { text: z.string().describe('Text to summarize') },
outputSchema: { summary: z.string() }
}, async ({ text }) => {
const response = await server.server.createMessage({
messages: [{
role: 'user',
content: { type: 'text', text: `Please summarize the following text concisely:\n\n${text}` }
}],
maxTokens: 500,
temperature: 0.7
});
const summary = response.content.type === 'text' ? response.content.text : 'Unable to generate summary';
const output = { summary };
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
});Servers can request structured input from users through forms.
async elicitInput(
params: {
message: string; // Message to display to user
requestedSchema: JSONSchema; // JSON Schema describing requested input
},
options?: RequestOptions
): Promise<ElicitResult>;
interface ElicitResult {
action: 'accept' | 'decline' | 'cancel';
content?: Record<string, unknown>; // User-provided content if action is 'accept'
_meta?: Record<string, unknown>;
}// User confirmation
server.registerTool('delete-file', {
title: 'Delete File',
description: 'Delete a file with user confirmation',
inputSchema: { filePath: z.string() },
outputSchema: { deleted: z.boolean() }
}, async ({ filePath }) => {
const result = await server.server.elicitInput({
message: `Are you sure you want to delete ${filePath}?`,
requestedSchema: {
type: 'object',
properties: {
confirm: { type: 'boolean', title: 'Confirm deletion', description: 'Check to confirm file deletion' }
},
required: ['confirm']
}
});
let output;
if (result.action === 'accept' && result.content?.confirm) {
await fs.unlink(filePath);
output = { deleted: true };
} else {
output = { deleted: false };
}
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
});
// Restaurant booking with alternatives
server.registerTool('book-restaurant', {
title: 'Book Restaurant',
description: 'Book a table at a restaurant',
inputSchema: { restaurant: z.string(), date: z.string(), partySize: z.number() }
}, async ({ restaurant, date, partySize }) => {
const available = await checkAvailability(restaurant, date, partySize);
if (!available) {
const result = await server.server.elicitInput({
message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`,
requestedSchema: {
type: 'object',
properties: {
checkAlternatives: { type: 'boolean', title: 'Check alternative dates' },
flexibleDates: {
type: 'string',
title: 'Date flexibility',
enum: ['next_day', 'same_week', 'next_week'],
enumNames: ['Next day', 'Same week', 'Next week']
}
},
required: ['checkAlternatives']
}
});
if (result.action === 'accept' && result.content?.checkAlternatives) {
const alternatives = await findAlternatives(restaurant, date, partySize, result.content.flexibleDates);
return {
content: [{ type: 'text', text: `Alternative dates: ${alternatives.join(', ')}` }],
structuredContent: { alternatives }
};
}
}
await makeBooking(restaurant, date, partySize);
return {
content: [{ type: 'text', text: 'Booking confirmed' }],
structuredContent: { success: true }
};
});Tools, resources, and prompts can be controlled at runtime:
interface RegisteredTool {
enable(): void; // Make visible in listTools
disable(): void; // Hide from listTools
update(config: Partial<ToolConfig>): void; // Update configuration
remove(): void; // Remove entirely
}// Register tools with different access levels
const readTool = server.registerTool('read-data', readConfig, readCallback);
const writeTool = server.registerTool('write-data', writeConfig, writeCallback);
const adminTool = server.registerTool('admin-action', adminConfig, adminCallback);
// Initially, only read access
writeTool.disable();
adminTool.disable();
// After authentication, enable based on role
function updatePermissions(userRole: string) {
if (userRole === 'editor') {
writeTool.enable();
} else if (userRole === 'admin') {
writeTool.enable();
adminTool.enable();
}
server.sendToolListChanged();
}
// Dynamic tool update
readTool.update({ description: `Read data (authenticated as ${username})` });Reduce network traffic by coalescing rapid notifications:
const server = new McpServer(
{ name: 'efficient-server', version: '1.0.0' },
{
debouncedNotificationMethods: [
'notifications/tools/list_changed',
'notifications/resources/list_changed',
'notifications/prompts/list_changed'
]
}
);
// Bulk updates send only one notification per type
for (let i = 0; i < 100; i++) {
server.registerTool(`tool${i}`, config, callback).disable();
}
// Only one notifications/tools/list_changed sentconst mcpServer = new McpServer({ name: 'my-server', version: '1.0.0' });
const lowLevelServer = mcpServer.server; // Access underlying Server instance
// Set custom request handlers
lowLevelServer.setRequestHandler(CustomRequestSchema, async (request, extra) => {
return customResult;
});
// Set custom notification handlers
lowLevelServer.setNotificationHandler(CustomNotificationSchema, async (notification) => {
// Handle notification
});
// Send custom requests to client
const result = await lowLevelServer.request({ method: 'custom/method', params: {} }, CustomResultSchema);
// Send custom notifications to client
await lowLevelServer.notification({ method: 'custom/notification', params: {} });import { Protocol, ProtocolOptions, RequestOptions, NotificationOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
abstract class Protocol<RequestT, NotificationT, ResultT> {
protected transport?: Transport;
setRequestHandler<T extends ZodType<object>>(schema: T, handler: (request: z.infer<T>, extra: RequestHandlerExtra) => Promise<Result> | Result): void;
setNotificationHandler<T extends ZodType<object>>(schema: T, handler: (notification: z.infer<T>) => Promise<void> | void): void;
request<U extends ZodType<object>>(request: RequestT, resultSchema: U, options?: RequestOptions): Promise<z.infer<U>>;
notification(notification: NotificationT, options?: NotificationOptions): Promise<void>;
close(): Promise<void>;
connect(transport: Transport): Promise<void>;
}
interface ProtocolOptions {
enforceStrictCapabilities?: boolean; // Default: true
debouncedNotificationMethods?: string[];
}server.getClientCapabilities(): ClientCapabilities | undefined;
server.getClientVersion(): Implementation | undefined;async sendLoggingMessage(
params: {
level: LoggingLevel;
logger?: string;
data: unknown;
},
sessionId?: string
): Promise<void>;
type LoggingLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency';await server.sendLoggingMessage({
level: 'info',
logger: 'my-tool',
data: 'Operation completed successfully'
});
await server.sendLoggingMessage({
level: 'error',
logger: 'database',
data: { error: 'Connection failed', code: 'ECONNREFUSED' }
});async listRoots(params?: Record<string, unknown>, options?: RequestOptions): Promise<ListRootsResult>;
interface ListRootsResult {
roots: Root[];
_meta?: Record<string, unknown>;
}
interface Root {
uri: string;
name: string;
}isConnected(): boolean;
async close(): Promise<void>;server.server.oninitialized = () => { console.log('Client initialized'); };
server.server.onclose = () => { console.log('Connection closed'); };
server.server.onerror = (error) => { console.error('Server error:', error); };interface RequestHandlerExtra {
request: <U>(request: Request, resultSchema: U, options?: RequestOptions) => Promise<Result>;
notification: (notification: Notification, options?: NotificationOptions) => Promise<void>;
sessionId?: string;
requestInfo?: RequestInfo;
signal: AbortSignal;
}
interface RequestOptions {
timeout?: number;
onprogress?: (progress: Progress) => void;
signal?: AbortSignal;
}
interface NotificationOptions {
priority?: number;
}