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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Paolo Ricciuti
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -19,25 +19,25 @@ tmcp offers significant advantages over the official MCP SDK:
19
19
 
20
20
  tmcp works with all major schema validation libraries through its adapter system:
21
21
 
22
- - **Zod** - `@tmcpkit/adapter-zod`
23
- - **Valibot** - `@tmcpkit/adapter-valibot`
24
- - **ArkType** - `@tmcpkit/adapter-arktype`
25
- - **Effect Schema** - `@tmcpkit/adapter-effect`
26
- - **Zod v3** - `@tmcpkit/adapter-zod-v3`
22
+ - **Zod** - `@tmcp/adapter-zod`
23
+ - **Valibot** - `@tmcp/adapter-valibot`
24
+ - **ArkType** - `@tmcp/adapter-arktype`
25
+ - **Effect Schema** - `@tmcp/adapter-effect`
26
+ - **Zod v3** - `@tmcp/adapter-zod-v3`
27
27
 
28
28
  ## Installation
29
29
 
30
30
  ```bash
31
31
  pnpm install tmcp
32
32
  # Choose your preferred schema library adapter
33
- ènpm install @tmcpkit/adapter-zod zod
33
+ pnpm install @tmcp/adapter-zod zod
34
34
  ```
35
35
 
36
36
  ## Quick Start
37
37
 
38
38
  ```javascript
39
39
  import { McpServer } from 'tmcp';
40
- import { ZodJsonSchemaAdapter } from '@tmcpkit/adapter-zod';
40
+ import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod';
41
41
  import { z } from 'zod';
42
42
 
43
43
  const adapter = new ZodJsonSchemaAdapter();
@@ -71,13 +71,21 @@ server.tool(
71
71
  async ({ operation, a, b }) => {
72
72
  switch (operation) {
73
73
  case 'add':
74
- return a + b;
74
+ return {
75
+ content: [{ type: 'text', text: `${a} + ${b} = ${a + b}` }],
76
+ };
75
77
  case 'subtract':
76
- return a - b;
78
+ return {
79
+ content: [{ type: 'text', text: `${a} - ${b} = ${a - b}` }],
80
+ };
77
81
  case 'multiply':
78
- return a * b;
82
+ return {
83
+ content: [{ type: 'text', text: `${a} × ${b} = ${a * b}` }],
84
+ };
79
85
  case 'divide':
80
- return a / b;
86
+ return {
87
+ content: [{ type: 'text', text: `${a} ÷ ${b} = ${a / b}` }],
88
+ };
81
89
  }
82
90
  },
83
91
  );
@@ -116,7 +124,7 @@ server.tool(
116
124
  },
117
125
  async (input) => {
118
126
  // Tool implementation
119
- return result;
127
+ return { content: [{ type: 'text', text: 'Tool result' }] };
120
128
  },
121
129
  );
122
130
  ```
@@ -131,7 +139,15 @@ server.prompt(
131
139
  name: 'prompt-name',
132
140
  description: 'Prompt description',
133
141
  schema: yourSchema, // optional
134
- complete: (arg, context) => ['completion1', 'completion2'] // optional
142
+ complete: {
143
+ paramName: (arg, context) => ({
144
+ completion: {
145
+ values: ['completion1', 'completion2'],
146
+ total: 2,
147
+ hasMore: false
148
+ }
149
+ })
150
+ } // optional
135
151
  },
136
152
  async (input) => {
137
153
  // Prompt implementation
@@ -168,7 +184,15 @@ server.template(
168
184
  name: 'template-name',
169
185
  description: 'Template description',
170
186
  uri: 'file://path/{id}/resource',
171
- complete: (arg, context) => ['id1', 'id2'] // optional
187
+ complete: {
188
+ id: (arg, context) => ({
189
+ completion: {
190
+ values: ['id1', 'id2', 'id3'],
191
+ total: 3,
192
+ hasMore: false
193
+ }
194
+ })
195
+ } // optional
172
196
  },
173
197
  async (uri, params) => {
174
198
  // Template implementation using params.id
@@ -177,16 +201,138 @@ server.template(
177
201
  );
178
202
  ```
179
203
 
180
- ##### `receive(request)`
204
+ ##### `receive(request, sessionId)`
181
205
 
182
206
  Process an incoming MCP request.
183
207
 
184
208
  ```javascript
185
- const response = server.receive(jsonRpcRequest);
209
+ const response = server.receive(jsonRpcRequest, sessionId);
210
+ ```
211
+
212
+ ##### `elicitation(schema)`
213
+
214
+ Request client elicitation with schema validation.
215
+
216
+ ```javascript
217
+ const result = await server.elicitation(schema);
218
+ ```
219
+
220
+ ##### `message(request)`
221
+
222
+ Request language model sampling from the client.
223
+
224
+ ```javascript
225
+ const result = await server.message({
226
+ messages: [
227
+ {
228
+ role: 'user',
229
+ content: { type: 'text', text: 'Hello!' },
230
+ },
231
+ ],
232
+ });
233
+ ```
234
+
235
+ ##### `refreshRoots()`
236
+
237
+ Refresh the roots list from the client.
238
+
239
+ ```javascript
240
+ await server.refreshRoots();
241
+ console.log(server.roots); // Access current roots
242
+ ```
243
+
244
+ ##### `changed(type, id)`
245
+
246
+ Send notifications for subscriptions.
247
+
248
+ ```javascript
249
+ server.changed('resource', 'file://path/to/resource');
250
+ ```
251
+
252
+ ##### `on(event, callback)`
253
+
254
+ Listen to server events.
255
+
256
+ ```javascript
257
+ server.on('initialize', (data) => {
258
+ console.log('Client initialized:', data);
259
+ });
260
+
261
+ server.on('send', ({ request, context }) => {
262
+ console.log('Sending request:', request);
263
+ });
186
264
  ```
187
265
 
188
266
  ## Advanced Examples
189
267
 
268
+ ### Client Interaction Features
269
+
270
+ ```javascript
271
+ // Elicitation - Request structured data from client
272
+ const userData = await server.elicitation(
273
+ z.object({
274
+ name: z.string(),
275
+ age: z.number(),
276
+ preferences: z.array(z.string()),
277
+ }),
278
+ );
279
+
280
+ // Message sampling - Request AI responses
281
+ const aiResponse = await server.message({
282
+ messages: [
283
+ {
284
+ role: 'user',
285
+ content: { type: 'text', text: 'Explain quantum computing' },
286
+ },
287
+ ],
288
+ maxTokens: 100,
289
+ });
290
+
291
+ // Roots management - Access client's filesystem roots
292
+ await server.refreshRoots();
293
+ console.log('Available roots:', server.roots);
294
+ ```
295
+
296
+ ### Event Handling
297
+
298
+ ```javascript
299
+ // Listen to server events
300
+ server.on('initialize', (data) => {
301
+ console.log('Client capabilities:', data.capabilities);
302
+ });
303
+
304
+ server.on('send', ({ request, context }) => {
305
+ console.log('Outgoing request:', request.method);
306
+ });
307
+ ```
308
+
309
+ ### Resource Subscriptions
310
+
311
+ ```javascript
312
+ // Subscribe to resource changes
313
+ server.resource(
314
+ {
315
+ name: 'file-watcher',
316
+ description: 'Watch file changes',
317
+ uri: 'file://watched/file.txt',
318
+ },
319
+ async (uri) => {
320
+ return {
321
+ contents: [
322
+ {
323
+ uri,
324
+ mimeType: 'text/plain',
325
+ text: await readFile(uri),
326
+ },
327
+ ],
328
+ };
329
+ },
330
+ );
331
+
332
+ // Notify subscribers when resource changes
333
+ server.changed('resource', 'file://watched/file.txt');
334
+ ```
335
+
190
336
  ### Multiple Schema Libraries
191
337
 
192
338
  ```javascript
@@ -199,7 +345,9 @@ server.tool(
199
345
  name: 'zod-tool',
200
346
  schema: z.object({ name: z.string() }),
201
347
  },
202
- async ({ name }) => `Hello ${name}`,
348
+ async ({ name }) => ({
349
+ content: [{ type: 'text', text: `Hello ${name}` }],
350
+ }),
203
351
  );
204
352
 
205
353
  server.tool(
@@ -207,11 +355,114 @@ server.tool(
207
355
  name: 'valibot-tool',
208
356
  schema: v.object({ age: v.number() }),
209
357
  },
210
- async ({ age }) => `Age: ${age}`,
358
+ async ({ age }) => ({
359
+ content: [{ type: 'text', text: `Age: ${age}` }],
360
+ }),
211
361
  );
212
362
  ```
213
363
 
214
- ### Resource Templates with Completion
364
+ ### Completion API
365
+
366
+ The completion API allows you to provide auto-completion suggestions for prompt and template parameters. Completion functions return an object with `completion` containing `values`, `total`, and `hasMore` properties.
367
+
368
+ #### Completion Response Format
369
+
370
+ ```javascript
371
+ {
372
+ completion: {
373
+ values: string[], // Array of completion values (max 100 items)
374
+ total?: number, // Total number of available completions
375
+ hasMore?: boolean // Whether there are more completions available
376
+ }
377
+ }
378
+ ```
379
+
380
+ #### Prompt Parameter Completion
381
+
382
+ ```javascript
383
+ server.prompt(
384
+ {
385
+ name: 'story-generator',
386
+ description: 'Generate a story with specific parameters',
387
+ schema: z.object({
388
+ genre: z.string(),
389
+ length: z.enum(['short', 'medium', 'long']),
390
+ character: z.string(),
391
+ }),
392
+ complete: {
393
+ genre: (arg, context) => {
394
+ const genres = [
395
+ 'fantasy',
396
+ 'sci-fi',
397
+ 'mystery',
398
+ 'romance',
399
+ 'thriller',
400
+ ];
401
+ const filtered = genres.filter((g) =>
402
+ g.startsWith(arg.toLowerCase()),
403
+ );
404
+
405
+ return {
406
+ completion: {
407
+ values: filtered,
408
+ total: filtered.length,
409
+ hasMore: false,
410
+ },
411
+ };
412
+ },
413
+ length: (arg, context) => {
414
+ const lengths = ['short', 'medium', 'long'];
415
+ const filtered = lengths.filter((l) => l.includes(arg));
416
+
417
+ return {
418
+ completion: {
419
+ values: filtered,
420
+ total: filtered.length,
421
+ hasMore: false,
422
+ },
423
+ };
424
+ },
425
+ character: (arg, context) => {
426
+ // Dynamic completion based on genre
427
+ const characters = {
428
+ fantasy: ['wizard', 'dragon', 'knight', 'elf'],
429
+ 'sci-fi': ['robot', 'alien', 'cyborg', 'space-explorer'],
430
+ mystery: ['detective', 'suspect', 'witness', 'victim'],
431
+ default: ['hero', 'villain', 'sidekick', 'mentor'],
432
+ };
433
+
434
+ const genre = context.params?.genre || 'default';
435
+ const charList = characters[genre] || characters.default;
436
+ const filtered = charList.filter((c) => c.includes(arg));
437
+
438
+ return {
439
+ completion: {
440
+ values: filtered,
441
+ total: filtered.length,
442
+ hasMore: false,
443
+ },
444
+ };
445
+ },
446
+ },
447
+ },
448
+ async (input) => {
449
+ return {
450
+ description: `A ${input.length} ${input.genre} story featuring a ${input.character}`,
451
+ messages: [
452
+ {
453
+ role: 'user',
454
+ content: {
455
+ type: 'text',
456
+ text: `Write a ${input.length} ${input.genre} story featuring a ${input.character}.`,
457
+ },
458
+ },
459
+ ],
460
+ };
461
+ },
462
+ );
463
+ ```
464
+
465
+ #### Resource Template Completion
215
466
 
216
467
  ```javascript
217
468
  server.template(
@@ -219,9 +470,20 @@ server.template(
219
470
  name: 'user-profile',
220
471
  description: 'Get user profile by ID',
221
472
  uri: 'users/{userId}/profile',
222
- complete: (arg, context) => {
223
- // Provide completions for userId parameter
224
- return ['user1', 'user2', 'user3'];
473
+ complete: {
474
+ userId: (arg, context) => {
475
+ // Filter users based on the current input
476
+ const allUsers = ['user1', 'user2', 'user3', 'admin-user'];
477
+ const filtered = allUsers.filter((id) => id.includes(arg));
478
+
479
+ return {
480
+ completion: {
481
+ values: filtered.slice(0, 10), // Limit to 10 results
482
+ total: filtered.length,
483
+ hasMore: filtered.length > 10,
484
+ },
485
+ };
486
+ },
225
487
  },
226
488
  },
227
489
  async (uri, params) => {
@@ -239,6 +501,118 @@ server.template(
239
501
  );
240
502
  ```
241
503
 
504
+ #### Advanced Completion Examples
505
+
506
+ ```javascript
507
+ // Completion with async data fetching
508
+ server.template(
509
+ {
510
+ name: 'project-files',
511
+ description: 'Access project files by path',
512
+ uri: 'projects/{projectId}/files/{filePath}',
513
+ complete: {
514
+ projectId: (arg, context) => {
515
+ // Static list of project IDs
516
+ const projects = ['web-app', 'mobile-app', 'api-server'];
517
+ const filtered = projects.filter((p) => p.includes(arg));
518
+
519
+ return {
520
+ completion: {
521
+ values: filtered,
522
+ total: filtered.length,
523
+ hasMore: false,
524
+ },
525
+ };
526
+ },
527
+ filePath: async (arg, context) => {
528
+ // Dynamic file path completion based on project
529
+ const projectId = context.params?.projectId;
530
+ if (!projectId) {
531
+ return {
532
+ completion: {
533
+ values: [],
534
+ total: 0,
535
+ hasMore: false,
536
+ },
537
+ };
538
+ }
539
+
540
+ // Simulate fetching files from filesystem
541
+ const files = await getProjectFiles(projectId);
542
+ const filtered = files.filter((f) => f.includes(arg));
543
+
544
+ return {
545
+ completion: {
546
+ values: filtered.slice(0, 20),
547
+ total: filtered.length,
548
+ hasMore: filtered.length > 20,
549
+ },
550
+ };
551
+ },
552
+ },
553
+ },
554
+ async (uri, params) => {
555
+ const content = await readProjectFile(
556
+ params.projectId,
557
+ params.filePath,
558
+ );
559
+ return {
560
+ contents: [
561
+ {
562
+ uri,
563
+ mimeType: 'text/plain',
564
+ text: content,
565
+ },
566
+ ],
567
+ };
568
+ },
569
+ );
570
+
571
+ // Completion with pagination support
572
+ server.prompt(
573
+ {
574
+ name: 'search-documents',
575
+ description: 'Search through large document collection',
576
+ schema: z.object({
577
+ query: z.string(),
578
+ category: z.string(),
579
+ }),
580
+ complete: {
581
+ category: (arg, context) => {
582
+ // Large category list with pagination
583
+ const allCategories = generateCategoryList(); // Assume this returns 500+ items
584
+ const filtered = allCategories.filter((c) =>
585
+ c.toLowerCase().includes(arg.toLowerCase()),
586
+ );
587
+
588
+ return {
589
+ completion: {
590
+ values: filtered.slice(0, 50), // Show first 50 matches
591
+ total: filtered.length,
592
+ hasMore: filtered.length > 50,
593
+ },
594
+ };
595
+ },
596
+ },
597
+ },
598
+ async (input) => {
599
+ const results = await searchDocuments(input.query, input.category);
600
+ return {
601
+ description: `Search results for "${input.query}" in ${input.category}`,
602
+ messages: [
603
+ {
604
+ role: 'user',
605
+ content: {
606
+ type: 'text',
607
+ text: `Found ${results.length} documents matching "${input.query}"`,
608
+ },
609
+ },
610
+ ],
611
+ };
612
+ },
613
+ );
614
+ ```
615
+
242
616
  ### Complex Validation
243
617
 
244
618
  ```javascript
@@ -266,7 +640,15 @@ server.tool(
266
640
  async (input) => {
267
641
  // Input is fully typed and validated
268
642
  const { user, preferences, tags } = input;
269
- return await createUser(user, preferences, tags);
643
+ const result = await createUser(user, preferences, tags);
644
+ return {
645
+ content: [
646
+ {
647
+ type: 'text',
648
+ text: `User created: ${user.name} (${user.email})`,
649
+ },
650
+ ],
651
+ };
270
652
  },
271
653
  );
272
654
  ```
@@ -275,6 +657,10 @@ server.tool(
275
657
 
276
658
  Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details.
277
659
 
660
+ ## Acknowledgments
661
+
662
+ Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm.
663
+
278
664
  ## License
279
665
 
280
666
  MIT © Paolo Ricciuti
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmcp",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "The main tmcp library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -28,6 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "json-rpc-2.0": "^1.7.1",
31
+ "sqids": "^0.3.0",
31
32
  "uri-template-matcher": "^1.1.0",
32
33
  "valibot": "^1.1.0"
33
34
  },