@modelcontextprotocol/server-everything 2025.12.18 → 2026.1.26
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 +9 -158
- package/dist/docs/architecture.md +44 -0
- package/dist/docs/extension.md +23 -0
- package/dist/docs/features.md +103 -0
- package/dist/docs/how-it-works.md +45 -0
- package/dist/docs/instructions.md +28 -0
- package/dist/docs/startup.md +73 -0
- package/dist/docs/structure.md +182 -0
- package/dist/index.js +19 -14
- package/dist/prompts/args.js +34 -0
- package/dist/prompts/completions.js +52 -0
- package/dist/prompts/index.js +15 -0
- package/dist/prompts/resource.js +60 -0
- package/dist/prompts/simple.js +23 -0
- package/dist/resources/files.js +83 -0
- package/dist/resources/index.js +33 -0
- package/dist/resources/session.js +44 -0
- package/dist/resources/subscriptions.js +125 -0
- package/dist/resources/templates.js +171 -0
- package/dist/server/index.js +93 -0
- package/dist/server/logging.js +64 -0
- package/dist/server/roots.js +65 -0
- package/dist/tools/echo.js +29 -0
- package/dist/tools/get-annotated-message.js +81 -0
- package/dist/tools/get-env.js +28 -0
- package/dist/tools/get-resource-links.js +62 -0
- package/dist/tools/get-resource-reference.js +74 -0
- package/dist/tools/get-roots-list.js +71 -0
- package/dist/tools/get-structured-content.js +72 -0
- package/dist/tools/get-sum.js +40 -0
- package/dist/tools/get-tiny-image.js +41 -0
- package/dist/tools/gzip-file-as-resource.js +182 -0
- package/dist/tools/index.js +50 -0
- package/dist/tools/simulate-research-query.js +249 -0
- package/dist/tools/toggle-simulated-logging.js +41 -0
- package/dist/tools/toggle-subscriber-updates.js +44 -0
- package/dist/tools/trigger-elicitation-request-async.js +202 -0
- package/dist/tools/trigger-elicitation-request.js +210 -0
- package/dist/tools/trigger-long-running-operation.js +59 -0
- package/dist/tools/trigger-sampling-request-async.js +168 -0
- package/dist/tools/trigger-sampling-request.js +71 -0
- package/dist/{sse.js → transports/sse.js} +25 -17
- package/dist/transports/stdio.js +27 -0
- package/dist/transports/streamableHttp.js +206 -0
- package/package.json +10 -7
- package/dist/everything.js +0 -978
- package/dist/instructions.md +0 -23
- package/dist/stdio.js +0 -23
- package/dist/streamableHttp.js +0 -174
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
// Track subscriber session id lists by URI
|
|
3
|
+
const subscriptions = new Map();
|
|
4
|
+
// Interval to send notifications to subscribers
|
|
5
|
+
const subsUpdateIntervals = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Sets up the subscription and unsubscription handlers for the provided server.
|
|
8
|
+
*
|
|
9
|
+
* The function defines two request handlers:
|
|
10
|
+
* 1. A `Subscribe` handler that allows clients to subscribe to specific resource URIs.
|
|
11
|
+
* 2. An `Unsubscribe` handler that allows clients to unsubscribe from specific resource URIs.
|
|
12
|
+
*
|
|
13
|
+
* The `Subscribe` handler performs the following actions:
|
|
14
|
+
* - Extracts the URI and session ID from the request.
|
|
15
|
+
* - Logs a message acknowledging the subscription request.
|
|
16
|
+
* - Updates the internal tracking of subscribers for the given URI.
|
|
17
|
+
*
|
|
18
|
+
* The `Unsubscribe` handler performs the following actions:
|
|
19
|
+
* - Extracts the URI and session ID from the request.
|
|
20
|
+
* - Logs a message acknowledging the unsubscription request.
|
|
21
|
+
* - Removes the subscriber for the specified URI.
|
|
22
|
+
*
|
|
23
|
+
* @param {McpServer} server - The server instance to which subscription handlers will be attached.
|
|
24
|
+
*/
|
|
25
|
+
export const setSubscriptionHandlers = (server) => {
|
|
26
|
+
// Set the subscription handler
|
|
27
|
+
server.server.setRequestHandler(SubscribeRequestSchema, async (request, extra) => {
|
|
28
|
+
// Get the URI to subscribe to
|
|
29
|
+
const { uri } = request.params;
|
|
30
|
+
// Get the session id (can be undefined for stdio)
|
|
31
|
+
const sessionId = extra.sessionId;
|
|
32
|
+
// Acknowledge the subscribe request
|
|
33
|
+
await server.sendLoggingMessage({
|
|
34
|
+
level: "info",
|
|
35
|
+
data: `Received Subscribe Resource request for URI: ${uri} ${sessionId ? `from session ${sessionId}` : ""}`,
|
|
36
|
+
}, sessionId);
|
|
37
|
+
// Get the subscribers for this URI
|
|
38
|
+
const subscribers = subscriptions.has(uri)
|
|
39
|
+
? subscriptions.get(uri)
|
|
40
|
+
: new Set();
|
|
41
|
+
subscribers.add(sessionId);
|
|
42
|
+
subscriptions.set(uri, subscribers);
|
|
43
|
+
return {};
|
|
44
|
+
});
|
|
45
|
+
// Set the unsubscription handler
|
|
46
|
+
server.server.setRequestHandler(UnsubscribeRequestSchema, async (request, extra) => {
|
|
47
|
+
// Get the URI to subscribe to
|
|
48
|
+
const { uri } = request.params;
|
|
49
|
+
// Get the session id (can be undefined for stdio)
|
|
50
|
+
const sessionId = extra.sessionId;
|
|
51
|
+
// Acknowledge the subscribe request
|
|
52
|
+
await server.sendLoggingMessage({
|
|
53
|
+
level: "info",
|
|
54
|
+
data: `Received Unsubscribe Resource request: ${uri} ${sessionId ? `from session ${sessionId}` : ""}`,
|
|
55
|
+
}, sessionId);
|
|
56
|
+
// Remove the subscriber
|
|
57
|
+
if (subscriptions.has(uri)) {
|
|
58
|
+
const subscribers = subscriptions.get(uri);
|
|
59
|
+
if (subscribers.has(sessionId))
|
|
60
|
+
subscribers.delete(sessionId);
|
|
61
|
+
}
|
|
62
|
+
return {};
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Sends simulated resource update notifications to the subscribed client.
|
|
67
|
+
*
|
|
68
|
+
* This function iterates through all resource URIs stored in the subscriptions
|
|
69
|
+
* and checks if the specified session ID is subscribed to them. If so, it sends
|
|
70
|
+
* a notification through the provided server. If the session ID is no longer valid
|
|
71
|
+
* (disconnected), it removes the session ID from the list of subscribers.
|
|
72
|
+
*
|
|
73
|
+
* @param {McpServer} server - The server instance used to send notifications.
|
|
74
|
+
* @param {string | undefined} sessionId - The session ID of the client to check for subscriptions.
|
|
75
|
+
* @returns {Promise<void>} Resolves once all applicable notifications are sent.
|
|
76
|
+
*/
|
|
77
|
+
const sendSimulatedResourceUpdates = async (server, sessionId) => {
|
|
78
|
+
// Search all URIs for ones this client is subscribed to
|
|
79
|
+
for (const uri of subscriptions.keys()) {
|
|
80
|
+
const subscribers = subscriptions.get(uri);
|
|
81
|
+
// If this client is subscribed, send the notification
|
|
82
|
+
if (subscribers.has(sessionId)) {
|
|
83
|
+
await server.server.notification({
|
|
84
|
+
method: "notifications/resources/updated",
|
|
85
|
+
params: { uri },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
subscribers.delete(sessionId); // subscriber has disconnected
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Starts the process of simulating resource updates and sending server notifications
|
|
95
|
+
* to the client for the resources they are subscribed to. If the update interval is
|
|
96
|
+
* already active, invoking this function will not start another interval.
|
|
97
|
+
*
|
|
98
|
+
* @param server
|
|
99
|
+
* @param sessionId
|
|
100
|
+
*/
|
|
101
|
+
export const beginSimulatedResourceUpdates = (server, sessionId) => {
|
|
102
|
+
if (!subsUpdateIntervals.has(sessionId)) {
|
|
103
|
+
// Send once immediately
|
|
104
|
+
sendSimulatedResourceUpdates(server, sessionId);
|
|
105
|
+
// Set the interval to send later resource update notifications to this client
|
|
106
|
+
subsUpdateIntervals.set(sessionId, setInterval(() => sendSimulatedResourceUpdates(server, sessionId), 5000));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Stops simulated resource updates for a given session.
|
|
111
|
+
*
|
|
112
|
+
* This function halts any active intervals associated with the provided session ID
|
|
113
|
+
* and removes the session's corresponding entries from resource management collections.
|
|
114
|
+
* Session ID can be undefined for stdio.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} [sessionId]
|
|
117
|
+
*/
|
|
118
|
+
export const stopSimulatedResourceUpdates = (sessionId) => {
|
|
119
|
+
// Remove active intervals
|
|
120
|
+
if (subsUpdateIntervals.has(sessionId)) {
|
|
121
|
+
const subsUpdateInterval = subsUpdateIntervals.get(sessionId);
|
|
122
|
+
clearInterval(subsUpdateInterval);
|
|
123
|
+
subsUpdateIntervals.delete(sessionId);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
|
|
4
|
+
// Resource types
|
|
5
|
+
export const RESOURCE_TYPE_TEXT = "Text";
|
|
6
|
+
export const RESOURCE_TYPE_BLOB = "Blob";
|
|
7
|
+
export const RESOURCE_TYPES = [
|
|
8
|
+
RESOURCE_TYPE_TEXT,
|
|
9
|
+
RESOURCE_TYPE_BLOB,
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* A completer function for resource types.
|
|
13
|
+
*
|
|
14
|
+
* This variable provides functionality to perform autocompletion for the resource types based on user input.
|
|
15
|
+
* It uses a schema description to validate the input and filters through a predefined list of resource types
|
|
16
|
+
* to return suggestions that start with the given input.
|
|
17
|
+
*
|
|
18
|
+
* The input value is expected to be a string representing the type of resource to fetch.
|
|
19
|
+
* The completion logic matches the input against available resource types.
|
|
20
|
+
*/
|
|
21
|
+
export const resourceTypeCompleter = completable(z.string().describe("Type of resource to fetch"), (value) => {
|
|
22
|
+
return RESOURCE_TYPES.filter((t) => t.startsWith(value));
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* A completer function for resource IDs as strings.
|
|
26
|
+
*
|
|
27
|
+
* The `resourceIdCompleter` accepts a string input representing the ID of a text resource
|
|
28
|
+
* and validates whether the provided value corresponds to an integer resource ID.
|
|
29
|
+
*
|
|
30
|
+
* NOTE: Currently, prompt arguments can only be strings since type is not field of `PromptArgument`
|
|
31
|
+
* Consequently, we must define it as a string and convert the argument to number before using it
|
|
32
|
+
* https://modelcontextprotocol.io/specification/2025-11-25/schema#promptargument
|
|
33
|
+
*
|
|
34
|
+
* If the value is a valid integer, it returns the value within an array.
|
|
35
|
+
* Otherwise, it returns an empty array.
|
|
36
|
+
*
|
|
37
|
+
* The input string is first transformed into a number and checked to ensure it is an integer.
|
|
38
|
+
* This helps validate and suggest appropriate resource IDs.
|
|
39
|
+
*/
|
|
40
|
+
export const resourceIdForPromptCompleter = completable(z.string().describe("ID of the text resource to fetch"), (value) => {
|
|
41
|
+
const resourceId = Number(value);
|
|
42
|
+
return Number.isInteger(resourceId) && resourceId > 0 ? [value] : [];
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* A callback function that acts as a completer for resource ID values, validating and returning
|
|
46
|
+
* the input value as part of a resource template.
|
|
47
|
+
*
|
|
48
|
+
* @typedef {CompleteResourceTemplateCallback}
|
|
49
|
+
* @param {string} value - The input string value to be evaluated as a resource ID.
|
|
50
|
+
* @returns {string[]} Returns an array containing the input value if it represents a positive
|
|
51
|
+
* integer resource ID, otherwise returns an empty array.
|
|
52
|
+
*/
|
|
53
|
+
export const resourceIdForResourceTemplateCompleter = (value) => {
|
|
54
|
+
const resourceId = Number(value);
|
|
55
|
+
return Number.isInteger(resourceId) && resourceId > 0 ? [value] : [];
|
|
56
|
+
};
|
|
57
|
+
const uriBase = "demo://resource/dynamic";
|
|
58
|
+
const textUriBase = `${uriBase}/text`;
|
|
59
|
+
const blobUriBase = `${uriBase}/blob`;
|
|
60
|
+
const textUriTemplate = `${textUriBase}/{resourceId}`;
|
|
61
|
+
const blobUriTemplate = `${blobUriBase}/{resourceId}`;
|
|
62
|
+
/**
|
|
63
|
+
* Create a dynamic text resource
|
|
64
|
+
* - Exposed for use by embedded resource prompt example
|
|
65
|
+
* @param uri
|
|
66
|
+
* @param resourceId
|
|
67
|
+
*/
|
|
68
|
+
export const textResource = (uri, resourceId) => {
|
|
69
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
70
|
+
return {
|
|
71
|
+
uri: uri.toString(),
|
|
72
|
+
mimeType: "text/plain",
|
|
73
|
+
text: `Resource ${resourceId}: This is a plaintext resource created at ${timestamp}`,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Create a dynamic blob resource
|
|
78
|
+
* - Exposed for use by embedded resource prompt example
|
|
79
|
+
* @param uri
|
|
80
|
+
* @param resourceId
|
|
81
|
+
*/
|
|
82
|
+
export const blobResource = (uri, resourceId) => {
|
|
83
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
84
|
+
const resourceText = Buffer.from(`Resource ${resourceId}: This is a base64 blob created at ${timestamp}`).toString("base64");
|
|
85
|
+
return {
|
|
86
|
+
uri: uri.toString(),
|
|
87
|
+
mimeType: "text/plain",
|
|
88
|
+
blob: resourceText,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Create a dynamic text resource URI
|
|
93
|
+
* - Exposed for use by embedded resource prompt example
|
|
94
|
+
* @param resourceId
|
|
95
|
+
*/
|
|
96
|
+
export const textResourceUri = (resourceId) => new URL(`${textUriBase}/${resourceId}`);
|
|
97
|
+
/**
|
|
98
|
+
* Create a dynamic blob resource URI
|
|
99
|
+
* - Exposed for use by embedded resource prompt example
|
|
100
|
+
* @param resourceId
|
|
101
|
+
*/
|
|
102
|
+
export const blobResourceUri = (resourceId) => new URL(`${blobUriBase}/${resourceId}`);
|
|
103
|
+
/**
|
|
104
|
+
* Parses the resource identifier from the provided URI and validates it
|
|
105
|
+
* against the given variables. Throws an error if the URI corresponds
|
|
106
|
+
* to an unknown resource or if the resource identifier is invalid.
|
|
107
|
+
*
|
|
108
|
+
* @param {URL} uri - The URI of the resource to be parsed.
|
|
109
|
+
* @param {Record<string, unknown>} variables - A record containing context-specific variables that include the resourceId.
|
|
110
|
+
* @returns {number} The parsed and validated resource identifier as an integer.
|
|
111
|
+
* @throws {Error} Throws an error if the URI matches unsupported base URIs or if the resourceId is invalid.
|
|
112
|
+
*/
|
|
113
|
+
const parseResourceId = (uri, variables) => {
|
|
114
|
+
const uriError = `Unknown resource: ${uri.toString()}`;
|
|
115
|
+
if (uri.toString().startsWith(textUriBase) &&
|
|
116
|
+
uri.toString().startsWith(blobUriBase)) {
|
|
117
|
+
throw new Error(uriError);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const idxStr = String(variables.resourceId ?? "");
|
|
121
|
+
const idx = Number(idxStr);
|
|
122
|
+
if (Number.isFinite(idx) && Number.isInteger(idx) && idx > 0) {
|
|
123
|
+
return idx;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
throw new Error(uriError);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Register resource templates with the MCP server.
|
|
132
|
+
* - Text and blob resources, dynamically generated from the URI {resourceId} variable
|
|
133
|
+
* - Any finite positive integer is acceptable for the resourceId variable
|
|
134
|
+
* - List resources method will not return these resources
|
|
135
|
+
* - These are only accessible via template URIs
|
|
136
|
+
* - Both blob and text resources:
|
|
137
|
+
* - have content that is dynamically generated, including a timestamp
|
|
138
|
+
* - have different template URIs
|
|
139
|
+
* - Blob: "demo://resource/dynamic/blob/{resourceId}"
|
|
140
|
+
* - Text: "demo://resource/dynamic/text/{resourceId}"
|
|
141
|
+
*
|
|
142
|
+
* @param server
|
|
143
|
+
*/
|
|
144
|
+
export const registerResourceTemplates = (server) => {
|
|
145
|
+
// Register the text resource template
|
|
146
|
+
server.registerResource("Dynamic Text Resource", new ResourceTemplate(textUriTemplate, {
|
|
147
|
+
list: undefined,
|
|
148
|
+
complete: { resourceId: resourceIdForResourceTemplateCompleter },
|
|
149
|
+
}), {
|
|
150
|
+
mimeType: "text/plain",
|
|
151
|
+
description: "Plaintext dynamic resource fabricated from the {resourceId} variable, which must be an integer.",
|
|
152
|
+
}, async (uri, variables) => {
|
|
153
|
+
const resourceId = parseResourceId(uri, variables);
|
|
154
|
+
return {
|
|
155
|
+
contents: [textResource(uri, resourceId)],
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
// Register the blob resource template
|
|
159
|
+
server.registerResource("Dynamic Blob Resource", new ResourceTemplate(blobUriTemplate, {
|
|
160
|
+
list: undefined,
|
|
161
|
+
complete: { resourceId: resourceIdForResourceTemplateCompleter },
|
|
162
|
+
}), {
|
|
163
|
+
mimeType: "application/octet-stream",
|
|
164
|
+
description: "Binary (base64) dynamic resource fabricated from the {resourceId} variable, which must be an integer.",
|
|
165
|
+
}, async (uri, variables) => {
|
|
166
|
+
const resourceId = parseResourceId(uri, variables);
|
|
167
|
+
return {
|
|
168
|
+
contents: [blobResource(uri, resourceId)],
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { InMemoryTaskStore, InMemoryTaskMessageQueue, } from "@modelcontextprotocol/sdk/experimental/tasks";
|
|
3
|
+
import { setSubscriptionHandlers, stopSimulatedResourceUpdates, } from "../resources/subscriptions.js";
|
|
4
|
+
import { registerConditionalTools, registerTools } from "../tools/index.js";
|
|
5
|
+
import { registerResources, readInstructions } from "../resources/index.js";
|
|
6
|
+
import { registerPrompts } from "../prompts/index.js";
|
|
7
|
+
import { stopSimulatedLogging } from "./logging.js";
|
|
8
|
+
import { syncRoots } from "./roots.js";
|
|
9
|
+
/**
|
|
10
|
+
* Server Factory
|
|
11
|
+
*
|
|
12
|
+
* This function initializes a `McpServer` with specific capabilities and instructions,
|
|
13
|
+
* registers tools, resources, and prompts, and configures resource subscription handlers.
|
|
14
|
+
*
|
|
15
|
+
* @returns {ServerFactoryResponse} An object containing the server instance, and a `cleanup`
|
|
16
|
+
* function for handling server-side cleanup when a session ends.
|
|
17
|
+
*
|
|
18
|
+
* Properties of the returned object:
|
|
19
|
+
* - `server` {Object}: The initialized server instance.
|
|
20
|
+
* - `cleanup` {Function}: Function to perform cleanup operations for a closing session.
|
|
21
|
+
*/
|
|
22
|
+
export const createServer = () => {
|
|
23
|
+
// Read the server instructions
|
|
24
|
+
const instructions = readInstructions();
|
|
25
|
+
// Create task store and message queue for task support
|
|
26
|
+
const taskStore = new InMemoryTaskStore();
|
|
27
|
+
const taskMessageQueue = new InMemoryTaskMessageQueue();
|
|
28
|
+
let initializeTimeout = null;
|
|
29
|
+
// Create the server
|
|
30
|
+
const server = new McpServer({
|
|
31
|
+
name: "mcp-servers/everything",
|
|
32
|
+
title: "Everything Reference Server",
|
|
33
|
+
version: "2.0.0",
|
|
34
|
+
}, {
|
|
35
|
+
capabilities: {
|
|
36
|
+
tools: {
|
|
37
|
+
listChanged: true,
|
|
38
|
+
},
|
|
39
|
+
prompts: {
|
|
40
|
+
listChanged: true,
|
|
41
|
+
},
|
|
42
|
+
resources: {
|
|
43
|
+
subscribe: true,
|
|
44
|
+
listChanged: true,
|
|
45
|
+
},
|
|
46
|
+
logging: {},
|
|
47
|
+
tasks: {
|
|
48
|
+
list: {},
|
|
49
|
+
cancel: {},
|
|
50
|
+
requests: {
|
|
51
|
+
tools: {
|
|
52
|
+
call: {},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
instructions,
|
|
58
|
+
taskStore,
|
|
59
|
+
taskMessageQueue,
|
|
60
|
+
});
|
|
61
|
+
// Register the tools
|
|
62
|
+
registerTools(server);
|
|
63
|
+
// Register the resources
|
|
64
|
+
registerResources(server);
|
|
65
|
+
// Register the prompts
|
|
66
|
+
registerPrompts(server);
|
|
67
|
+
// Set resource subscription handlers
|
|
68
|
+
setSubscriptionHandlers(server);
|
|
69
|
+
// Perform post-initialization operations
|
|
70
|
+
server.server.oninitialized = async () => {
|
|
71
|
+
// Register conditional tools now that client capabilities are known.
|
|
72
|
+
// This finishes before the `notifications/initialized` handler finishes.
|
|
73
|
+
registerConditionalTools(server);
|
|
74
|
+
// Sync roots if the client supports them.
|
|
75
|
+
// This is delayed until after the `notifications/initialized` handler finishes,
|
|
76
|
+
// otherwise, the request gets lost.
|
|
77
|
+
const sessionId = server.server.transport?.sessionId;
|
|
78
|
+
initializeTimeout = setTimeout(() => syncRoots(server, sessionId), 350);
|
|
79
|
+
};
|
|
80
|
+
// Return the ServerFactoryResponse
|
|
81
|
+
return {
|
|
82
|
+
server,
|
|
83
|
+
cleanup: (sessionId) => {
|
|
84
|
+
// Stop any simulated logging or resource updates that may have been initiated.
|
|
85
|
+
stopSimulatedLogging(sessionId);
|
|
86
|
+
stopSimulatedResourceUpdates(sessionId);
|
|
87
|
+
// Clean up task store timers
|
|
88
|
+
taskStore.cleanup();
|
|
89
|
+
if (initializeTimeout)
|
|
90
|
+
clearTimeout(initializeTimeout);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Map session ID to the interval for sending logging messages to the client
|
|
2
|
+
const logsUpdateIntervals = new Map();
|
|
3
|
+
/**
|
|
4
|
+
* Initiates a simulated logging process by sending random log messages to the client at a
|
|
5
|
+
* fixed interval. Each log message contains a random logging level and optional session ID.
|
|
6
|
+
*
|
|
7
|
+
* @param {McpServer} server - The server instance responsible for handling the logging messages.
|
|
8
|
+
* @param {string | undefined} sessionId - An optional identifier for the session. If provided,
|
|
9
|
+
* the session ID will be appended to log messages.
|
|
10
|
+
*/
|
|
11
|
+
export const beginSimulatedLogging = (server, sessionId) => {
|
|
12
|
+
const maybeAppendSessionId = sessionId ? ` - SessionId ${sessionId}` : "";
|
|
13
|
+
const messages = [
|
|
14
|
+
{ level: "debug", data: `Debug-level message${maybeAppendSessionId}` },
|
|
15
|
+
{ level: "info", data: `Info-level message${maybeAppendSessionId}` },
|
|
16
|
+
{ level: "notice", data: `Notice-level message${maybeAppendSessionId}` },
|
|
17
|
+
{
|
|
18
|
+
level: "warning",
|
|
19
|
+
data: `Warning-level message${maybeAppendSessionId}`,
|
|
20
|
+
},
|
|
21
|
+
{ level: "error", data: `Error-level message${maybeAppendSessionId}` },
|
|
22
|
+
{
|
|
23
|
+
level: "critical",
|
|
24
|
+
data: `Critical-level message${maybeAppendSessionId}`,
|
|
25
|
+
},
|
|
26
|
+
{ level: "alert", data: `Alert level-message${maybeAppendSessionId}` },
|
|
27
|
+
{
|
|
28
|
+
level: "emergency",
|
|
29
|
+
data: `Emergency-level message${maybeAppendSessionId}`,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Send a simulated logging message to the client
|
|
34
|
+
*/
|
|
35
|
+
const sendSimulatedLoggingMessage = async (sessionId) => {
|
|
36
|
+
// By using the `sendLoggingMessage` function to send the message, we
|
|
37
|
+
// ensure that the client's chosen logging level will be respected
|
|
38
|
+
await server.sendLoggingMessage(messages[Math.floor(Math.random() * messages.length)], sessionId);
|
|
39
|
+
};
|
|
40
|
+
// Set the interval to send later logging messages to this client
|
|
41
|
+
if (!logsUpdateIntervals.has(sessionId)) {
|
|
42
|
+
// Send once immediately
|
|
43
|
+
sendSimulatedLoggingMessage(sessionId);
|
|
44
|
+
// Send a randomly-leveled log message every 5 seconds
|
|
45
|
+
logsUpdateIntervals.set(sessionId, setInterval(() => sendSimulatedLoggingMessage(sessionId), 5000));
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Stops the simulated logging process for a given session.
|
|
50
|
+
*
|
|
51
|
+
* This function halts the periodic logging updates associated with the specified
|
|
52
|
+
* session ID by clearing the interval and removing the session's tracking
|
|
53
|
+
* reference. Session ID can be undefined for stdio.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} [sessionId] - The optional unique identifier of the session.
|
|
56
|
+
*/
|
|
57
|
+
export const stopSimulatedLogging = (sessionId) => {
|
|
58
|
+
// Remove active intervals
|
|
59
|
+
if (logsUpdateIntervals.has(sessionId)) {
|
|
60
|
+
const logsUpdateInterval = logsUpdateIntervals.get(sessionId);
|
|
61
|
+
clearInterval(logsUpdateInterval);
|
|
62
|
+
logsUpdateIntervals.delete(sessionId);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { RootsListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
// Track roots by session id
|
|
3
|
+
export const roots = new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Get the latest the client roots list for the session.
|
|
6
|
+
*
|
|
7
|
+
* - Request and cache the roots list for the session if it has not been fetched before.
|
|
8
|
+
* - Return the cached roots list for the session if it exists.
|
|
9
|
+
*
|
|
10
|
+
* When requesting the roots list for a session, it also sets up a `roots/list_changed`
|
|
11
|
+
* notification handler. This ensures that updates are automatically fetched and handled
|
|
12
|
+
* in real-time.
|
|
13
|
+
*
|
|
14
|
+
* This function is idempotent. It should only request roots from the client once per session,
|
|
15
|
+
* returning the cached version thereafter.
|
|
16
|
+
*
|
|
17
|
+
* @param {McpServer} server - An instance of the MCP server used to communicate with the client.
|
|
18
|
+
* @param {string} [sessionId] - An optional session id used to associate the roots list with a specific client session.
|
|
19
|
+
*
|
|
20
|
+
* @throws {Error} In case of a failure to request the roots from the client, an error log message is sent.
|
|
21
|
+
*/
|
|
22
|
+
export const syncRoots = async (server, sessionId) => {
|
|
23
|
+
const clientCapabilities = server.server.getClientCapabilities() || {};
|
|
24
|
+
const clientSupportsRoots = clientCapabilities?.roots !== undefined;
|
|
25
|
+
// Fetch the roots list for this client
|
|
26
|
+
if (clientSupportsRoots) {
|
|
27
|
+
// Function to request the updated roots list from the client
|
|
28
|
+
const requestRoots = async () => {
|
|
29
|
+
try {
|
|
30
|
+
// Request the updated roots list from the client
|
|
31
|
+
const response = await server.server.listRoots();
|
|
32
|
+
if (response && "roots" in response) {
|
|
33
|
+
// Store the roots list for this client
|
|
34
|
+
roots.set(sessionId, response.roots);
|
|
35
|
+
// Notify the client of roots received
|
|
36
|
+
await server.sendLoggingMessage({
|
|
37
|
+
level: "info",
|
|
38
|
+
logger: "everything-server",
|
|
39
|
+
data: `Roots updated: ${response?.roots?.length} root(s) received from client`,
|
|
40
|
+
}, sessionId);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await server.sendLoggingMessage({
|
|
44
|
+
level: "info",
|
|
45
|
+
logger: "everything-server",
|
|
46
|
+
data: "Client returned no roots set",
|
|
47
|
+
}, sessionId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error(`Failed to request roots from client ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// If the roots have not been synced for this client,
|
|
55
|
+
// set notification handler and request initial roots
|
|
56
|
+
if (!roots.has(sessionId)) {
|
|
57
|
+
// Set the list changed notification handler
|
|
58
|
+
server.server.setNotificationHandler(RootsListChangedNotificationSchema, requestRoots);
|
|
59
|
+
// Request the initial roots list immediately
|
|
60
|
+
await requestRoots();
|
|
61
|
+
}
|
|
62
|
+
// Return the roots list for this client
|
|
63
|
+
return roots.get(sessionId);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Tool input schema
|
|
3
|
+
export const EchoSchema = z.object({
|
|
4
|
+
message: z.string().describe("Message to echo"),
|
|
5
|
+
});
|
|
6
|
+
// Tool configuration
|
|
7
|
+
const name = "echo";
|
|
8
|
+
const config = {
|
|
9
|
+
title: "Echo Tool",
|
|
10
|
+
description: "Echoes back the input string",
|
|
11
|
+
inputSchema: EchoSchema,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Registers the 'echo' tool.
|
|
15
|
+
*
|
|
16
|
+
* The registered tool validates input arguments using the EchoSchema and
|
|
17
|
+
* returns a response that echoes the message provided in the arguments.
|
|
18
|
+
*
|
|
19
|
+
* @param {McpServer} server - The McpServer instance where the tool will be registered.
|
|
20
|
+
* @returns {void}
|
|
21
|
+
*/
|
|
22
|
+
export const registerEchoTool = (server) => {
|
|
23
|
+
server.registerTool(name, config, async (args) => {
|
|
24
|
+
const validatedArgs = EchoSchema.parse(args);
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: `Echo: ${validatedArgs.message}` }],
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { MCP_TINY_IMAGE } from "./get-tiny-image.js";
|
|
3
|
+
// Tool input schema
|
|
4
|
+
const GetAnnotatedMessageSchema = z.object({
|
|
5
|
+
messageType: z
|
|
6
|
+
.enum(["error", "success", "debug"])
|
|
7
|
+
.describe("Type of message to demonstrate different annotation patterns"),
|
|
8
|
+
includeImage: z
|
|
9
|
+
.boolean()
|
|
10
|
+
.default(false)
|
|
11
|
+
.describe("Whether to include an example image"),
|
|
12
|
+
});
|
|
13
|
+
// Tool configuration
|
|
14
|
+
const name = "get-annotated-message";
|
|
15
|
+
const config = {
|
|
16
|
+
title: "Get Annotated Message Tool",
|
|
17
|
+
description: "Demonstrates how annotations can be used to provide metadata about content.",
|
|
18
|
+
inputSchema: GetAnnotatedMessageSchema,
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Registers the 'get-annotated-message' tool.
|
|
22
|
+
*
|
|
23
|
+
* The registered tool generates and sends messages with specific types, such as error,
|
|
24
|
+
* success, or debug, carrying associated annotations like priority level and intended
|
|
25
|
+
* audience.
|
|
26
|
+
*
|
|
27
|
+
* The response will have annotations and optionally contain an annotated image.
|
|
28
|
+
*
|
|
29
|
+
* @function
|
|
30
|
+
* @param {McpServer} server - The McpServer instance where the tool will be registered.
|
|
31
|
+
*/
|
|
32
|
+
export const registerGetAnnotatedMessageTool = (server) => {
|
|
33
|
+
server.registerTool(name, config, async (args) => {
|
|
34
|
+
const { messageType, includeImage } = GetAnnotatedMessageSchema.parse(args);
|
|
35
|
+
const content = [];
|
|
36
|
+
// Main message with different priorities/audiences based on type
|
|
37
|
+
if (messageType === "error") {
|
|
38
|
+
content.push({
|
|
39
|
+
type: "text",
|
|
40
|
+
text: "Error: Operation failed",
|
|
41
|
+
annotations: {
|
|
42
|
+
priority: 1.0, // Errors are highest priority
|
|
43
|
+
audience: ["user", "assistant"], // Both need to know about errors
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else if (messageType === "success") {
|
|
48
|
+
content.push({
|
|
49
|
+
type: "text",
|
|
50
|
+
text: "Operation completed successfully",
|
|
51
|
+
annotations: {
|
|
52
|
+
priority: 0.7, // Success messages are important but not critical
|
|
53
|
+
audience: ["user"], // Success mainly for user consumption
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else if (messageType === "debug") {
|
|
58
|
+
content.push({
|
|
59
|
+
type: "text",
|
|
60
|
+
text: "Debug: Cache hit ratio 0.95, latency 150ms",
|
|
61
|
+
annotations: {
|
|
62
|
+
priority: 0.3, // Debug info is low priority
|
|
63
|
+
audience: ["assistant"], // Technical details for assistant
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Optional image with its own annotations
|
|
68
|
+
if (includeImage) {
|
|
69
|
+
content.push({
|
|
70
|
+
type: "image",
|
|
71
|
+
data: MCP_TINY_IMAGE,
|
|
72
|
+
mimeType: "image/png",
|
|
73
|
+
annotations: {
|
|
74
|
+
priority: 0.5,
|
|
75
|
+
audience: ["user"], // Images primarily for user visualization
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return { content };
|
|
80
|
+
});
|
|
81
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Tool configuration
|
|
2
|
+
const name = "get-env";
|
|
3
|
+
const config = {
|
|
4
|
+
title: "Print Environment Tool",
|
|
5
|
+
description: "Returns all environment variables, helpful for debugging MCP server configuration",
|
|
6
|
+
inputSchema: {},
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Registers the 'get-env' tool.
|
|
10
|
+
*
|
|
11
|
+
* The registered tool Retrieves and returns the environment variables
|
|
12
|
+
* of the current process as a JSON-formatted string encapsulated in a text response.
|
|
13
|
+
*
|
|
14
|
+
* @param {McpServer} server - The McpServer instance where the tool will be registered.
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
export const registerGetEnvTool = (server) => {
|
|
18
|
+
server.registerTool(name, config, async (args) => {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: JSON.stringify(process.env, null, 2),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
};
|