-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathserverGuide.examples.ts
More file actions
559 lines (511 loc) · 18.7 KB
/
serverGuide.examples.ts
File metadata and controls
559 lines (511 loc) · 18.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
/**
* Type-checked examples for docs/server.md.
*
* Regions are synced into markdown code fences via `pnpm sync:snippets`.
* Each function wraps a single region. The function name matches the region name.
*
* @module
*/
//#region imports
import { randomUUID } from 'node:crypto';
import { createMcpExpressApp } from '@modelcontextprotocol/express';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server';
import { completable, McpServer, ResourceTemplate, StdioServerTransport } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
//#endregion imports
// ---------------------------------------------------------------------------
// Server instructions
// ---------------------------------------------------------------------------
/** Example: McpServer with instructions for LLM guidance. */
function instructions_basic() {
//#region instructions_basic
const server = new McpServer(
{ name: 'db-server', version: '1.0.0' },
{
instructions:
'Always call list_tables before running queries. Use validate_schema before migrate_schema for safe migrations. Results are limited to 1000 rows.'
}
);
//#endregion instructions_basic
return server;
}
// ---------------------------------------------------------------------------
// Tools, resources, and prompts
// ---------------------------------------------------------------------------
/** Example: Registering a tool with inputSchema, outputSchema, and structuredContent. */
function registerTool_basic(server: McpServer) {
//#region registerTool_basic
server.registerTool(
'calculate-bmi',
{
title: 'BMI Calculator',
description: 'Calculate Body Mass Index',
inputSchema: z.object({
weightKg: z.number(),
heightM: z.number()
}),
outputSchema: z.object({ bmi: z.number() })
},
async ({ weightKg, heightM }) => {
const output = { bmi: weightKg / (heightM * heightM) };
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
}
);
//#endregion registerTool_basic
}
/** Example: Tool returning resource_link content items. */
function registerTool_resourceLink(server: McpServer) {
//#region registerTool_resourceLink
server.registerTool(
'list-files',
{
title: 'List Files',
description: 'Returns files as resource links without embedding content'
},
async (): Promise<CallToolResult> => {
const links: ResourceLink[] = [
{
type: 'resource_link',
uri: 'file:///projects/readme.md',
name: 'README',
mimeType: 'text/markdown'
},
{
type: 'resource_link',
uri: 'file:///projects/config.json',
name: 'Config',
mimeType: 'application/json'
}
];
return { content: links };
}
);
//#endregion registerTool_resourceLink
}
/** Example: Tool with explicit error handling using isError. */
function registerTool_errorHandling(server: McpServer) {
//#region registerTool_errorHandling
server.registerTool(
'fetch-data',
{
description: 'Fetch data from a URL',
inputSchema: z.object({ url: z.string() })
},
async ({ url }): Promise<CallToolResult> => {
try {
const res = await fetch(url);
if (!res.ok) {
return {
content: [{ type: 'text', text: `HTTP ${res.status}: ${res.statusText}` }],
isError: true
};
}
const text = await res.text();
return { content: [{ type: 'text', text }] };
} catch (error) {
return {
content: [{ type: 'text', text: `Failed: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
//#endregion registerTool_errorHandling
}
/** Example: Tool with annotations hinting at behavior. */
function registerTool_annotations(server: McpServer) {
//#region registerTool_annotations
server.registerTool(
'delete-file',
{
description: 'Delete a file from the project',
inputSchema: z.object({ path: z.string() }),
annotations: {
title: 'Delete File',
destructiveHint: true,
idempotentHint: true
}
},
async ({ path }): Promise<CallToolResult> => {
// ... perform deletion ...
return { content: [{ type: 'text', text: `Deleted ${path}` }] };
}
);
//#endregion registerTool_annotations
}
/** Example: Registering a static resource at a fixed URI. */
function registerResource_static(server: McpServer) {
//#region registerResource_static
server.registerResource(
'config',
'config://app',
{
title: 'Application Config',
description: 'Application configuration data',
mimeType: 'text/plain'
},
async uri => ({
contents: [{ uri: uri.href, text: 'App configuration here' }]
})
);
//#endregion registerResource_static
}
/** Example: Dynamic resource with ResourceTemplate and listing. */
function registerResource_template(server: McpServer) {
//#region registerResource_template
server.registerResource(
'user-profile',
new ResourceTemplate('user://{userId}/profile', {
list: async () => ({
resources: [
{ uri: 'user://123/profile', name: 'Alice' },
{ uri: 'user://456/profile', name: 'Bob' }
]
})
}),
{
title: 'User Profile',
description: 'User profile data',
mimeType: 'application/json'
},
async (uri, { userId }) => ({
contents: [
{
uri: uri.href,
text: JSON.stringify({ userId, name: 'Example User' })
}
]
})
);
//#endregion registerResource_template
}
/** Example: Registering a prompt with argsSchema. */
function registerPrompt_basic(server: McpServer) {
//#region registerPrompt_basic
server.registerPrompt(
'review-code',
{
title: 'Code Review',
description: 'Review code for best practices and potential issues',
argsSchema: z.object({
code: z.string()
})
},
({ code }) => ({
messages: [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Please review this code:\n\n${code}`
}
}
]
})
);
//#endregion registerPrompt_basic
}
/** Example: Prompt with completable argsSchema for autocompletion. */
function registerPrompt_completion(server: McpServer) {
//#region registerPrompt_completion
server.registerPrompt(
'review-code',
{
title: 'Code Review',
description: 'Review code for best practices',
argsSchema: z.object({
language: completable(z.string().describe('Programming language'), value =>
['typescript', 'javascript', 'python', 'rust', 'go'].filter(lang => lang.startsWith(value))
)
})
},
({ language }) => ({
messages: [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Review this ${language} code for best practices.`
}
}
]
})
);
//#endregion registerPrompt_completion
}
// ---------------------------------------------------------------------------
// Logging
// ---------------------------------------------------------------------------
/** Example: Server with logging capability + tool that logs progress messages. */
function registerTool_logging() {
//#region logging_capability
const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { logging: {} } });
//#endregion logging_capability
//#region registerTool_logging
server.registerTool(
'fetch-data',
{
description: 'Fetch data from an API',
inputSchema: z.object({ url: z.string() })
},
async ({ url }, ctx): Promise<CallToolResult> => {
await ctx.mcpReq.log('info', `Fetching ${url}`);
const res = await fetch(url);
await ctx.mcpReq.log('debug', `Response status: ${res.status}`);
const text = await res.text();
return { content: [{ type: 'text', text }] };
}
);
//#endregion registerTool_logging
return server;
}
// ---------------------------------------------------------------------------
// Progress
// ---------------------------------------------------------------------------
/** Example: Tool that sends progress notifications during a long-running operation. */
function registerTool_progress(server: McpServer) {
//#region registerTool_progress
server.registerTool(
'process-files',
{
description: 'Process files with progress updates',
inputSchema: z.object({ files: z.array(z.string()) })
},
async ({ files }, ctx): Promise<CallToolResult> => {
const progressToken = ctx.mcpReq._meta?.progressToken;
for (let i = 0; i < files.length; i++) {
// ... process files[i] ...
if (progressToken !== undefined) {
await ctx.mcpReq.notify({
method: 'notifications/progress',
params: {
progressToken,
progress: i + 1,
total: files.length,
message: `Processed ${files[i]}`
}
});
}
}
return { content: [{ type: 'text', text: `Processed ${files.length} files` }] };
}
);
//#endregion registerTool_progress
}
// ---------------------------------------------------------------------------
// Server-initiated requests
// ---------------------------------------------------------------------------
/** Example: Tool that uses sampling to request an LLM completion from the client. */
function registerTool_sampling(server: McpServer) {
//#region registerTool_sampling
server.registerTool(
'summarize',
{
description: 'Summarize text using the client LLM',
inputSchema: z.object({ text: z.string() })
},
async ({ text }, ctx): Promise<CallToolResult> => {
const response = await ctx.mcpReq.requestSampling({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please summarize:\n\n${text}`
}
}
],
maxTokens: 500
});
return {
content: [
{
type: 'text',
text: `Model (${response.model}): ${JSON.stringify(response.content)}`
}
]
};
}
);
//#endregion registerTool_sampling
}
/** Example: Tool that uses form elicitation to collect user input. */
function registerTool_elicitation(server: McpServer) {
//#region registerTool_elicitation
server.registerTool(
'collect-feedback',
{
description: 'Collect user feedback via a form',
inputSchema: z.object({})
},
async (_args, ctx): Promise<CallToolResult> => {
const result = await ctx.mcpReq.elicitInput({
mode: 'form',
message: 'Please share your feedback:',
requestedSchema: {
type: 'object',
properties: {
rating: {
type: 'number',
title: 'Rating (1\u20135)',
minimum: 1,
maximum: 5
},
comment: { type: 'string', title: 'Comment' }
},
required: ['rating']
}
});
if (result.action === 'accept') {
return {
content: [
{
type: 'text',
text: `Thanks! ${JSON.stringify(result.content)}`
}
]
};
}
return { content: [{ type: 'text', text: 'Feedback declined.' }] };
}
);
//#endregion registerTool_elicitation
}
/** Example: Tool that requests the client's filesystem roots. */
function registerTool_roots(server: McpServer) {
//#region registerTool_roots
server.registerTool(
'list-workspace-files',
{
description: 'List files across all workspace roots',
inputSchema: z.object({})
},
async (_args, _ctx): Promise<CallToolResult> => {
const { roots } = await server.server.listRoots();
const summary = roots.map(r => `${r.name ?? r.uri}: ${r.uri}`).join('\n');
return { content: [{ type: 'text', text: summary }] };
}
);
//#endregion registerTool_roots
}
// ---------------------------------------------------------------------------
// Transports
// ---------------------------------------------------------------------------
/** Example: Stateful Streamable HTTP transport with session management. */
async function streamableHttp_stateful() {
//#region streamableHttp_stateful
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID()
});
await server.connect(transport);
//#endregion streamableHttp_stateful
}
/** Example: Stateless Streamable HTTP transport (no session persistence). */
async function streamableHttp_stateless() {
//#region streamableHttp_stateless
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined
});
await server.connect(transport);
//#endregion streamableHttp_stateless
}
/** Example: Streamable HTTP with JSON response mode (no SSE). */
async function streamableHttp_jsonResponse() {
//#region streamableHttp_jsonResponse
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: true
});
await server.connect(transport);
//#endregion streamableHttp_jsonResponse
}
/** Example: stdio transport for local process-spawned integrations. */
async function stdio_basic() {
//#region stdio_basic
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
const transport = new StdioServerTransport();
await server.connect(transport);
//#endregion stdio_basic
}
// ---------------------------------------------------------------------------
// Shutdown
// ---------------------------------------------------------------------------
/** Example: Graceful shutdown for a stateful multi-session HTTP server. */
function shutdown_statefulHttp(app: ReturnType<typeof createMcpExpressApp>, transports: Map<string, NodeStreamableHTTPServerTransport>) {
//#region shutdown_statefulHttp
// Capture the http.Server so it can be closed on shutdown
const httpServer = app.listen(3000);
process.on('SIGINT', async () => {
httpServer.close();
for (const [sessionId, transport] of transports) {
await transport.close();
transports.delete(sessionId);
}
process.exit(0);
});
//#endregion shutdown_statefulHttp
}
/** Example: Graceful shutdown for a stdio server. */
function shutdown_stdio(server: McpServer) {
//#region shutdown_stdio
process.on('SIGINT', async () => {
await server.close();
process.exit(0);
});
//#endregion shutdown_stdio
}
// ---------------------------------------------------------------------------
// DNS rebinding protection
// ---------------------------------------------------------------------------
/** Example: createMcpExpressApp with different host bindings. */
function dnsRebinding_basic() {
//#region dnsRebinding_basic
// Default: DNS rebinding protection auto-enabled (host is 127.0.0.1)
const app = createMcpExpressApp();
// DNS rebinding protection also auto-enabled for localhost
const appLocal = createMcpExpressApp({ host: 'localhost' });
// No automatic protection when binding to all interfaces
const appOpen = createMcpExpressApp({ host: '0.0.0.0' });
//#endregion dnsRebinding_basic
return { app, appLocal, appOpen };
}
/** Example: createMcpExpressApp with allowedHosts for non-localhost binding. */
function dnsRebinding_allowedHosts() {
//#region dnsRebinding_allowedHosts
const app = createMcpExpressApp({
host: '0.0.0.0',
allowedHosts: ['localhost', '127.0.0.1', 'myhost.local']
});
//#endregion dnsRebinding_allowedHosts
return app;
}
// Suppress unused-function warnings (functions exist solely for type-checking)
void instructions_basic;
void registerTool_basic;
void registerTool_resourceLink;
void registerTool_errorHandling;
void registerTool_annotations;
void registerTool_logging;
void registerTool_progress;
void registerTool_sampling;
void registerTool_elicitation;
void registerTool_roots;
void registerResource_static;
void registerResource_template;
void registerPrompt_basic;
void registerPrompt_completion;
void streamableHttp_stateful;
void streamableHttp_stateless;
void streamableHttp_jsonResponse;
void stdio_basic;
void shutdown_statefulHttp;
void shutdown_stdio;
void dnsRebinding_basic;
void dnsRebinding_allowedHosts;