tmcp 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +409 -23
- package/package.json +2 -1
- package/src/index.d.ts +52 -14
- package/src/index.js +401 -63
- package/src/internal/internal.d.ts +21 -8
- package/src/validation/index.d.ts +4083 -466
- package/src/validation/index.js +1396 -135
- package/src/validation/version.d.ts +85 -0
- package/src/validation/version.js +197 -0
package/src/index.js
CHANGED
|
@@ -1,21 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
* @import SqidsType from "sqids";
|
|
3
4
|
* @import { JSONRPCRequest, JSONRPCParams } from "json-rpc-2.0";
|
|
4
5
|
* @import { ExtractURITemplateVariables } from "./internal/uri-template.js";
|
|
5
|
-
* @import { CallToolResult, ReadResourceResult, GetPromptResult, ClientCapabilities as ClientCapabilitiesType } from "./validation/index.js";
|
|
6
|
-
* @import { Tool, Completion, Prompt,
|
|
6
|
+
* @import { CallToolResult, ReadResourceResult, GetPromptResult, ClientCapabilities as ClientCapabilitiesType, JSONRPCRequest as JSONRPCRequestType, JSONRPCResponse, CreateMessageRequestParams, CreateMessageResult, Resource, LoggingLevel } from "./validation/index.js";
|
|
7
|
+
* @import { Tool, Completion, Prompt, StoredResource, ServerOptions, ServerInfo, SubscriptionsKeys, McpEvents } from "./internal/internal.js";
|
|
7
8
|
*/
|
|
8
|
-
import {
|
|
9
|
+
import { JSONRPCClient, JSONRPCServer } from 'json-rpc-2.0';
|
|
10
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
11
|
import { UriTemplateMatcher } from 'uri-template-matcher';
|
|
12
|
+
import * as v from 'valibot';
|
|
10
13
|
import {
|
|
11
14
|
CallToolResultSchema,
|
|
12
|
-
ReadResourceResultSchema,
|
|
13
|
-
GetPromptResultSchema,
|
|
14
15
|
CompleteResultSchema,
|
|
15
|
-
|
|
16
|
+
CreateMessageRequestParamsSchema,
|
|
17
|
+
CreateMessageResultSchema,
|
|
18
|
+
GetPromptResultSchema,
|
|
19
|
+
InitializeRequestParamsSchema,
|
|
20
|
+
JSONRPCRequestSchema,
|
|
21
|
+
JSONRPCResponseSchema,
|
|
22
|
+
McpError,
|
|
23
|
+
ReadResourceResultSchema,
|
|
16
24
|
} from './validation/index.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
25
|
+
import {
|
|
26
|
+
get_supported_versions,
|
|
27
|
+
negotiate_protocol_version,
|
|
28
|
+
} from './validation/version.js';
|
|
29
|
+
import { should_version_negotiation_fail } from './validation/version.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @type {SqidsType | undefined}
|
|
33
|
+
*/
|
|
34
|
+
let Sqids;
|
|
35
|
+
|
|
36
|
+
async function get_sqids() {
|
|
37
|
+
if (!Sqids) {
|
|
38
|
+
Sqids = new (await import('sqids')).default();
|
|
39
|
+
}
|
|
40
|
+
return Sqids;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Encode a cursor for pagination
|
|
45
|
+
* @param {number} offset
|
|
46
|
+
*/
|
|
47
|
+
async function encode_cursor(offset) {
|
|
48
|
+
return (await get_sqids()).encode([offset]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Decode a cursor from pagination
|
|
53
|
+
* @param {string} cursor
|
|
54
|
+
*/
|
|
55
|
+
async function decode_cursor(cursor) {
|
|
56
|
+
const [decoded] = (await get_sqids()).decode(cursor);
|
|
57
|
+
return decoded;
|
|
58
|
+
}
|
|
19
59
|
|
|
20
60
|
/**
|
|
21
61
|
* @typedef {ClientCapabilitiesType} ClientCapabilities
|
|
@@ -40,10 +80,14 @@ export class McpServer {
|
|
|
40
80
|
*/
|
|
41
81
|
#prompts = new Map();
|
|
42
82
|
/**
|
|
43
|
-
* @type {Map<string,
|
|
83
|
+
* @type {Map<string, StoredResource>}
|
|
44
84
|
*/
|
|
45
85
|
#resources = new Map();
|
|
46
86
|
#templates = new UriTemplateMatcher();
|
|
87
|
+
/**
|
|
88
|
+
* @type {Array<{uri: string, name?: string}>}
|
|
89
|
+
*/
|
|
90
|
+
roots = [];
|
|
47
91
|
/**
|
|
48
92
|
* @type {{ [ref: string]: Map<string, Partial<Record<string, Completion>>> }}
|
|
49
93
|
*/
|
|
@@ -74,6 +118,16 @@ export class McpServer {
|
|
|
74
118
|
*/
|
|
75
119
|
#client_capabilities_map = new Map();
|
|
76
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @type {Map<string|undefined, string>}
|
|
123
|
+
*/
|
|
124
|
+
#negotiated_protocol_versions = new Map();
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @type {Map<string|undefined, LoggingLevel>}
|
|
128
|
+
*/
|
|
129
|
+
#session_log_levels = new Map();
|
|
130
|
+
|
|
77
131
|
/**
|
|
78
132
|
* @param {ServerInfo} server_info
|
|
79
133
|
* @param {ServerOptions<StandardSchema>} options
|
|
@@ -81,24 +135,90 @@ export class McpServer {
|
|
|
81
135
|
constructor(server_info, options) {
|
|
82
136
|
this.#options = options;
|
|
83
137
|
this.#server.addMethod('initialize', (initialize_request) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
138
|
+
try {
|
|
139
|
+
// Validate basic request format
|
|
140
|
+
const validated_initialize = v.parse(
|
|
141
|
+
InitializeRequestParamsSchema,
|
|
142
|
+
initialize_request,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const session_id = this.#session_id;
|
|
146
|
+
|
|
147
|
+
// Validate protocol version format
|
|
148
|
+
if (
|
|
149
|
+
should_version_negotiation_fail(
|
|
150
|
+
validated_initialize.protocolVersion,
|
|
151
|
+
)
|
|
152
|
+
) {
|
|
153
|
+
// Return JSON-RPC error for invalid protocol version format
|
|
154
|
+
const error = new McpError(
|
|
155
|
+
-32602,
|
|
156
|
+
'Invalid protocol version format',
|
|
157
|
+
);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Negotiate protocol version
|
|
162
|
+
const negotiated_version = negotiate_protocol_version(
|
|
163
|
+
validated_initialize.protocolVersion,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Store negotiated protocol version
|
|
167
|
+
this.#negotiated_protocol_versions.set(
|
|
168
|
+
session_id,
|
|
169
|
+
negotiated_version,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Store client capabilities
|
|
173
|
+
this.#client_capabilities_map.set(
|
|
174
|
+
session_id,
|
|
175
|
+
validated_initialize.capabilities,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Dispatch initialization event
|
|
179
|
+
this.#event_target.dispatchEvent(
|
|
180
|
+
new CustomEvent('initialize', {
|
|
181
|
+
detail: validated_initialize,
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Trigger initial roots request if client supports it
|
|
186
|
+
if (validated_initialize.capabilities?.roots) {
|
|
187
|
+
this.#refresh_roots();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Return server response with negotiated version and capabilities
|
|
191
|
+
return {
|
|
192
|
+
protocolVersion: negotiated_version,
|
|
193
|
+
...options,
|
|
194
|
+
serverInfo: server_info,
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
// Enhanced error handling for initialization failures
|
|
198
|
+
if (error instanceof McpError) {
|
|
199
|
+
// Already has JSON-RPC error code, re-throw
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (
|
|
204
|
+
/** @type {Error} */ (error).message?.includes(
|
|
205
|
+
'Protocol version',
|
|
206
|
+
)
|
|
207
|
+
) {
|
|
208
|
+
const rpc_error = new McpError(
|
|
209
|
+
-32602,
|
|
210
|
+
`Protocol version validation failed: ${/** @type {Error} */ (error).message}. Server supports: ${get_supported_versions().join(', ')}`,
|
|
211
|
+
);
|
|
212
|
+
throw rpc_error;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// General initialization error
|
|
216
|
+
const rpc_error = new McpError(
|
|
217
|
+
-32603,
|
|
218
|
+
`Initialization failed: ${/** @type {Error} */ (error).message}`,
|
|
219
|
+
);
|
|
220
|
+
throw rpc_error;
|
|
221
|
+
}
|
|
102
222
|
});
|
|
103
223
|
this.#server.addMethod('ping', () => {
|
|
104
224
|
return {};
|
|
@@ -109,7 +229,9 @@ export class McpServer {
|
|
|
109
229
|
this.#init_tools();
|
|
110
230
|
this.#init_prompts();
|
|
111
231
|
this.#init_resources();
|
|
232
|
+
this.#init_roots();
|
|
112
233
|
this.#init_completion();
|
|
234
|
+
this.#init_logging();
|
|
113
235
|
}
|
|
114
236
|
|
|
115
237
|
get #session_id() {
|
|
@@ -175,7 +297,7 @@ export class McpServer {
|
|
|
175
297
|
[...this.#tools].map(async ([name, tool]) => {
|
|
176
298
|
return {
|
|
177
299
|
name,
|
|
178
|
-
title: tool.description,
|
|
300
|
+
title: tool.title || tool.description,
|
|
179
301
|
description: tool.description,
|
|
180
302
|
inputSchema: tool.schema
|
|
181
303
|
? await this.#options.adapter.toJsonSchema(
|
|
@@ -219,8 +341,8 @@ export class McpServer {
|
|
|
219
341
|
*/
|
|
220
342
|
#init_prompts() {
|
|
221
343
|
if (!this.#options.capabilities?.prompts) return;
|
|
222
|
-
this.#server.addMethod('prompts/list', async () => {
|
|
223
|
-
const
|
|
344
|
+
this.#server.addMethod('prompts/list', async ({ cursor } = {}) => {
|
|
345
|
+
const all_prompts = await Promise.all(
|
|
224
346
|
[...this.#prompts].map(async ([name, prompt]) => {
|
|
225
347
|
const arguments_schema = prompt.schema
|
|
226
348
|
? await this.#options.adapter.toJsonSchema(
|
|
@@ -236,7 +358,7 @@ export class McpServer {
|
|
|
236
358
|
const required = arguments_schema.required ?? [];
|
|
237
359
|
return {
|
|
238
360
|
name,
|
|
239
|
-
title: prompt.description,
|
|
361
|
+
title: prompt.title || prompt.description,
|
|
240
362
|
description: prompt.description,
|
|
241
363
|
arguments: keys.map((key) => {
|
|
242
364
|
const property = arguments_schema.properties?.[key];
|
|
@@ -253,8 +375,26 @@ export class McpServer {
|
|
|
253
375
|
};
|
|
254
376
|
}),
|
|
255
377
|
);
|
|
378
|
+
|
|
379
|
+
const pagination_options = this.#options.pagination?.prompts;
|
|
380
|
+
if (!pagination_options || pagination_options.size == null) {
|
|
381
|
+
return { prompts: all_prompts };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const page_length = pagination_options.size;
|
|
385
|
+
const offset = cursor ? await decode_cursor(cursor) : 0;
|
|
386
|
+
const start_index = offset;
|
|
387
|
+
const end_index = start_index + page_length;
|
|
388
|
+
|
|
389
|
+
const prompts = all_prompts.slice(start_index, end_index);
|
|
390
|
+
const has_next = end_index < all_prompts.length;
|
|
391
|
+
const next_cursor = has_next
|
|
392
|
+
? await encode_cursor(end_index)
|
|
393
|
+
: null;
|
|
394
|
+
|
|
256
395
|
return {
|
|
257
|
-
prompts
|
|
396
|
+
prompts,
|
|
397
|
+
...(next_cursor && { nextCursor: next_cursor }),
|
|
258
398
|
};
|
|
259
399
|
});
|
|
260
400
|
this.#server.addMethod(
|
|
@@ -303,37 +443,61 @@ export class McpServer {
|
|
|
303
443
|
});
|
|
304
444
|
}
|
|
305
445
|
|
|
306
|
-
this.#server.addMethod('resources/list', async () => {
|
|
446
|
+
this.#server.addMethod('resources/list', async ({ cursor } = {}) => {
|
|
447
|
+
const all_resources = [];
|
|
448
|
+
|
|
449
|
+
// Add static resources
|
|
450
|
+
for (const [uri, { description, name, title, ...resource }] of this
|
|
451
|
+
.#resources) {
|
|
452
|
+
if (!resource.template) {
|
|
453
|
+
all_resources.push({
|
|
454
|
+
name,
|
|
455
|
+
title: title || description,
|
|
456
|
+
description,
|
|
457
|
+
uri,
|
|
458
|
+
});
|
|
459
|
+
} else if (resource.list_resources) {
|
|
460
|
+
const template_resources = await resource.list_resources();
|
|
461
|
+
all_resources.push(...template_resources);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const pagination_options = this.#options.pagination?.resources;
|
|
466
|
+
if (!pagination_options || pagination_options.size == null) {
|
|
467
|
+
return { resources: all_resources };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const page_length = pagination_options.size;
|
|
471
|
+
const offset = cursor ? await decode_cursor(cursor) : 0;
|
|
472
|
+
const start_index = offset;
|
|
473
|
+
const end_index = start_index + page_length;
|
|
474
|
+
|
|
475
|
+
const resources = all_resources.slice(start_index, end_index);
|
|
476
|
+
const has_next = end_index < all_resources.length;
|
|
477
|
+
const next_cursor = has_next
|
|
478
|
+
? await encode_cursor(end_index)
|
|
479
|
+
: null;
|
|
480
|
+
|
|
307
481
|
return {
|
|
308
|
-
resources
|
|
309
|
-
|
|
310
|
-
if (!template) {
|
|
311
|
-
arr.push({
|
|
312
|
-
name,
|
|
313
|
-
description,
|
|
314
|
-
uri,
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
return arr;
|
|
318
|
-
},
|
|
319
|
-
/** @type {Array<{name: string, description: string, uri: string}>} */ ([]),
|
|
320
|
-
),
|
|
482
|
+
resources,
|
|
483
|
+
...(next_cursor && { nextCursor: next_cursor }),
|
|
321
484
|
};
|
|
322
485
|
});
|
|
323
486
|
this.#server.addMethod('resources/templates/list', async () => {
|
|
324
487
|
return {
|
|
325
488
|
resourceTemplates: [...this.#resources].reduce(
|
|
326
|
-
(arr, [uri, { description, name, template }]) => {
|
|
489
|
+
(arr, [uri, { description, name, title, template }]) => {
|
|
327
490
|
if (template) {
|
|
328
491
|
arr.push({
|
|
329
492
|
name,
|
|
493
|
+
title: title || description,
|
|
330
494
|
description,
|
|
331
495
|
uriTemplate: uri,
|
|
332
496
|
});
|
|
333
497
|
}
|
|
334
498
|
return arr;
|
|
335
499
|
},
|
|
336
|
-
/** @type {Array<{name: string, description: string, uriTemplate: string}>} */ ([]),
|
|
500
|
+
/** @type {Array<{name: string, title: string, description: string, uriTemplate: string}>} */ ([]),
|
|
337
501
|
),
|
|
338
502
|
};
|
|
339
503
|
});
|
|
@@ -364,6 +528,37 @@ export class McpServer {
|
|
|
364
528
|
);
|
|
365
529
|
});
|
|
366
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
*
|
|
533
|
+
*/
|
|
534
|
+
#init_roots() {
|
|
535
|
+
this.#server.addMethod('notifications/roots/list_changed', () => {
|
|
536
|
+
this.#refresh_roots();
|
|
537
|
+
return null;
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Request roots list from client
|
|
543
|
+
*/
|
|
544
|
+
async #refresh_roots() {
|
|
545
|
+
if (!this.#client_capabilities?.roots) return;
|
|
546
|
+
|
|
547
|
+
this.#lazyily_create_client();
|
|
548
|
+
try {
|
|
549
|
+
const response = await this.#client?.request(
|
|
550
|
+
'roots/list',
|
|
551
|
+
undefined,
|
|
552
|
+
{
|
|
553
|
+
sessions: [this.#session_id],
|
|
554
|
+
},
|
|
555
|
+
);
|
|
556
|
+
this.roots = response?.roots || [];
|
|
557
|
+
} catch {
|
|
558
|
+
// Client doesn't support roots or request failed
|
|
559
|
+
this.roots = [];
|
|
560
|
+
}
|
|
561
|
+
}
|
|
367
562
|
/**
|
|
368
563
|
*
|
|
369
564
|
*/
|
|
@@ -384,27 +579,39 @@ export class McpServer {
|
|
|
384
579
|
},
|
|
385
580
|
);
|
|
386
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
*
|
|
584
|
+
*/
|
|
585
|
+
#init_logging() {
|
|
586
|
+
if (!this.#options.capabilities?.logging) return;
|
|
587
|
+
|
|
588
|
+
this.#server.addMethod('logging/setLevel', ({ level }) => {
|
|
589
|
+
this.#session_log_levels.set(this.#session_id, level);
|
|
590
|
+
return {};
|
|
591
|
+
});
|
|
592
|
+
}
|
|
387
593
|
/**
|
|
388
594
|
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
389
|
-
* @param {{ name: string; description: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never }} options
|
|
595
|
+
* @param {{ name: string; description: string; title?: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never }} options
|
|
390
596
|
* @param {TSchema extends undefined ? (()=>Promise<CallToolResult> | CallToolResult) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<CallToolResult> | CallToolResult)} execute
|
|
391
597
|
*/
|
|
392
|
-
tool({ name, description, schema }, execute) {
|
|
598
|
+
tool({ name, description, title, schema }, execute) {
|
|
393
599
|
if (this.#options.capabilities?.tools?.listChanged) {
|
|
394
600
|
this.#notify('notifications/tools/list_changed', {});
|
|
395
601
|
}
|
|
396
602
|
this.#tools.set(name, {
|
|
397
603
|
description,
|
|
604
|
+
title,
|
|
398
605
|
schema,
|
|
399
606
|
execute,
|
|
400
607
|
});
|
|
401
608
|
}
|
|
402
609
|
/**
|
|
403
610
|
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
404
|
-
* @param {{ name: string; description: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never; complete?: NoInfer<TSchema extends undefined ? never : Partial<Record<keyof (StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>), Completion>>> }} options
|
|
611
|
+
* @param {{ name: string; description: string; title?: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never; complete?: NoInfer<TSchema extends undefined ? never : Partial<Record<keyof (StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>), Completion>>> }} options
|
|
405
612
|
* @param {TSchema extends undefined ? (()=>Promise<GetPromptResult> | GetPromptResult) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<GetPromptResult> | GetPromptResult} execute
|
|
406
613
|
*/
|
|
407
|
-
prompt({ name, description, schema, complete }, execute) {
|
|
614
|
+
prompt({ name, description, title, schema, complete }, execute) {
|
|
408
615
|
if (complete) {
|
|
409
616
|
this.#completions['ref/prompt'].set(name, complete);
|
|
410
617
|
}
|
|
@@ -413,12 +620,13 @@ export class McpServer {
|
|
|
413
620
|
}
|
|
414
621
|
this.#prompts.set(name, {
|
|
415
622
|
description,
|
|
623
|
+
title,
|
|
416
624
|
schema,
|
|
417
625
|
execute,
|
|
418
626
|
});
|
|
419
627
|
}
|
|
420
628
|
/**
|
|
421
|
-
* @type {(resource:
|
|
629
|
+
* @type {(resource: StoredResource & {complete?: Partial<Record<string,Completion>>, uri: string})=> void}
|
|
422
630
|
*/
|
|
423
631
|
#resource({ uri, complete, ...resource }) {
|
|
424
632
|
if (complete) {
|
|
@@ -433,13 +641,14 @@ export class McpServer {
|
|
|
433
641
|
this.#resources.set(uri, resource);
|
|
434
642
|
}
|
|
435
643
|
/**
|
|
436
|
-
* @param {{ name: string; description: string; uri: string }} options
|
|
644
|
+
* @param {{ name: string; description: string; title?: string; uri: string }} options
|
|
437
645
|
* @param {(uri: string) => Promise<ReadResourceResult> | ReadResourceResult} execute
|
|
438
646
|
*/
|
|
439
|
-
resource({ name, description, uri }, execute) {
|
|
647
|
+
resource({ name, description, title, uri }, execute) {
|
|
440
648
|
this.#resource({
|
|
441
649
|
name,
|
|
442
650
|
description,
|
|
651
|
+
title,
|
|
443
652
|
uri,
|
|
444
653
|
execute,
|
|
445
654
|
template: false,
|
|
@@ -448,28 +657,58 @@ export class McpServer {
|
|
|
448
657
|
/**
|
|
449
658
|
* @template {string} TUri
|
|
450
659
|
* @template {ExtractURITemplateVariables<TUri>} TVariables
|
|
451
|
-
* @param {{ name: string; description: string; uri: TUri; complete?: NoInfer<TVariables extends never ? never : Partial<Record<TVariables, Completion
|
|
660
|
+
* @param {{ name: string; description: string; title?: string; uri: TUri; complete?: NoInfer<TVariables extends never ? never : Partial<Record<TVariables, Completion>>>; list?: () => Promise<Array<Resource>> | Array<Resource> }} options
|
|
452
661
|
* @param {(uri: string, params: Record<TVariables, string | string[]>) => Promise<ReadResourceResult> | ReadResourceResult} execute
|
|
453
662
|
*/
|
|
454
|
-
template(
|
|
663
|
+
template(
|
|
664
|
+
{ name, description, title, uri, complete, list: list_resources },
|
|
665
|
+
execute,
|
|
666
|
+
) {
|
|
455
667
|
this.#resource({
|
|
456
668
|
name,
|
|
457
669
|
description,
|
|
670
|
+
title,
|
|
458
671
|
uri,
|
|
459
672
|
execute,
|
|
460
673
|
complete,
|
|
674
|
+
list_resources,
|
|
461
675
|
template: true,
|
|
462
676
|
});
|
|
463
677
|
}
|
|
464
678
|
/**
|
|
465
|
-
* @param {JSONRPCRequest}
|
|
679
|
+
* @param {JSONRPCResponse | JSONRPCRequest} message
|
|
466
680
|
* @param {string} [session_id]
|
|
467
|
-
* @returns {ReturnType<JSONRPCServer['receive']>}
|
|
681
|
+
* @returns {ReturnType<JSONRPCServer['receive']> | ReturnType<JSONRPCClient['receive'] | undefined>}
|
|
468
682
|
*/
|
|
469
|
-
receive(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
683
|
+
receive(message, session_id) {
|
|
684
|
+
// Validate the message first
|
|
685
|
+
const validated_message = v.safeParse(JSONRPCRequestSchema, message);
|
|
686
|
+
|
|
687
|
+
// Check if it's a request or response
|
|
688
|
+
if (validated_message.success) {
|
|
689
|
+
// we want to set a default log level for each session
|
|
690
|
+
// so that if the user calls `log` it can send a notification
|
|
691
|
+
// to each client that didn't explicitly set a log level too
|
|
692
|
+
if (
|
|
693
|
+
!!this.#options.capabilities?.logging &&
|
|
694
|
+
!this.#session_log_levels.has(session_id)
|
|
695
|
+
) {
|
|
696
|
+
this.#session_log_levels.set(
|
|
697
|
+
session_id,
|
|
698
|
+
this.#options.logging?.default ?? 'info',
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
return this.#session_storage.run(
|
|
702
|
+
session_id,
|
|
703
|
+
async () =>
|
|
704
|
+
await this.#server.receive(validated_message.output),
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
// It's a response - handle with client
|
|
708
|
+
const validated_response = v.parse(JSONRPCResponseSchema, message);
|
|
709
|
+
this.#lazyily_create_client();
|
|
710
|
+
return this.#session_storage.run(session_id, async () =>
|
|
711
|
+
this.#client?.receive(validated_response),
|
|
473
712
|
);
|
|
474
713
|
}
|
|
475
714
|
|
|
@@ -496,6 +735,13 @@ export class McpServer {
|
|
|
496
735
|
}
|
|
497
736
|
}
|
|
498
737
|
|
|
738
|
+
/**
|
|
739
|
+
* Refresh roots list from client
|
|
740
|
+
*/
|
|
741
|
+
async refreshRoots() {
|
|
742
|
+
await this.#refresh_roots();
|
|
743
|
+
}
|
|
744
|
+
|
|
499
745
|
/**
|
|
500
746
|
* @template {StandardSchema} TSchema
|
|
501
747
|
* @param {TSchema} schema
|
|
@@ -526,4 +772,96 @@ export class McpServer {
|
|
|
526
772
|
}
|
|
527
773
|
return validated_result.value;
|
|
528
774
|
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Request language model sampling from the client
|
|
778
|
+
* @param {CreateMessageRequestParams} request
|
|
779
|
+
* @returns {Promise<CreateMessageResult>}
|
|
780
|
+
*/
|
|
781
|
+
async message(request) {
|
|
782
|
+
if (!this.#client_capabilities?.sampling)
|
|
783
|
+
throw new Error("Client doesn't support sampling");
|
|
784
|
+
|
|
785
|
+
this.#lazyily_create_client();
|
|
786
|
+
|
|
787
|
+
// Validate the request
|
|
788
|
+
const validated_request = v.parse(
|
|
789
|
+
CreateMessageRequestParamsSchema,
|
|
790
|
+
request,
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
// Make the request to the client
|
|
794
|
+
const response = await this.#client?.request(
|
|
795
|
+
'sampling/createMessage',
|
|
796
|
+
validated_request,
|
|
797
|
+
{
|
|
798
|
+
sessions: [this.#session_id],
|
|
799
|
+
},
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
// Validate and return the response
|
|
803
|
+
return v.parse(CreateMessageResultSchema, response);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Log a message to the client if logging is enabled and the level is appropriate
|
|
808
|
+
* @param {LoggingLevel} level
|
|
809
|
+
* @param {unknown} data
|
|
810
|
+
* @param {string} [logger]
|
|
811
|
+
*/
|
|
812
|
+
log(level, data, logger) {
|
|
813
|
+
if (!this.#options.capabilities?.logging) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
"The server doesn't support logging, please enable it in capabilities",
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const sessions = [];
|
|
820
|
+
|
|
821
|
+
for (let [session_id, session_log_level] of this.#session_log_levels) {
|
|
822
|
+
// Check if the current log level should be sent based on severity
|
|
823
|
+
if (this.#should_log(level, session_log_level)) {
|
|
824
|
+
sessions.push(session_id);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (sessions.length === 0) return;
|
|
829
|
+
|
|
830
|
+
// Send the log notification to the client
|
|
831
|
+
this.#notify(
|
|
832
|
+
'notifications/message',
|
|
833
|
+
{
|
|
834
|
+
level,
|
|
835
|
+
data,
|
|
836
|
+
logger,
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
sessions,
|
|
840
|
+
},
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Check if a log message should be sent based on severity levels
|
|
846
|
+
* @param {LoggingLevel} message_level
|
|
847
|
+
* @param {LoggingLevel} session_level
|
|
848
|
+
* @returns {boolean}
|
|
849
|
+
*/
|
|
850
|
+
#should_log(message_level, session_level) {
|
|
851
|
+
const levels = [
|
|
852
|
+
'debug',
|
|
853
|
+
'info',
|
|
854
|
+
'notice',
|
|
855
|
+
'warning',
|
|
856
|
+
'error',
|
|
857
|
+
'critical',
|
|
858
|
+
'alert',
|
|
859
|
+
'emergency',
|
|
860
|
+
];
|
|
861
|
+
const message_severity = levels.indexOf(message_level);
|
|
862
|
+
const session_severity = levels.indexOf(session_level);
|
|
863
|
+
|
|
864
|
+
// Send if message severity is equal to or higher than session level
|
|
865
|
+
return message_severity >= session_severity;
|
|
866
|
+
}
|
|
529
867
|
}
|