tmcp 1.3.0 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "The main tmcp library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -34,6 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@standard-schema/spec": "^1.0.0",
36
36
  "@types/json-schema": "^7.0.15",
37
+ "@types/node": "^24.0.13",
37
38
  "publint": "^0.3.12"
38
39
  },
39
40
  "scripts": {
package/src/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ /**
2
+ * @typedef {ClientCapabilitiesType} ClientCapabilities
3
+ */
1
4
  /**
2
5
  * @template {StandardSchemaV1} StandardSchema
3
6
  */
@@ -7,6 +10,21 @@ export class McpServer<StandardSchema extends StandardSchemaV1> {
7
10
  * @param {ServerOptions<StandardSchema>} options
8
11
  */
9
12
  constructor(server_info: ServerInfo, options: ServerOptions<StandardSchema>);
13
+ currentClientCapabilities(): {
14
+ roots?: {
15
+ listChanged?: boolean;
16
+ } & {
17
+ [key: string]: unknown;
18
+ };
19
+ sampling?: {} & {
20
+ [key: string]: unknown;
21
+ };
22
+ elicitation?: {} & {
23
+ [key: string]: unknown;
24
+ };
25
+ } & {
26
+ [key: string]: unknown;
27
+ };
10
28
  /**
11
29
  * @template {keyof McpEvents} TEvent
12
30
  * @param {TEvent} event
@@ -58,17 +76,25 @@ export class McpServer<StandardSchema extends StandardSchemaV1> {
58
76
  }, execute: (uri: string, params: Record<TVariables, string | string[]>) => Promise<ReadResourceResult> | ReadResourceResult): void;
59
77
  /**
60
78
  * @param {JSONRPCRequest} request
79
+ * @param {string} [session_id]
61
80
  * @returns {ReturnType<JSONRPCServer['receive']>}
62
81
  */
63
- receive(request: JSONRPCRequest): ReturnType<JSONRPCServer["receive"]>;
82
+ receive(request: JSONRPCRequest, session_id?: string): ReturnType<JSONRPCServer["receive"]>;
64
83
  /**
65
84
  * Send a notification for subscriptions
66
85
  * @param {SubscriptionsKeys} what
67
86
  * @param {string} id
68
87
  */
69
88
  changed(what: SubscriptionsKeys, id: string): void;
89
+ /**
90
+ * @template {StandardSchema} TSchema
91
+ * @param {TSchema} schema
92
+ * @returns {Promise<StandardSchemaV1.InferOutput<TSchema>>}
93
+ */
94
+ elicitation<TSchema extends StandardSchema>(schema: TSchema): Promise<StandardSchemaV1.InferOutput<TSchema>>;
70
95
  #private;
71
96
  }
97
+ export type ClientCapabilities = ClientCapabilitiesType;
72
98
  import type { StandardSchemaV1 } from "@standard-schema/spec";
73
99
  import type { McpEvents } from "./internal/internal.js";
74
100
  import type { CallToolResult } from "./validation/index.js";
@@ -81,3 +107,4 @@ import { JSONRPCServer } from 'json-rpc-2.0';
81
107
  import type { SubscriptionsKeys } from "./internal/internal.js";
82
108
  import type { ServerInfo } from "./internal/internal.js";
83
109
  import type { ServerOptions } from "./internal/internal.js";
110
+ import type { ClientCapabilities as ClientCapabilitiesType } from "./validation/index.js";
package/src/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * @import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
  * @import { JSONRPCRequest, JSONRPCParams } from "json-rpc-2.0";
4
4
  * @import { ExtractURITemplateVariables } from "./internal/uri-template.js";
5
- * @import { CallToolResult, ReadResourceResult, GetPromptResult, CompleteResult } from "./validation/index.js";
5
+ * @import { CallToolResult, ReadResourceResult, GetPromptResult, ClientCapabilities as ClientCapabilitiesType } from "./validation/index.js";
6
6
  * @import { Tool, Completion, Prompt, Resource, ServerOptions, ServerInfo, SubscriptionsKeys, McpEvents } from "./internal/internal.js";
7
7
  */
8
8
  import { JSONRPCServer, JSONRPCClient } from 'json-rpc-2.0';
@@ -12,16 +12,22 @@ import {
12
12
  ReadResourceResultSchema,
13
13
  GetPromptResultSchema,
14
14
  CompleteResultSchema,
15
+ InitializeRequestSchema,
15
16
  } from './validation/index.js';
17
+ import { AsyncLocalStorage } from 'node:async_hooks';
16
18
  import * as v from 'valibot';
17
19
 
20
+ /**
21
+ * @typedef {ClientCapabilitiesType} ClientCapabilities
22
+ */
23
+
18
24
  /**
19
25
  * @template {StandardSchemaV1} StandardSchema
20
26
  */
21
27
  export class McpServer {
22
28
  #server = new JSONRPCServer();
23
29
  /**
24
- * @type {JSONRPCClient | undefined}
30
+ * @type {JSONRPCClient<Record<string, any> | undefined> | undefined}
25
31
  */
26
32
  #client;
27
33
  #options;
@@ -49,24 +55,47 @@ export class McpServer {
49
55
  #event_target = new EventTarget();
50
56
 
51
57
  /**
52
- * @type {Record<SubscriptionsKeys, Set<string>>}
58
+ * @type {Record<SubscriptionsKeys, Map<string, Set<string | undefined>>>}
53
59
  */
54
60
  #subscriptions = {
55
61
  /**
56
- * @type {Set<string>}
62
+ * @type {Map<string, Set<string>>}
57
63
  */
58
- resource: new Set(),
64
+ resource: new Map(),
59
65
  };
60
66
 
67
+ /**
68
+ * @type {AsyncLocalStorage<string | undefined>}
69
+ */
70
+ #session_storage = new AsyncLocalStorage();
71
+
72
+ /**
73
+ * @type {Map<string|undefined, ClientCapabilities>}
74
+ */
75
+ #client_capabilities_map = new Map();
76
+
61
77
  /**
62
78
  * @param {ServerInfo} server_info
63
79
  * @param {ServerOptions<StandardSchema>} options
64
80
  */
65
81
  constructor(server_info, options) {
66
82
  this.#options = options;
67
- this.#server.addMethod('initialize', ({ protocolVersion }) => {
83
+ this.#server.addMethod('initialize', (initialize_request) => {
84
+ const validated_initialize = v.parse(
85
+ InitializeRequestSchema,
86
+ initialize_request,
87
+ );
88
+ this.#client_capabilities_map.set(
89
+ this.#session_id,
90
+ validated_initialize.capabilities,
91
+ );
92
+ this.#event_target.dispatchEvent(
93
+ new CustomEvent('initialize', {
94
+ detail: validated_initialize,
95
+ }),
96
+ );
68
97
  return {
69
- protocolVersion,
98
+ protocolVersion: validated_initialize.protocolVersion,
70
99
  ...options,
71
100
  serverInfo: server_info,
72
101
  };
@@ -83,6 +112,30 @@ export class McpServer {
83
112
  this.#init_completion();
84
113
  }
85
114
 
115
+ get #session_id() {
116
+ return this.#session_storage.getStore();
117
+ }
118
+
119
+ get #client_capabilities() {
120
+ return this.#client_capabilities_map.get(this.#session_id);
121
+ }
122
+
123
+ currentClientCapabilities() {
124
+ return this.#client_capabilities;
125
+ }
126
+
127
+ #lazyily_create_client() {
128
+ if (!this.#client) {
129
+ this.#client = new JSONRPCClient((payload, context = {}) => {
130
+ this.#event_target.dispatchEvent(
131
+ new CustomEvent('send', {
132
+ detail: { request: payload, context },
133
+ }),
134
+ );
135
+ });
136
+ }
137
+ }
138
+
86
139
  /**
87
140
  * @template {keyof McpEvents} TEvent
88
141
  * @param {TEvent} event
@@ -90,12 +143,8 @@ export class McpServer {
90
143
  * @param {AddEventListenerOptions} [options]
91
144
  */
92
145
  on(event, callback, options) {
93
- if (event === 'send' && !this.#client) {
94
- this.#client = new JSONRPCClient((payload) => {
95
- this.#event_target.dispatchEvent(
96
- new CustomEvent('send', { detail: payload }),
97
- );
98
- });
146
+ if (event === 'send') {
147
+ this.#lazyily_create_client();
99
148
  }
100
149
 
101
150
  this.#event_target.addEventListener(
@@ -110,9 +159,10 @@ export class McpServer {
110
159
  /**
111
160
  * @param {string} method
112
161
  * @param {JSONRPCParams} [params]
162
+ * @param {Record<string, any>} [extra]
113
163
  */
114
- #notify(method, params) {
115
- this.#client?.notify(method, params);
164
+ #notify(method, params, extra) {
165
+ this.#client?.notify(method, params, extra);
116
166
  }
117
167
 
118
168
  /**
@@ -243,7 +293,12 @@ export class McpServer {
243
293
 
244
294
  if (this.#options.capabilities?.resources?.subscribe) {
245
295
  this.#server.addMethod('resources/subscribe', async ({ uri }) => {
246
- this.#subscriptions.resource.add(uri);
296
+ let subscriptions = this.#subscriptions.resource.get(uri);
297
+ if (!subscriptions) {
298
+ subscriptions = new Set();
299
+ this.#subscriptions.resource.set(uri, subscriptions);
300
+ }
301
+ subscriptions.add(this.#session_id);
247
302
  return {};
248
303
  });
249
304
  }
@@ -408,10 +463,14 @@ export class McpServer {
408
463
  }
409
464
  /**
410
465
  * @param {JSONRPCRequest} request
466
+ * @param {string} [session_id]
411
467
  * @returns {ReturnType<JSONRPCServer['receive']>}
412
468
  */
413
- receive(request) {
414
- return this.#server.receive(request);
469
+ receive(request, session_id) {
470
+ return this.#session_storage.run(
471
+ session_id,
472
+ async () => await this.#server.receive(request),
473
+ );
415
474
  }
416
475
 
417
476
  /**
@@ -423,10 +482,48 @@ export class McpServer {
423
482
  if (this.#subscriptions[what].has(id)) {
424
483
  const resource = this.#resources.get(id);
425
484
  if (!resource) return;
426
- this.#notify(`notifications/resources/updated`, {
427
- uri: id,
428
- title: resource.name,
429
- });
485
+ const sessions = this.#subscriptions[what].get(id);
486
+ this.#notify(
487
+ `notifications/resources/updated`,
488
+ {
489
+ uri: id,
490
+ title: resource.name,
491
+ },
492
+ {
493
+ sessions: sessions ? [...sessions] : undefined,
494
+ },
495
+ );
496
+ }
497
+ }
498
+
499
+ /**
500
+ * @template {StandardSchema} TSchema
501
+ * @param {TSchema} schema
502
+ * @returns {Promise<StandardSchemaV1.InferOutput<TSchema>>}
503
+ */
504
+ async elicitation(schema) {
505
+ if (!this.#client_capabilities?.elicitation)
506
+ throw new Error("Client doesn't support elicitation");
507
+
508
+ this.#lazyily_create_client();
509
+ let validated_result = schema['~standard'].validate(
510
+ await this.#client?.request(
511
+ 'elicitation/create',
512
+ {
513
+ params: await this.#options.adapter.toJsonSchema(schema),
514
+ },
515
+ {
516
+ sessions: [this.#session_id],
517
+ },
518
+ ),
519
+ );
520
+ if (validated_result instanceof Promise)
521
+ validated_result = await validated_result;
522
+ if (validated_result.issues) {
523
+ throw new Error(
524
+ `Invalid elicitation result: ${JSON.stringify(validated_result.issues)}`,
525
+ );
430
526
  }
527
+ return validated_result.value;
431
528
  }
432
529
  }
@@ -6,6 +6,7 @@ import {
6
6
  CallToolResult,
7
7
  ReadResourceResult,
8
8
  CompleteResult,
9
+ InitializeRequest,
9
10
  } from '../validation/index.js';
10
11
 
11
12
  export type Tool<TSchema extends StandardSchemaV1 = StandardSchemaV1<any>> = {
@@ -67,5 +68,11 @@ export type ServerInfo = {
67
68
  export type SubscriptionsKeys = 'resource';
68
69
 
69
70
  export type McpEvents = {
70
- send: (message: JSONRPCRequest) => void;
71
+ send: (message: {
72
+ request: JSONRPCRequest;
73
+ context: {
74
+ sessions?: string[] | undefined;
75
+ };
76
+ }) => void;
77
+ initialize: (initialize_request: InitializeRequest) => void;
71
78
  };
@@ -675,6 +675,25 @@ export const CompleteResultSchema: v.LooseObjectSchema<{
675
675
  }, undefined>;
676
676
  readonly _meta: v.OptionalSchema<v.LooseObjectSchema<{}, undefined>, undefined>;
677
677
  }, undefined>;
678
+ export const ClientCapabilitiesSchema: v.LooseObjectSchema<{
679
+ readonly roots: v.OptionalSchema<v.LooseObjectSchema<{
680
+ readonly listChanged: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
681
+ }, undefined>, undefined>;
682
+ readonly sampling: v.OptionalSchema<v.LooseObjectSchema<{}, undefined>, undefined>;
683
+ readonly elicitation: v.OptionalSchema<v.LooseObjectSchema<{}, undefined>, undefined>;
684
+ }, undefined>;
685
+ export const InitializeRequestSchema: v.LooseObjectSchema<{
686
+ readonly protocolVersion: v.StringSchema<undefined>;
687
+ readonly capabilities: v.LooseObjectSchema<{
688
+ readonly roots: v.OptionalSchema<v.LooseObjectSchema<{
689
+ readonly listChanged: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
690
+ }, undefined>, undefined>;
691
+ readonly sampling: v.OptionalSchema<v.LooseObjectSchema<{}, undefined>, undefined>;
692
+ readonly elicitation: v.OptionalSchema<v.LooseObjectSchema<{}, undefined>, undefined>;
693
+ }, undefined>;
694
+ }, undefined>;
695
+ export type ClientCapabilities = v.InferInput<typeof ClientCapabilitiesSchema>;
696
+ export type InitializeRequest = v.InferInput<typeof InitializeRequestSchema>;
678
697
  export type CallToolResult = v.InferInput<typeof CallToolResultSchema>;
679
698
  export type ReadResourceResult = v.InferInput<typeof ReadResourceResultSchema>;
680
699
  export type GetPromptResult = v.InferInput<typeof GetPromptResultSchema>;
@@ -231,6 +231,29 @@ export const CompleteResultSchema = v.looseObject({
231
231
  _meta: v.optional(v.looseObject({})),
232
232
  });
233
233
 
234
+ export const ClientCapabilitiesSchema = v.looseObject({
235
+ roots: v.optional(
236
+ v.looseObject({
237
+ listChanged: v.optional(v.boolean()),
238
+ }),
239
+ ),
240
+ sampling: v.optional(v.looseObject({})),
241
+ elicitation: v.optional(v.looseObject({})),
242
+ });
243
+
244
+ export const InitializeRequestSchema = v.looseObject({
245
+ protocolVersion: v.string(),
246
+ capabilities: ClientCapabilitiesSchema,
247
+ });
248
+
249
+ /**
250
+ * @typedef {v.InferInput<typeof ClientCapabilitiesSchema>} ClientCapabilities
251
+ */
252
+
253
+ /**
254
+ * @typedef {v.InferInput<typeof InitializeRequestSchema>} InitializeRequest
255
+ */
256
+
234
257
  /**
235
258
  * @typedef {v.InferInput<typeof CallToolResultSchema>} CallToolResult
236
259
  */