tmcp 1.10.2 → 1.11.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/README.md +375 -0
- package/package.json +1 -1
- package/src/index.js +260 -96
- package/src/internal/internal.d.ts +5 -1
- package/src/types/index.d.ts +80 -16
- package/src/types/index.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -249,6 +249,67 @@ Send notifications for subscriptions.
|
|
|
249
249
|
server.changed('resource', 'file://path/to/resource');
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
+
##### `progress(progress, total?, message?)`
|
|
253
|
+
|
|
254
|
+
Report progress during long-running operations. Progress notifications are only sent when a progress token is provided by the client in the request's `_meta.progressToken` field.
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
server.tool(
|
|
258
|
+
{
|
|
259
|
+
name: 'process-large-file',
|
|
260
|
+
description: 'Process a large file with progress updates',
|
|
261
|
+
},
|
|
262
|
+
async (input) => {
|
|
263
|
+
const totalSteps = 100;
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i <= totalSteps; i++) {
|
|
266
|
+
// Report progress with current step, total steps, and optional message
|
|
267
|
+
server.progress(
|
|
268
|
+
i,
|
|
269
|
+
totalSteps,
|
|
270
|
+
`Processing step ${i}/${totalSteps}`,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Simulate work
|
|
274
|
+
await processStep(i);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: 'text', text: 'Processing complete!' }],
|
|
279
|
+
};
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Parameters:**
|
|
285
|
+
|
|
286
|
+
- `progress` (number): Current progress value (should be ≤ total and always increase)
|
|
287
|
+
- `total` (number, optional): Maximum progress value (defaults to 1)
|
|
288
|
+
- `message` (string, optional): Descriptive message about current progress
|
|
289
|
+
|
|
290
|
+
**Notes:**
|
|
291
|
+
|
|
292
|
+
- Progress notifications are only sent when the client provides a `progressToken` in the request
|
|
293
|
+
- Progress values should be monotonically increasing within a single operation
|
|
294
|
+
- Each session maintains its own progress context
|
|
295
|
+
|
|
296
|
+
##### `log(level, data, logger?)`
|
|
297
|
+
|
|
298
|
+
Send log messages to connected clients when logging is enabled.
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// Log at different severity levels
|
|
302
|
+
server.log('info', 'Server started successfully');
|
|
303
|
+
server.log('warning', 'Configuration missing, using defaults');
|
|
304
|
+
server.log('error', 'Failed to connect to database', 'database-logger');
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Parameters:**
|
|
308
|
+
|
|
309
|
+
- `level` (string): Log level ('debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency')
|
|
310
|
+
- `data` (any): Data to log
|
|
311
|
+
- `logger` (string, optional): Logger name/category
|
|
312
|
+
|
|
252
313
|
##### `on(event, callback)`
|
|
253
314
|
|
|
254
315
|
Listen to server events.
|
|
@@ -265,6 +326,94 @@ server.on('send', ({ request, context }) => {
|
|
|
265
326
|
|
|
266
327
|
## Advanced Examples
|
|
267
328
|
|
|
329
|
+
### Progress Reporting
|
|
330
|
+
|
|
331
|
+
Provide real-time progress updates for long-running operations:
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
server.tool(
|
|
335
|
+
{
|
|
336
|
+
name: 'analyze-codebase',
|
|
337
|
+
description: 'Analyze a large codebase with progress tracking',
|
|
338
|
+
schema: z.object({
|
|
339
|
+
path: z.string(),
|
|
340
|
+
includeTests: z.boolean().default(false),
|
|
341
|
+
}),
|
|
342
|
+
},
|
|
343
|
+
async ({ path, includeTests }) => {
|
|
344
|
+
// Discover files to analyze
|
|
345
|
+
const files = await discoverFiles(path, includeTests);
|
|
346
|
+
const totalFiles = files.length;
|
|
347
|
+
|
|
348
|
+
server.progress(0, totalFiles, 'Starting analysis...');
|
|
349
|
+
|
|
350
|
+
const results = [];
|
|
351
|
+
for (let i = 0; i < files.length; i++) {
|
|
352
|
+
const file = files[i];
|
|
353
|
+
|
|
354
|
+
// Update progress with current file being processed
|
|
355
|
+
server.progress(
|
|
356
|
+
i + 1,
|
|
357
|
+
totalFiles,
|
|
358
|
+
`Analyzing ${file} (${i + 1}/${totalFiles})`,
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
// Analyze the file
|
|
362
|
+
const analysis = await analyzeFile(file);
|
|
363
|
+
results.push(analysis);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Final progress update
|
|
367
|
+
server.progress(totalFiles, totalFiles, 'Analysis complete!');
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
content: [
|
|
371
|
+
{
|
|
372
|
+
type: 'text',
|
|
373
|
+
text: `Analyzed ${totalFiles} files. Found ${results.length} issues.`,
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
structuredContent: {
|
|
377
|
+
totalFiles,
|
|
378
|
+
results,
|
|
379
|
+
issues: results.length,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// Progress also works in prompts and resources
|
|
386
|
+
server.prompt(
|
|
387
|
+
{
|
|
388
|
+
name: 'generate-report',
|
|
389
|
+
description: 'Generate a comprehensive report',
|
|
390
|
+
schema: z.object({
|
|
391
|
+
sections: z.array(z.string()),
|
|
392
|
+
}),
|
|
393
|
+
},
|
|
394
|
+
async ({ sections }) => {
|
|
395
|
+
const messages = [];
|
|
396
|
+
|
|
397
|
+
for (let i = 0; i < sections.length; i++) {
|
|
398
|
+
server.progress(
|
|
399
|
+
i,
|
|
400
|
+
sections.length,
|
|
401
|
+
`Generating section: ${sections[i]}`,
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const content = await generateSection(sections[i]);
|
|
405
|
+
messages.push({
|
|
406
|
+
role: 'user',
|
|
407
|
+
content: { type: 'text', text: content },
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
server.progress(sections.length, sections.length, 'Report complete');
|
|
412
|
+
return { messages };
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
```
|
|
416
|
+
|
|
268
417
|
### Client Interaction Features
|
|
269
418
|
|
|
270
419
|
```javascript
|
|
@@ -625,6 +774,232 @@ server.tool(
|
|
|
625
774
|
);
|
|
626
775
|
```
|
|
627
776
|
|
|
777
|
+
## Dynamic Enabling/Disabling
|
|
778
|
+
|
|
779
|
+
All MCP capabilities (tools, prompts, resources, templates) support dynamic enabling and disabling through the `enabled` function. This allows you to conditionally show or hide capabilities based on runtime conditions, user permissions, or any other logic.
|
|
780
|
+
|
|
781
|
+
### Basic Usage
|
|
782
|
+
|
|
783
|
+
The `enabled` function is called before each list operation and determines whether the capability should be included in the response:
|
|
784
|
+
|
|
785
|
+
```javascript
|
|
786
|
+
server.tool(
|
|
787
|
+
{
|
|
788
|
+
name: 'admin-only-tool',
|
|
789
|
+
description: 'Administrative tool',
|
|
790
|
+
enabled: () => {
|
|
791
|
+
// Only show this tool if user is admin
|
|
792
|
+
return getCurrentUser().isAdmin;
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
async (input) => {
|
|
796
|
+
return { content: [{ type: 'text', text: 'Admin action completed' }] };
|
|
797
|
+
},
|
|
798
|
+
);
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### Async Enabled Functions
|
|
802
|
+
|
|
803
|
+
The `enabled` function can be synchronous or asynchronous:
|
|
804
|
+
|
|
805
|
+
```javascript
|
|
806
|
+
server.resource(
|
|
807
|
+
{
|
|
808
|
+
name: 'private-document',
|
|
809
|
+
description: 'Access private documents',
|
|
810
|
+
uri: 'private://document.txt',
|
|
811
|
+
enabled: async () => {
|
|
812
|
+
// Check permissions asynchronously
|
|
813
|
+
const user = await getCurrentUser();
|
|
814
|
+
return await hasPermission(user.id, 'read-private-docs');
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
async (uri) => {
|
|
818
|
+
return {
|
|
819
|
+
contents: [
|
|
820
|
+
{
|
|
821
|
+
uri,
|
|
822
|
+
mimeType: 'text/plain',
|
|
823
|
+
text: await readPrivateDocument(uri),
|
|
824
|
+
},
|
|
825
|
+
],
|
|
826
|
+
};
|
|
827
|
+
},
|
|
828
|
+
);
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
### Context-Aware Enabling
|
|
832
|
+
|
|
833
|
+
Within the `enabled` function you can read the session context with `server.ctx`, allowing for user-specific or session-specific logic:
|
|
834
|
+
|
|
835
|
+
```javascript
|
|
836
|
+
server.prompt(
|
|
837
|
+
{
|
|
838
|
+
name: 'personalized-prompt',
|
|
839
|
+
description: 'User-specific prompt template',
|
|
840
|
+
enabled: () => {
|
|
841
|
+
// Access session information
|
|
842
|
+
const sessionId = server.ctx.sessionId;
|
|
843
|
+
const userPrefs = getUserPreferences(sessionId);
|
|
844
|
+
return userPrefs.enablePersonalization;
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
async (input) => {
|
|
848
|
+
return {
|
|
849
|
+
messages: [
|
|
850
|
+
{
|
|
851
|
+
role: 'user',
|
|
852
|
+
content: { type: 'text', text: 'Personalized content...' },
|
|
853
|
+
},
|
|
854
|
+
],
|
|
855
|
+
};
|
|
856
|
+
},
|
|
857
|
+
);
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
or enable a something based on client capabilities or info
|
|
861
|
+
|
|
862
|
+
```javascript
|
|
863
|
+
server.prompt(
|
|
864
|
+
{
|
|
865
|
+
name: 'personalized-prompt',
|
|
866
|
+
description: 'Claude Code prompt',
|
|
867
|
+
enabled: () => {
|
|
868
|
+
// Access session information
|
|
869
|
+
const clientInfo = server.currentClientInfo();
|
|
870
|
+
return clientInfo.name === 'Claude Code';
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
async (input) => {
|
|
874
|
+
return {
|
|
875
|
+
messages: [
|
|
876
|
+
{
|
|
877
|
+
role: 'user',
|
|
878
|
+
content: { type: 'text', text: 'Personalized content...' },
|
|
879
|
+
},
|
|
880
|
+
],
|
|
881
|
+
};
|
|
882
|
+
},
|
|
883
|
+
);
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
or
|
|
887
|
+
|
|
888
|
+
```javascript
|
|
889
|
+
server.prompt(
|
|
890
|
+
{
|
|
891
|
+
name: 'fetch-repositories',
|
|
892
|
+
description: 'fetch the repositories of the user',
|
|
893
|
+
enabled: () => {
|
|
894
|
+
// Access session information
|
|
895
|
+
const clientInfo = server.currentClientCapabilities();
|
|
896
|
+
return clientInfo.elicitation != null;
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
async (input) => {
|
|
900
|
+
const username = await server.elicitation(
|
|
901
|
+
v.object({
|
|
902
|
+
value: v.string(),
|
|
903
|
+
}),
|
|
904
|
+
);
|
|
905
|
+
const repos = await fetchRepos(username.value);
|
|
906
|
+
return {
|
|
907
|
+
messages: [
|
|
908
|
+
{
|
|
909
|
+
role: 'user',
|
|
910
|
+
content: {
|
|
911
|
+
type: 'text',
|
|
912
|
+
text: `Your repositories are ${repos.join(', ')}`,
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
],
|
|
916
|
+
};
|
|
917
|
+
},
|
|
918
|
+
);
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
### Template Enabling
|
|
922
|
+
|
|
923
|
+
URI templates also support the `enabled` function:
|
|
924
|
+
|
|
925
|
+
```javascript
|
|
926
|
+
server.template(
|
|
927
|
+
{
|
|
928
|
+
name: 'user-files',
|
|
929
|
+
description: 'Access user-specific files',
|
|
930
|
+
uri: 'users/{userId}/files/{filename}',
|
|
931
|
+
enabled: async () => {
|
|
932
|
+
// Check if file system is available
|
|
933
|
+
return await isFileSystemMounted();
|
|
934
|
+
},
|
|
935
|
+
complete: {
|
|
936
|
+
userId: (arg, context) => ({
|
|
937
|
+
completion: {
|
|
938
|
+
values: ['user1', 'user2', 'user3'],
|
|
939
|
+
total: 3,
|
|
940
|
+
hasMore: false,
|
|
941
|
+
},
|
|
942
|
+
}),
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
async (uri, params) => {
|
|
946
|
+
const content = await readUserFile(params.userId, params.filename);
|
|
947
|
+
return {
|
|
948
|
+
contents: [
|
|
949
|
+
{
|
|
950
|
+
uri,
|
|
951
|
+
mimeType: 'text/plain',
|
|
952
|
+
text: content,
|
|
953
|
+
},
|
|
954
|
+
],
|
|
955
|
+
};
|
|
956
|
+
},
|
|
957
|
+
);
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
### Error Handling
|
|
961
|
+
|
|
962
|
+
If an `enabled` function throws an error, the capability will be treated as disabled:
|
|
963
|
+
|
|
964
|
+
```javascript
|
|
965
|
+
server.tool(
|
|
966
|
+
{
|
|
967
|
+
name: 'network-tool',
|
|
968
|
+
description: 'Tool requiring network access',
|
|
969
|
+
enabled: () => {
|
|
970
|
+
// If network check fails, tool is disabled
|
|
971
|
+
if (!checkNetworkConnection()) {
|
|
972
|
+
throw new Error('Network unavailable');
|
|
973
|
+
}
|
|
974
|
+
return true;
|
|
975
|
+
},
|
|
976
|
+
},
|
|
977
|
+
async (input) => {
|
|
978
|
+
return {
|
|
979
|
+
content: [{ type: 'text', text: 'Network operation completed' }],
|
|
980
|
+
};
|
|
981
|
+
},
|
|
982
|
+
);
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
### Performance Considerations
|
|
986
|
+
|
|
987
|
+
- The `enabled` function is called every time a list is requested
|
|
988
|
+
- Keep enabled functions lightweight to avoid performance issues
|
|
989
|
+
- Consider caching expensive checks when possible
|
|
990
|
+
- Async functions add latency to list operations
|
|
991
|
+
|
|
992
|
+
### Use Cases
|
|
993
|
+
|
|
994
|
+
Common scenarios where `enabled` functions are useful:
|
|
995
|
+
|
|
996
|
+
1. **Permission-based access**: Show tools only to authorized users
|
|
997
|
+
2. **Feature flags**: Enable/disable features based on configuration
|
|
998
|
+
3. **Resource availability**: Hide resources when underlying systems are unavailable
|
|
999
|
+
4. **User preferences**: Customize available capabilities per user
|
|
1000
|
+
5. **Time-based access**: Enable tools only during specific hours
|
|
1001
|
+
6. **License restrictions**: Limit features based on subscription level
|
|
1002
|
+
|
|
628
1003
|
## Contributing
|
|
629
1004
|
|
|
630
1005
|
Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
/* eslint-disable jsdoc/no-undefined-types */
|
|
1
2
|
/**
|
|
2
3
|
* @import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
4
|
* @import SqidsType from "sqids";
|
|
4
5
|
* @import { JSONRPCRequest, JSONRPCParams } from "json-rpc-2.0";
|
|
5
6
|
* @import { ExtractURITemplateVariables } from "./internal/uri-template.js";
|
|
6
|
-
* @import { CallToolResult, ReadResourceResult, GetPromptResult, ClientCapabilities as ClientCapabilitiesType, JSONRPCRequest as JSONRPCRequestType, JSONRPCResponse, CreateMessageRequestParams, CreateMessageResult, Resource, LoggingLevel, ToolAnnotations } from "./validation/index.js";
|
|
7
|
+
* @import { CallToolResult, ReadResourceResult, GetPromptResult, ClientCapabilities as ClientCapabilitiesType, JSONRPCRequest as JSONRPCRequestType, JSONRPCResponse, CreateMessageRequestParams, CreateMessageResult, Resource, LoggingLevel, ToolAnnotations, ClientInfo } from "./validation/index.js";
|
|
7
8
|
* @import { Tool, Completion, Prompt, StoredResource, ServerOptions, ServerInfo, SubscriptionsKeys, McpEvents } from "./internal/internal.js";
|
|
8
9
|
*/
|
|
9
10
|
import { JSONRPCClient, JSONRPCServer } from 'json-rpc-2.0';
|
|
@@ -43,9 +44,11 @@ import { should_version_negotiation_fail } from './validation/version.js';
|
|
|
43
44
|
*/
|
|
44
45
|
|
|
45
46
|
/**
|
|
47
|
+
* @template {Record<string, unknown> | undefined} [TCustom=undefined]
|
|
46
48
|
* @typedef {Object} Context
|
|
47
49
|
* @property {string} [sessionId]
|
|
48
50
|
* @property {AuthInfo} [auth]
|
|
51
|
+
* @property {TCustom} [custom]
|
|
49
52
|
*/
|
|
50
53
|
|
|
51
54
|
/**
|
|
@@ -77,12 +80,24 @@ async function decode_cursor(cursor) {
|
|
|
77
80
|
return decoded;
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @param {()=>boolean | Promise<boolean>} enabled
|
|
85
|
+
*/
|
|
86
|
+
async function safe_enabled(enabled) {
|
|
87
|
+
try {
|
|
88
|
+
return await enabled();
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
/**
|
|
81
95
|
* @typedef {ClientCapabilitiesType} ClientCapabilities
|
|
82
96
|
*/
|
|
83
97
|
|
|
84
98
|
/**
|
|
85
|
-
* @template {StandardSchemaV1} StandardSchema
|
|
99
|
+
* @template {StandardSchemaV1 | undefined} [StandardSchema=undefined]
|
|
100
|
+
* @template {Record<string, unknown> | undefined} [CustomContext=undefined]
|
|
86
101
|
*/
|
|
87
102
|
export class McpServer {
|
|
88
103
|
#server = new JSONRPCServer();
|
|
@@ -129,7 +144,7 @@ export class McpServer {
|
|
|
129
144
|
};
|
|
130
145
|
|
|
131
146
|
/**
|
|
132
|
-
* @type {AsyncLocalStorage<Context>}
|
|
147
|
+
* @type {AsyncLocalStorage<Context<CustomContext> & { progress_token?: string }>}
|
|
133
148
|
*/
|
|
134
149
|
#ctx_storage = new AsyncLocalStorage();
|
|
135
150
|
|
|
@@ -138,6 +153,11 @@ export class McpServer {
|
|
|
138
153
|
*/
|
|
139
154
|
#client_capabilities_map = new Map();
|
|
140
155
|
|
|
156
|
+
/**
|
|
157
|
+
* @type {Map<string|undefined, ClientInfo>}
|
|
158
|
+
*/
|
|
159
|
+
#client_info_map = new Map();
|
|
160
|
+
|
|
141
161
|
/**
|
|
142
162
|
* @type {Map<string|undefined, string>}
|
|
143
163
|
*/
|
|
@@ -195,6 +215,12 @@ export class McpServer {
|
|
|
195
215
|
validated_initialize.capabilities,
|
|
196
216
|
);
|
|
197
217
|
|
|
218
|
+
// Store client info
|
|
219
|
+
this.#client_info_map.set(
|
|
220
|
+
session_id,
|
|
221
|
+
validated_initialize.clientInfo,
|
|
222
|
+
);
|
|
223
|
+
|
|
198
224
|
// Dispatch initialization event
|
|
199
225
|
this.#event_target.dispatchEvent(
|
|
200
226
|
new CustomEvent('initialize', {
|
|
@@ -254,18 +280,51 @@ export class McpServer {
|
|
|
254
280
|
this.#init_logging();
|
|
255
281
|
}
|
|
256
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Utility method to specify the type of the custom context for this server instance without the need to specify the standard schema type.
|
|
285
|
+
* @example
|
|
286
|
+
* const server = new McpServer({ ... }, { ... }).withContext<{ name: string }>();
|
|
287
|
+
* @template {Record<string, unknown>} TCustom
|
|
288
|
+
* @returns {McpServer<StandardSchema, TCustom>}
|
|
289
|
+
*/
|
|
290
|
+
withContext() {
|
|
291
|
+
return /** @type {McpServer<StandardSchema, TCustom>} */ (
|
|
292
|
+
/** @type {unknown} */ (this)
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
257
296
|
get #session_id() {
|
|
258
297
|
return this.#ctx_storage.getStore()?.sessionId;
|
|
259
298
|
}
|
|
260
299
|
|
|
300
|
+
get #progress_token() {
|
|
301
|
+
return this.#ctx_storage.getStore()?.progress_token;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* The context of the current request, include the session ID, any auth information, and custom data.
|
|
306
|
+
* @type {Context<CustomContext>}
|
|
307
|
+
*/
|
|
261
308
|
get ctx() {
|
|
262
|
-
|
|
309
|
+
// eslint-disable-next-line no-unused-vars
|
|
310
|
+
const { progress_token, ...rest } = this.#ctx_storage.getStore() ?? {};
|
|
311
|
+
return rest;
|
|
263
312
|
}
|
|
264
313
|
|
|
265
314
|
get #client_capabilities() {
|
|
266
315
|
return this.#client_capabilities_map.get(this.#session_id);
|
|
267
316
|
}
|
|
268
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Get the client information (name, version, etc.) of the client that initiated the current request...useful if you want to do something different based on the client.
|
|
320
|
+
*/
|
|
321
|
+
currentClientInfo() {
|
|
322
|
+
return this.#client_info_map.get(this.#session_id);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get the client capabilities of the client that initiated the current request, you can use this to verify the client support something before invoking the respective method.
|
|
327
|
+
*/
|
|
269
328
|
currentClientCapabilities() {
|
|
270
329
|
return this.#client_capabilities;
|
|
271
330
|
}
|
|
@@ -322,34 +381,41 @@ export class McpServer {
|
|
|
322
381
|
#init_tools() {
|
|
323
382
|
if (!this.#options.capabilities?.tools) return;
|
|
324
383
|
this.#server.addMethod('tools/list', async () => {
|
|
325
|
-
const available_tools =
|
|
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
|
-
|
|
384
|
+
const available_tools = (
|
|
385
|
+
await Promise.all(
|
|
386
|
+
[...this.#tools].map(async ([name, tool]) => {
|
|
387
|
+
if (
|
|
388
|
+
tool.enabled != null &&
|
|
389
|
+
(await safe_enabled(tool.enabled)) === false
|
|
390
|
+
)
|
|
391
|
+
return null;
|
|
392
|
+
return {
|
|
393
|
+
name,
|
|
394
|
+
title: tool.title || tool.description,
|
|
395
|
+
description: tool.description,
|
|
396
|
+
|
|
397
|
+
inputSchema: tool.schema
|
|
398
|
+
? await this.#options.adapter.toJsonSchema(
|
|
399
|
+
tool.schema,
|
|
400
|
+
)
|
|
401
|
+
: { type: 'object', properties: {} },
|
|
402
|
+
...(tool.outputSchema
|
|
403
|
+
? {
|
|
404
|
+
outputSchema:
|
|
405
|
+
await this.#options.adapter.toJsonSchema(
|
|
406
|
+
tool.outputSchema,
|
|
407
|
+
),
|
|
408
|
+
}
|
|
409
|
+
: {}),
|
|
410
|
+
...(tool.annotations
|
|
411
|
+
? {
|
|
412
|
+
annotations: tool.annotations,
|
|
413
|
+
}
|
|
414
|
+
: {}),
|
|
415
|
+
};
|
|
416
|
+
}),
|
|
417
|
+
)
|
|
418
|
+
).filter((tool) => tool !== null);
|
|
353
419
|
return {
|
|
354
420
|
tools: available_tools,
|
|
355
421
|
};
|
|
@@ -419,39 +485,49 @@ export class McpServer {
|
|
|
419
485
|
#init_prompts() {
|
|
420
486
|
if (!this.#options.capabilities?.prompts) return;
|
|
421
487
|
this.#server.addMethod('prompts/list', async ({ cursor } = {}) => {
|
|
422
|
-
const all_prompts =
|
|
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
|
-
|
|
488
|
+
const all_prompts = (
|
|
489
|
+
await Promise.all(
|
|
490
|
+
[...this.#prompts].map(async ([name, prompt]) => {
|
|
491
|
+
if (
|
|
492
|
+
prompt.enabled != null &&
|
|
493
|
+
(await safe_enabled(prompt.enabled)) === false
|
|
494
|
+
)
|
|
495
|
+
return null;
|
|
496
|
+
const arguments_schema = prompt.schema
|
|
497
|
+
? await this.#options.adapter.toJsonSchema(
|
|
498
|
+
prompt.schema,
|
|
499
|
+
)
|
|
500
|
+
: {
|
|
501
|
+
type: 'object',
|
|
502
|
+
properties:
|
|
503
|
+
/** @type {Record<string, {description: string}>} */ ({}),
|
|
504
|
+
required: [],
|
|
505
|
+
};
|
|
506
|
+
const keys = Object.keys(
|
|
507
|
+
arguments_schema.properties ?? {},
|
|
508
|
+
);
|
|
509
|
+
const required = arguments_schema.required ?? [];
|
|
510
|
+
return {
|
|
511
|
+
name,
|
|
512
|
+
title: prompt.title || prompt.description,
|
|
513
|
+
description: prompt.description,
|
|
514
|
+
arguments: keys.map((key) => {
|
|
515
|
+
const property =
|
|
516
|
+
arguments_schema.properties?.[key];
|
|
517
|
+
const description =
|
|
518
|
+
property && property !== true
|
|
519
|
+
? property.description
|
|
520
|
+
: key;
|
|
521
|
+
return {
|
|
522
|
+
name: key,
|
|
523
|
+
required: required.includes(key),
|
|
524
|
+
description,
|
|
525
|
+
};
|
|
526
|
+
}),
|
|
527
|
+
};
|
|
528
|
+
}),
|
|
529
|
+
)
|
|
530
|
+
).filter((prompt) => prompt !== null);
|
|
455
531
|
|
|
456
532
|
const pagination_options = this.#options.pagination?.prompts;
|
|
457
533
|
if (!pagination_options || pagination_options.size == null) {
|
|
@@ -528,6 +604,11 @@ export class McpServer {
|
|
|
528
604
|
for (const [uri, { description, name, title, ...resource }] of this
|
|
529
605
|
.#resources) {
|
|
530
606
|
if (!resource.template) {
|
|
607
|
+
if (
|
|
608
|
+
resource.enabled != null &&
|
|
609
|
+
(await safe_enabled(resource.enabled)) === false
|
|
610
|
+
)
|
|
611
|
+
continue;
|
|
531
612
|
all_resources.push({
|
|
532
613
|
name,
|
|
533
614
|
title: title || description,
|
|
@@ -535,6 +616,11 @@ export class McpServer {
|
|
|
535
616
|
uri,
|
|
536
617
|
});
|
|
537
618
|
} else if (resource.list_resources) {
|
|
619
|
+
if (
|
|
620
|
+
resource.enabled != null &&
|
|
621
|
+
(await safe_enabled(resource.enabled)) === false
|
|
622
|
+
)
|
|
623
|
+
continue;
|
|
538
624
|
const template_resources = await resource.list_resources();
|
|
539
625
|
all_resources.push(...template_resources);
|
|
540
626
|
}
|
|
@@ -563,20 +649,29 @@ export class McpServer {
|
|
|
563
649
|
});
|
|
564
650
|
this.#server.addMethod('resources/templates/list', async () => {
|
|
565
651
|
return {
|
|
566
|
-
resourceTemplates:
|
|
567
|
-
(
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
652
|
+
resourceTemplates: (
|
|
653
|
+
await Promise.all(
|
|
654
|
+
[...this.#resources].map(
|
|
655
|
+
async ([
|
|
656
|
+
uri,
|
|
657
|
+
{ description, name, title, template, enabled },
|
|
658
|
+
]) => {
|
|
659
|
+
if (!template) return null;
|
|
660
|
+
if (
|
|
661
|
+
enabled != null &&
|
|
662
|
+
(await safe_enabled(enabled)) === false
|
|
663
|
+
)
|
|
664
|
+
return null;
|
|
665
|
+
return {
|
|
666
|
+
name,
|
|
667
|
+
title: title || description,
|
|
668
|
+
description,
|
|
669
|
+
uriTemplate: uri,
|
|
670
|
+
};
|
|
671
|
+
},
|
|
672
|
+
),
|
|
673
|
+
)
|
|
674
|
+
).filter((resource) => resource != null),
|
|
580
675
|
};
|
|
581
676
|
});
|
|
582
677
|
this.#server.addMethod('resources/read', async ({ uri }) => {
|
|
@@ -640,9 +735,7 @@ export class McpServer {
|
|
|
640
735
|
this.roots = [];
|
|
641
736
|
}
|
|
642
737
|
}
|
|
643
|
-
|
|
644
|
-
*
|
|
645
|
-
*/
|
|
738
|
+
|
|
646
739
|
#init_completion() {
|
|
647
740
|
this.#server.addMethod(
|
|
648
741
|
'completion/complete',
|
|
@@ -660,9 +753,7 @@ export class McpServer {
|
|
|
660
753
|
},
|
|
661
754
|
);
|
|
662
755
|
}
|
|
663
|
-
|
|
664
|
-
*
|
|
665
|
-
*/
|
|
756
|
+
|
|
666
757
|
#init_logging() {
|
|
667
758
|
if (!this.#options.capabilities?.logging) return;
|
|
668
759
|
|
|
@@ -672,13 +763,25 @@ export class McpServer {
|
|
|
672
763
|
});
|
|
673
764
|
}
|
|
674
765
|
/**
|
|
766
|
+
* Add a tool to the server. If you want to receive any input you need to provide a schema. The schema needs to be a valid Standard Schema V1 schema and needs to be an Object with the properties you need,
|
|
767
|
+
* Use the description and title to help the LLM to understand what the tool does and when to use it. If you provide an outputSchema, you need to return a structuredContent that matches the schema.
|
|
768
|
+
*
|
|
769
|
+
* Tools will be invoked by the LLM when it thinks it needs to use them, you can use the annotations to provide additional information about the tool, like what it does, how to use it, etc.
|
|
675
770
|
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
676
771
|
* @template {StandardSchema | undefined} [TOutputSchema=undefined]
|
|
677
|
-
* @param {{ name: string; description: string; title?: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never; outputSchema?: StandardSchemaV1.InferOutput<TOutputSchema extends undefined ? never : TOutputSchema> extends Record<string, unknown> ? TOutputSchema : never; annotations?: ToolAnnotations }} options
|
|
772
|
+
* @param {{ name: string; description: string; title?: string; enabled?: ()=>boolean | Promise<boolean>; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never; outputSchema?: StandardSchemaV1.InferOutput<TOutputSchema extends undefined ? never : TOutputSchema> extends Record<string, unknown> ? TOutputSchema : never; annotations?: ToolAnnotations }} options
|
|
678
773
|
* @param {TSchema extends undefined ? (()=>Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>)} execute
|
|
679
774
|
*/
|
|
680
775
|
tool(
|
|
681
|
-
{
|
|
776
|
+
{
|
|
777
|
+
name,
|
|
778
|
+
description,
|
|
779
|
+
title,
|
|
780
|
+
schema,
|
|
781
|
+
outputSchema,
|
|
782
|
+
annotations,
|
|
783
|
+
enabled,
|
|
784
|
+
},
|
|
682
785
|
execute,
|
|
683
786
|
) {
|
|
684
787
|
if (this.#options.capabilities?.tools?.listChanged) {
|
|
@@ -687,6 +790,7 @@ export class McpServer {
|
|
|
687
790
|
this.#tools.set(name, {
|
|
688
791
|
description,
|
|
689
792
|
title,
|
|
793
|
+
enabled,
|
|
690
794
|
schema,
|
|
691
795
|
outputSchema,
|
|
692
796
|
execute,
|
|
@@ -694,11 +798,16 @@ export class McpServer {
|
|
|
694
798
|
});
|
|
695
799
|
}
|
|
696
800
|
/**
|
|
801
|
+
* Add a prompt to the server. Prompts are used to provide the user with pre-defined messages that adds context to the LLM.
|
|
802
|
+
* Use the description and title to help the user to understand what the prompt does and when to use it.
|
|
803
|
+
*
|
|
804
|
+
* A prompt can also have a schema that defines the input it expects, the user will be prompted to enter the inputs you request. It can also have a complete function
|
|
805
|
+
* for each input that will be used to provide completions for the user.
|
|
697
806
|
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
698
|
-
* @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
|
|
807
|
+
* @param {{ name: string; description: string; title?: string; enabled?: ()=>boolean | Promise<boolean>; 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
|
|
699
808
|
* @param {TSchema extends undefined ? (()=>Promise<GetPromptResult> | GetPromptResult) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<GetPromptResult> | GetPromptResult} execute
|
|
700
809
|
*/
|
|
701
|
-
prompt({ name, description, title, schema, complete }, execute) {
|
|
810
|
+
prompt({ name, description, title, schema, complete, enabled }, execute) {
|
|
702
811
|
if (complete) {
|
|
703
812
|
this.#completions['ref/prompt'].set(name, complete);
|
|
704
813
|
}
|
|
@@ -710,6 +819,7 @@ export class McpServer {
|
|
|
710
819
|
title,
|
|
711
820
|
schema,
|
|
712
821
|
execute,
|
|
822
|
+
enabled,
|
|
713
823
|
});
|
|
714
824
|
}
|
|
715
825
|
/**
|
|
@@ -727,32 +837,50 @@ export class McpServer {
|
|
|
727
837
|
}
|
|
728
838
|
this.#resources.set(uri, resource);
|
|
729
839
|
}
|
|
840
|
+
|
|
730
841
|
/**
|
|
731
|
-
*
|
|
842
|
+
* Add a resource to the server. Resources are added manually to the context by the user to provide the LLM with additional context.
|
|
843
|
+
* Use the description and title to help the user to understand what the resource is.
|
|
844
|
+
* @param {{ name: string; description: string; title?: string; uri: string, enabled?: ()=>boolean | Promise<boolean>; }} options
|
|
732
845
|
* @param {(uri: string) => Promise<ReadResourceResult> | ReadResourceResult} execute
|
|
733
846
|
*/
|
|
734
|
-
resource({ name, description, title, uri }, execute) {
|
|
847
|
+
resource({ name, description, title, uri, enabled }, execute) {
|
|
735
848
|
this.#resource({
|
|
736
849
|
name,
|
|
737
850
|
description,
|
|
738
851
|
title,
|
|
739
852
|
uri,
|
|
740
853
|
execute,
|
|
854
|
+
enabled,
|
|
741
855
|
template: false,
|
|
742
856
|
});
|
|
743
857
|
}
|
|
744
858
|
/**
|
|
859
|
+
* Add a resource template to the server. Resources are added manually to the context by the user to provide the LLM with additional context.
|
|
860
|
+
* Resource templates are used to create resources dynamically based on a URI template. The URI template should be a valid URI template as defined in RFC 6570.
|
|
861
|
+
* Resource templates can have a list method that returns a list of resources that match the template and a complete method that returns a list of resources given one of the template variables, this method will
|
|
862
|
+
* be invoked to provide completions for the template variables to the user.
|
|
863
|
+
* Use the description and title to help the user to understand what the resource is.
|
|
745
864
|
* @template {string} TUri
|
|
746
865
|
* @template {ExtractURITemplateVariables<TUri>} TVariables
|
|
747
|
-
* @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
|
|
866
|
+
* @param {{ name: string; description: string; title?: string; enabled?: ()=>boolean | Promise<boolean>; uri: TUri; complete?: NoInfer<TVariables extends never ? never : Partial<Record<TVariables, Completion>>>; list?: () => Promise<Array<Resource>> | Array<Resource> }} options
|
|
748
867
|
* @param {(uri: string, params: Record<TVariables, string | string[]>) => Promise<ReadResourceResult> | ReadResourceResult} execute
|
|
749
868
|
*/
|
|
750
869
|
template(
|
|
751
|
-
{
|
|
870
|
+
{
|
|
871
|
+
name,
|
|
872
|
+
description,
|
|
873
|
+
title,
|
|
874
|
+
uri,
|
|
875
|
+
complete,
|
|
876
|
+
list: list_resources,
|
|
877
|
+
enabled,
|
|
878
|
+
},
|
|
752
879
|
execute,
|
|
753
880
|
) {
|
|
754
881
|
this.#resource({
|
|
755
882
|
name,
|
|
883
|
+
enabled,
|
|
756
884
|
description,
|
|
757
885
|
title,
|
|
758
886
|
uri,
|
|
@@ -763,8 +891,10 @@ export class McpServer {
|
|
|
763
891
|
});
|
|
764
892
|
}
|
|
765
893
|
/**
|
|
894
|
+
* The main function that receive a JSONRpc message and either dispatch a `send` event or process the request.
|
|
895
|
+
*
|
|
766
896
|
* @param {JSONRPCResponse | JSONRPCRequest} message
|
|
767
|
-
* @param {Context} [ctx]
|
|
897
|
+
* @param {Context<CustomContext>} [ctx]
|
|
768
898
|
* @returns {ReturnType<JSONRPCServer['receive']> | ReturnType<JSONRPCClient['receive'] | undefined>}
|
|
769
899
|
*/
|
|
770
900
|
receive(message, ctx) {
|
|
@@ -788,8 +918,11 @@ export class McpServer {
|
|
|
788
918
|
this.#options.logging?.default ?? 'info',
|
|
789
919
|
);
|
|
790
920
|
}
|
|
921
|
+
const progress_token = /** @type {string | undefined} */ (
|
|
922
|
+
validated_message.output.params?._meta?.progressToken
|
|
923
|
+
);
|
|
791
924
|
return this.#ctx_storage.run(
|
|
792
|
-
ctx ?? {},
|
|
925
|
+
{ ...(ctx ?? {}), progress_token },
|
|
793
926
|
async () =>
|
|
794
927
|
await this.#server.receive(validated_message.output),
|
|
795
928
|
);
|
|
@@ -833,7 +966,13 @@ export class McpServer {
|
|
|
833
966
|
}
|
|
834
967
|
|
|
835
968
|
/**
|
|
836
|
-
*
|
|
969
|
+
* Emit an elicitation request to the client. Elicitations are used to ask the user for input in a structured way, the client will show a UI to the user to fill the input.
|
|
970
|
+
* The schema should be a valid Standard Schema V1 schema and should be an Object with the properties you need.
|
|
971
|
+
* The client will return the validated input as a JSON object that matches the schema.
|
|
972
|
+
*
|
|
973
|
+
* If the client doesn't support elicitation, it will throw an error.
|
|
974
|
+
*
|
|
975
|
+
* @template {StandardSchema extends undefined ? never : StandardSchema} TSchema
|
|
837
976
|
* @param {TSchema} schema
|
|
838
977
|
* @returns {Promise<StandardSchemaV1.InferOutput<TSchema>>}
|
|
839
978
|
*/
|
|
@@ -894,8 +1033,33 @@ export class McpServer {
|
|
|
894
1033
|
return v.parse(CreateMessageResultSchema, response);
|
|
895
1034
|
}
|
|
896
1035
|
|
|
1036
|
+
/**
|
|
1037
|
+
* Send a progress notification to the client. This is useful for long-running operations where you want to inform the user about the progress.
|
|
1038
|
+
*
|
|
1039
|
+
* @param {number} progress The current progress value, it should be between 0 and total and should always increase
|
|
1040
|
+
* @param {number} [total] The total value, defaults to 1
|
|
1041
|
+
* @param {string} [message] An optional message to accompany the progress update
|
|
1042
|
+
*/
|
|
1043
|
+
progress(progress, total = 1, message = undefined) {
|
|
1044
|
+
if (this.#progress_token != null) {
|
|
1045
|
+
this.#notify(
|
|
1046
|
+
'notifications/progress',
|
|
1047
|
+
{
|
|
1048
|
+
progress,
|
|
1049
|
+
total,
|
|
1050
|
+
message,
|
|
1051
|
+
progressToken: this.#progress_token,
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
sessions: this.#session_id ? [this.#session_id] : undefined,
|
|
1055
|
+
},
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
897
1060
|
/**
|
|
898
1061
|
* Log a message to the client if logging is enabled and the level is appropriate
|
|
1062
|
+
*
|
|
899
1063
|
* @param {LoggingLevel} level
|
|
900
1064
|
* @param {unknown} data
|
|
901
1065
|
* @param {string} [logger]
|
|
@@ -19,6 +19,7 @@ export type Tool<TSchema extends StandardSchemaV1 = StandardSchemaV1<any>, TOutp
|
|
|
19
19
|
outputSchema?: TOutputSchema;
|
|
20
20
|
title?: string;
|
|
21
21
|
annotations?: ToolAnnotations;
|
|
22
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
22
23
|
execute: (
|
|
23
24
|
input?: StandardSchemaV1.InferInput<TSchema>,
|
|
24
25
|
) => Promise<CallToolResult> | CallToolResult;
|
|
@@ -33,6 +34,7 @@ export type Prompt<TSchema extends StandardSchemaV1 = StandardSchemaV1<any>> = {
|
|
|
33
34
|
description: string;
|
|
34
35
|
schema: TSchema;
|
|
35
36
|
title?: string;
|
|
37
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
36
38
|
execute: (
|
|
37
39
|
input?: StandardSchemaV1.InferInput<TSchema>,
|
|
38
40
|
) => Promise<GetPromptResult> | GetPromptResult;
|
|
@@ -44,6 +46,7 @@ export type StoredResource =
|
|
|
44
46
|
name: string;
|
|
45
47
|
template: true;
|
|
46
48
|
title?: string;
|
|
49
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
47
50
|
list_resources?: () =>
|
|
48
51
|
| Promise<Array<Resource>>
|
|
49
52
|
| Array<Resource>;
|
|
@@ -57,12 +60,13 @@ export type StoredResource =
|
|
|
57
60
|
name: string;
|
|
58
61
|
template: false;
|
|
59
62
|
title?: string;
|
|
63
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
60
64
|
execute: (
|
|
61
65
|
uri: string,
|
|
62
66
|
) => Promise<ReadResourceResult> | ReadResourceResult;
|
|
63
67
|
};
|
|
64
68
|
|
|
65
|
-
export type ServerOptions<TSchema extends StandardSchemaV1> = {
|
|
69
|
+
export type ServerOptions<TSchema extends StandardSchemaV1 | undefined> = {
|
|
66
70
|
capabilities?: ServerCapabilities;
|
|
67
71
|
instructions?: string;
|
|
68
72
|
adapter: JsonSchemaAdapter<TSchema>;
|
package/src/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ declare module 'tmcp' {
|
|
|
3
3
|
import type { JSONRPCRequest, JSONRPCServer, JSONRPCClient } from 'json-rpc-2.0';
|
|
4
4
|
import type { JSONSchema7 } from 'json-schema';
|
|
5
5
|
import * as v from 'valibot';
|
|
6
|
-
export class McpServer<StandardSchema extends StandardSchemaV1> {
|
|
6
|
+
export class McpServer<StandardSchema extends StandardSchemaV1 | undefined = undefined, CustomContext extends Record<string, unknown> | undefined = undefined> {
|
|
7
7
|
|
|
8
8
|
constructor(server_info: ServerInfo, options: ServerOptions<StandardSchema>);
|
|
9
9
|
|
|
@@ -11,7 +11,27 @@ declare module 'tmcp' {
|
|
|
11
11
|
uri: string;
|
|
12
12
|
name?: string;
|
|
13
13
|
}>;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Utility method to specify the type of the custom context for this server instance without the need to specify the standard schema type.
|
|
16
|
+
* @example
|
|
17
|
+
* const server = new McpServer({ ... }, { ... }).withContext<{ name: string }>();
|
|
18
|
+
* */
|
|
19
|
+
withContext<TCustom extends Record<string, unknown>>(): McpServer<StandardSchema, TCustom>;
|
|
20
|
+
/**
|
|
21
|
+
* The context of the current request, include the session ID, any auth information, and custom data.
|
|
22
|
+
* */
|
|
23
|
+
get ctx(): Context<CustomContext>;
|
|
24
|
+
/**
|
|
25
|
+
* Get the client information (name, version, etc.) of the client that initiated the current request...useful if you want to do something different based on the client.
|
|
26
|
+
*/
|
|
27
|
+
currentClientInfo(): {
|
|
28
|
+
version: string;
|
|
29
|
+
name: string;
|
|
30
|
+
title?: string | undefined;
|
|
31
|
+
} | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Get the client capabilities of the client that initiated the current request, you can use this to verify the client support something before invoking the respective method.
|
|
34
|
+
*/
|
|
15
35
|
currentClientCapabilities(): {
|
|
16
36
|
experimental?: {} | undefined;
|
|
17
37
|
sampling?: {} | undefined;
|
|
@@ -22,41 +42,68 @@ declare module 'tmcp' {
|
|
|
22
42
|
} | undefined;
|
|
23
43
|
|
|
24
44
|
on<TEvent extends keyof McpEvents>(event: TEvent, callback: McpEvents[TEvent], options?: AddEventListenerOptions): () => void;
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Add a tool to the server. If you want to receive any input you need to provide a schema. The schema needs to be a valid Standard Schema V1 schema and needs to be an Object with the properties you need,
|
|
47
|
+
* Use the description and title to help the LLM to understand what the tool does and when to use it. If you provide an outputSchema, you need to return a structuredContent that matches the schema.
|
|
48
|
+
*
|
|
49
|
+
* Tools will be invoked by the LLM when it thinks it needs to use them, you can use the annotations to provide additional information about the tool, like what it does, how to use it, etc.
|
|
50
|
+
* */
|
|
51
|
+
tool<TSchema extends StandardSchema | undefined = undefined, TOutputSchema extends StandardSchema | undefined = undefined>({ name, description, title, schema, outputSchema, annotations, enabled, }: {
|
|
27
52
|
name: string;
|
|
28
53
|
description: string;
|
|
29
54
|
title?: string;
|
|
55
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
30
56
|
schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never;
|
|
31
57
|
outputSchema?: StandardSchemaV1.InferOutput<TOutputSchema extends undefined ? never : TOutputSchema> extends Record<string, unknown> ? TOutputSchema : never;
|
|
32
58
|
annotations?: ToolAnnotations;
|
|
33
59
|
}, execute: TSchema extends undefined ? (() => Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>)): void;
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Add a prompt to the server. Prompts are used to provide the user with pre-defined messages that adds context to the LLM.
|
|
62
|
+
* Use the description and title to help the user to understand what the prompt does and when to use it.
|
|
63
|
+
*
|
|
64
|
+
* A prompt can also have a schema that defines the input it expects, the user will be prompted to enter the inputs you request. It can also have a complete function
|
|
65
|
+
* for each input that will be used to provide completions for the user.
|
|
66
|
+
* */
|
|
67
|
+
prompt<TSchema extends StandardSchema | undefined = undefined>({ name, description, title, schema, complete, enabled }: {
|
|
36
68
|
name: string;
|
|
37
69
|
description: string;
|
|
38
70
|
title?: string;
|
|
71
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
39
72
|
schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never;
|
|
40
73
|
complete?: NoInfer<TSchema extends undefined ? never : Partial<Record<keyof StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>, Completion>>>;
|
|
41
74
|
}, execute: TSchema extends undefined ? (() => Promise<GetPromptResult> | GetPromptResult) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<GetPromptResult> | GetPromptResult): void;
|
|
42
|
-
|
|
43
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Add a resource to the server. Resources are added manually to the context by the user to provide the LLM with additional context.
|
|
77
|
+
* Use the description and title to help the user to understand what the resource is.
|
|
78
|
+
* */
|
|
79
|
+
resource({ name, description, title, uri, enabled }: {
|
|
44
80
|
name: string;
|
|
45
81
|
description: string;
|
|
46
82
|
title?: string;
|
|
47
83
|
uri: string;
|
|
84
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
48
85
|
}, execute: (uri: string) => Promise<ReadResourceResult> | ReadResourceResult): void;
|
|
49
|
-
|
|
50
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Add a resource template to the server. Resources are added manually to the context by the user to provide the LLM with additional context.
|
|
88
|
+
* Resource templates are used to create resources dynamically based on a URI template. The URI template should be a valid URI template as defined in RFC 6570.
|
|
89
|
+
* Resource templates can have a list method that returns a list of resources that match the template and a complete method that returns a list of resources given one of the template variables, this method will
|
|
90
|
+
* be invoked to provide completions for the template variables to the user.
|
|
91
|
+
* Use the description and title to help the user to understand what the resource is.
|
|
92
|
+
* */
|
|
93
|
+
template<TUri extends string, TVariables extends ExtractURITemplateVariables<TUri>>({ name, description, title, uri, complete, list: list_resources, enabled, }: {
|
|
51
94
|
name: string;
|
|
52
95
|
description: string;
|
|
53
96
|
title?: string;
|
|
97
|
+
enabled?: () => boolean | Promise<boolean>;
|
|
54
98
|
uri: TUri;
|
|
55
99
|
complete?: NoInfer<TVariables extends never ? never : Partial<Record<TVariables, Completion>>>;
|
|
56
100
|
list?: () => Promise<Array<Resource>> | Array<Resource>;
|
|
57
101
|
}, execute: (uri: string, params: Record<TVariables, string | string[]>) => Promise<ReadResourceResult> | ReadResourceResult): void;
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
/**
|
|
103
|
+
* The main function that receive a JSONRpc message and either dispatch a `send` event or process the request.
|
|
104
|
+
*
|
|
105
|
+
* */
|
|
106
|
+
receive(message: JSONRPCResponse | JSONRPCRequest, ctx?: Context<CustomContext>): ReturnType<JSONRPCServer["receive"]> | ReturnType<JSONRPCClient["receive"] | undefined>;
|
|
60
107
|
/**
|
|
61
108
|
* Send a notification for subscriptions
|
|
62
109
|
* */
|
|
@@ -65,14 +112,30 @@ declare module 'tmcp' {
|
|
|
65
112
|
* Refresh roots list from client
|
|
66
113
|
*/
|
|
67
114
|
refreshRoots(): Promise<void>;
|
|
68
|
-
|
|
69
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Emit an elicitation request to the client. Elicitations are used to ask the user for input in a structured way, the client will show a UI to the user to fill the input.
|
|
117
|
+
* The schema should be a valid Standard Schema V1 schema and should be an Object with the properties you need.
|
|
118
|
+
* The client will return the validated input as a JSON object that matches the schema.
|
|
119
|
+
*
|
|
120
|
+
* If the client doesn't support elicitation, it will throw an error.
|
|
121
|
+
*
|
|
122
|
+
* */
|
|
123
|
+
elicitation<TSchema extends StandardSchema extends undefined ? never : StandardSchema>(schema: TSchema): Promise<StandardSchemaV1.InferOutput<TSchema>>;
|
|
70
124
|
/**
|
|
71
125
|
* Request language model sampling from the client
|
|
72
126
|
* */
|
|
73
127
|
message(request: CreateMessageRequestParams): Promise<CreateMessageResult>;
|
|
128
|
+
/**
|
|
129
|
+
* Send a progress notification to the client. This is useful for long-running operations where you want to inform the user about the progress.
|
|
130
|
+
*
|
|
131
|
+
* @param progress The current progress value, it should be between 0 and total and should always increase
|
|
132
|
+
* @param total The total value, defaults to 1
|
|
133
|
+
* @param message An optional message to accompany the progress update
|
|
134
|
+
*/
|
|
135
|
+
progress(progress: number, total?: number, message?: string): void;
|
|
74
136
|
/**
|
|
75
137
|
* Log a message to the client if logging is enabled and the level is appropriate
|
|
138
|
+
*
|
|
76
139
|
*
|
|
77
140
|
*/
|
|
78
141
|
log(level: LoggingLevel, data: unknown, logger?: string): void;
|
|
@@ -109,9 +172,10 @@ declare module 'tmcp' {
|
|
|
109
172
|
*/
|
|
110
173
|
extra?: Record<string, unknown> | undefined;
|
|
111
174
|
};
|
|
112
|
-
export type Context = {
|
|
175
|
+
export type Context<TCustom extends Record<string, unknown> | undefined = undefined> = {
|
|
113
176
|
sessionId?: string | undefined;
|
|
114
177
|
auth?: AuthInfo | undefined;
|
|
178
|
+
custom?: TCustom | undefined;
|
|
115
179
|
};
|
|
116
180
|
export type ClientCapabilities = ClientCapabilities_1;
|
|
117
181
|
type Completion = (
|
|
@@ -119,7 +183,7 @@ declare module 'tmcp' {
|
|
|
119
183
|
context: { arguments: Record<string, string> },
|
|
120
184
|
) => CompleteResult | Promise<CompleteResult>;
|
|
121
185
|
|
|
122
|
-
type ServerOptions<TSchema extends StandardSchemaV1> = {
|
|
186
|
+
type ServerOptions<TSchema extends StandardSchemaV1 | undefined> = {
|
|
123
187
|
capabilities?: ServerCapabilities;
|
|
124
188
|
instructions?: string;
|
|
125
189
|
adapter: JsonSchemaAdapter<TSchema>;
|
package/src/types/index.d.ts.map
CHANGED
|
@@ -55,6 +55,6 @@
|
|
|
55
55
|
null,
|
|
56
56
|
null
|
|
57
57
|
],
|
|
58
|
-
"mappings": ";;;;;
|
|
58
|
+
"mappings": ";;;;;cAqGaA,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aCk2CsCC,kBAAkBA;MC56ClEC,UAAUA;;;;;MAyCVC,aAAaA;;;;;;;;;;;;;MAabC,UAAUA;;;;;;MAMVC,iBAAiBA;;MAEjBC,SAASA;;;;;;;;;;;;ODWRC,qBAAqBA;;;;;;;;;;;;;;OAuGrBC,wBAAwBA;;;;;;;;;;;;;;;;;;;;;;;OA6BxBC,6BAA6BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuB7BC,wBAAwBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmMxBC,cAAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4GdC,wBAAwBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8PxBC,qBAAqBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BrBC,qBAAqBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2GrBC,oBAAoBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoEpBC,kBAAkBA;OAuGlBC,gCAAgCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8ChCC,yBAAyBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+LzBC,oBAAoBA;;;;;;;;;;;;;;;;;;;;;;MA8H2BC,kBAAkBA;MASbC,uBAAuBA;;;;;;MAO5BC,kBAAkBA;MAGrBC,eAAeA;MAGhBC,cAAcA;MAGFC,0BAA0BA;MAGjCC,mBAAmBA;MAY9BC,QAAQA;MAMDC,eAAeA;MAGlBC,YAAYA;MAGTC,eAAeA;;ME7/CnEC,IAAIA;;;;;;;MAOJC,cAAcA;;;;;;;MAOdC,cAAcA;;;;;MAKdC,qBAAqBA;;;;;;;;;;;;;;;;;MAiBrBC,4BAA4BA;;;;;;MAMrBC,2BAA2BA;;;;;;;OCjC1BC,iBAAiBA;;;;;;;;;;;;;;;;cAAjBA,iBAAiBA",
|
|
59
59
|
"ignoreList": []
|
|
60
60
|
}
|