@modelcontextprotocol/server-everything 2025.12.18 → 2026.1.14
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 +52 -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 +73 -0
- package/dist/server/logging.js +64 -0
- package/dist/server/roots.js +69 -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 +42 -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.js +210 -0
- package/dist/tools/trigger-long-running-operation.js +59 -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/{streamableHttp.js → transports/streamableHttp.js} +68 -57
- 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/index.js
CHANGED
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Parse command line arguments first
|
|
3
3
|
const args = process.argv.slice(2);
|
|
4
|
-
const scriptName = args[0] ||
|
|
4
|
+
const scriptName = args[0] || "stdio";
|
|
5
5
|
async function run() {
|
|
6
6
|
try {
|
|
7
7
|
// Dynamically import only the requested module to prevent all modules from initializing
|
|
8
8
|
switch (scriptName) {
|
|
9
|
-
case
|
|
9
|
+
case "stdio":
|
|
10
10
|
// Import and run the default server
|
|
11
|
-
await import(
|
|
11
|
+
await import("./transports/stdio.js");
|
|
12
12
|
break;
|
|
13
|
-
case
|
|
13
|
+
case "sse":
|
|
14
14
|
// Import and run the SSE server
|
|
15
|
-
await import(
|
|
15
|
+
await import("./transports/sse.js");
|
|
16
16
|
break;
|
|
17
|
-
case
|
|
17
|
+
case "streamableHttp":
|
|
18
18
|
// Import and run the streamable HTTP server
|
|
19
|
-
await import(
|
|
19
|
+
await import("./transports/streamableHttp.js");
|
|
20
20
|
break;
|
|
21
21
|
default:
|
|
22
|
-
console.error(
|
|
23
|
-
console.
|
|
24
|
-
console.
|
|
25
|
-
console.
|
|
26
|
-
console.
|
|
22
|
+
console.error(`-`.repeat(53));
|
|
23
|
+
console.error(` Everything Server Launcher`);
|
|
24
|
+
console.error(` Usage: node ./index.js [stdio|sse|streamableHttp]`);
|
|
25
|
+
console.error(` Default transport: stdio`);
|
|
26
|
+
console.error(`-`.repeat(53));
|
|
27
|
+
console.error(`Unknown transport: ${scriptName}`);
|
|
28
|
+
console.log("Available transports:");
|
|
29
|
+
console.log("- stdio");
|
|
30
|
+
console.log("- sse");
|
|
31
|
+
console.log("- streamableHttp");
|
|
27
32
|
process.exit(1);
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
catch (error) {
|
|
31
|
-
console.error(
|
|
36
|
+
console.error("Error running script:", error);
|
|
32
37
|
process.exit(1);
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
|
-
run();
|
|
40
|
+
await run();
|
|
36
41
|
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Register a prompt with arguments
|
|
4
|
+
* - Two arguments, one required and one optional
|
|
5
|
+
* - Combines argument values in the returned prompt
|
|
6
|
+
*
|
|
7
|
+
* @param server
|
|
8
|
+
*/
|
|
9
|
+
export const registerArgumentsPrompt = (server) => {
|
|
10
|
+
// Prompt arguments
|
|
11
|
+
const promptArgsSchema = {
|
|
12
|
+
city: z.string().describe("Name of the city"),
|
|
13
|
+
state: z.string().describe("Name of the state").optional(),
|
|
14
|
+
};
|
|
15
|
+
// Register the prompt
|
|
16
|
+
server.registerPrompt("args-prompt", {
|
|
17
|
+
title: "Arguments Prompt",
|
|
18
|
+
description: "A prompt with two arguments, one required and one optional",
|
|
19
|
+
argsSchema: promptArgsSchema,
|
|
20
|
+
}, (args) => {
|
|
21
|
+
const location = `${args?.city}${args?.state ? `, ${args?.state}` : ""}`;
|
|
22
|
+
return {
|
|
23
|
+
messages: [
|
|
24
|
+
{
|
|
25
|
+
role: "user",
|
|
26
|
+
content: {
|
|
27
|
+
type: "text",
|
|
28
|
+
text: `What's weather in ${location}?`,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
|
|
3
|
+
/**
|
|
4
|
+
* Register a prompt with completable arguments
|
|
5
|
+
* - Two required arguments, both with completion handlers
|
|
6
|
+
* - First argument value will be included in context for second argument
|
|
7
|
+
* - Allows second argument to depend on the first argument value
|
|
8
|
+
*
|
|
9
|
+
* @param server
|
|
10
|
+
*/
|
|
11
|
+
export const registerPromptWithCompletions = (server) => {
|
|
12
|
+
// Prompt arguments
|
|
13
|
+
const promptArgsSchema = {
|
|
14
|
+
department: completable(z.string().describe("Choose the department."), (value) => {
|
|
15
|
+
return ["Engineering", "Sales", "Marketing", "Support"].filter((d) => d.startsWith(value));
|
|
16
|
+
}),
|
|
17
|
+
name: completable(z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("Choose a team member to lead the selected department."), (value, context) => {
|
|
20
|
+
const department = context?.arguments?.["department"];
|
|
21
|
+
if (department === "Engineering") {
|
|
22
|
+
return ["Alice", "Bob", "Charlie"].filter((n) => n.startsWith(value));
|
|
23
|
+
}
|
|
24
|
+
else if (department === "Sales") {
|
|
25
|
+
return ["David", "Eve", "Frank"].filter((n) => n.startsWith(value));
|
|
26
|
+
}
|
|
27
|
+
else if (department === "Marketing") {
|
|
28
|
+
return ["Grace", "Henry", "Iris"].filter((n) => n.startsWith(value));
|
|
29
|
+
}
|
|
30
|
+
else if (department === "Support") {
|
|
31
|
+
return ["John", "Kim", "Lee"].filter((n) => n.startsWith(value));
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
// Register the prompt
|
|
37
|
+
server.registerPrompt("completable-prompt", {
|
|
38
|
+
title: "Team Management",
|
|
39
|
+
description: "First argument choice narrows values for second argument.",
|
|
40
|
+
argsSchema: promptArgsSchema,
|
|
41
|
+
}, ({ department, name }) => ({
|
|
42
|
+
messages: [
|
|
43
|
+
{
|
|
44
|
+
role: "user",
|
|
45
|
+
content: {
|
|
46
|
+
type: "text",
|
|
47
|
+
text: `Please promote ${name} to the head of the ${department} team.`,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
}));
|
|
52
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { registerSimplePrompt } from "./simple.js";
|
|
2
|
+
import { registerArgumentsPrompt } from "./args.js";
|
|
3
|
+
import { registerPromptWithCompletions } from "./completions.js";
|
|
4
|
+
import { registerEmbeddedResourcePrompt } from "./resource.js";
|
|
5
|
+
/**
|
|
6
|
+
* Register the prompts with the MCP server.
|
|
7
|
+
*
|
|
8
|
+
* @param server
|
|
9
|
+
*/
|
|
10
|
+
export const registerPrompts = (server) => {
|
|
11
|
+
registerSimplePrompt(server);
|
|
12
|
+
registerArgumentsPrompt(server);
|
|
13
|
+
registerPromptWithCompletions(server);
|
|
14
|
+
registerEmbeddedResourcePrompt(server);
|
|
15
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resourceTypeCompleter, resourceIdForPromptCompleter, } from "../resources/templates.js";
|
|
2
|
+
import { textResource, textResourceUri, blobResourceUri, blobResource, RESOURCE_TYPE_BLOB, RESOURCE_TYPE_TEXT, RESOURCE_TYPES, } from "../resources/templates.js";
|
|
3
|
+
/**
|
|
4
|
+
* Register a prompt with an embedded resource reference
|
|
5
|
+
* - Takes a resource type and id
|
|
6
|
+
* - Returns the corresponding dynamically created resource
|
|
7
|
+
*
|
|
8
|
+
* @param server
|
|
9
|
+
*/
|
|
10
|
+
export const registerEmbeddedResourcePrompt = (server) => {
|
|
11
|
+
// Prompt arguments
|
|
12
|
+
const promptArgsSchema = {
|
|
13
|
+
resourceType: resourceTypeCompleter,
|
|
14
|
+
resourceId: resourceIdForPromptCompleter,
|
|
15
|
+
};
|
|
16
|
+
// Register the prompt
|
|
17
|
+
server.registerPrompt("resource-prompt", {
|
|
18
|
+
title: "Resource Prompt",
|
|
19
|
+
description: "A prompt that includes an embedded resource reference",
|
|
20
|
+
argsSchema: promptArgsSchema,
|
|
21
|
+
}, (args) => {
|
|
22
|
+
// Validate resource type argument
|
|
23
|
+
const resourceType = args.resourceType;
|
|
24
|
+
if (!RESOURCE_TYPES.includes(resourceType)) {
|
|
25
|
+
throw new Error(`Invalid resourceType: ${args?.resourceType}. Must be ${RESOURCE_TYPE_TEXT} or ${RESOURCE_TYPE_BLOB}.`);
|
|
26
|
+
}
|
|
27
|
+
// Validate resourceId argument
|
|
28
|
+
const resourceId = Number(args?.resourceId);
|
|
29
|
+
if (!Number.isFinite(resourceId) ||
|
|
30
|
+
!Number.isInteger(resourceId) ||
|
|
31
|
+
resourceId < 1) {
|
|
32
|
+
throw new Error(`Invalid resourceId: ${args?.resourceId}. Must be a finite positive integer.`);
|
|
33
|
+
}
|
|
34
|
+
// Get resource based on the resource type
|
|
35
|
+
const uri = resourceType === RESOURCE_TYPE_TEXT
|
|
36
|
+
? textResourceUri(resourceId)
|
|
37
|
+
: blobResourceUri(resourceId);
|
|
38
|
+
const resource = resourceType === RESOURCE_TYPE_TEXT
|
|
39
|
+
? textResource(uri, resourceId)
|
|
40
|
+
: blobResource(uri, resourceId);
|
|
41
|
+
return {
|
|
42
|
+
messages: [
|
|
43
|
+
{
|
|
44
|
+
role: "user",
|
|
45
|
+
content: {
|
|
46
|
+
type: "text",
|
|
47
|
+
text: `This prompt includes the ${resourceType} resource with id: ${resourceId}. Please analyze the following resource:`,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
role: "user",
|
|
52
|
+
content: {
|
|
53
|
+
type: "resource",
|
|
54
|
+
resource: resource,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register a simple prompt with no arguments
|
|
3
|
+
* - Returns the fixed text of the prompt with no modifications
|
|
4
|
+
*
|
|
5
|
+
* @param server
|
|
6
|
+
*/
|
|
7
|
+
export const registerSimplePrompt = (server) => {
|
|
8
|
+
// Register the prompt
|
|
9
|
+
server.registerPrompt("simple-prompt", {
|
|
10
|
+
title: "Simple Prompt",
|
|
11
|
+
description: "A prompt with no arguments",
|
|
12
|
+
}, () => ({
|
|
13
|
+
messages: [
|
|
14
|
+
{
|
|
15
|
+
role: "user",
|
|
16
|
+
content: {
|
|
17
|
+
type: "text",
|
|
18
|
+
text: "This is a simple prompt without arguments.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
}));
|
|
23
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { dirname, join } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
4
|
+
/**
|
|
5
|
+
* Register static file resources
|
|
6
|
+
* - Each file in src/everything/docs is exposed as an individual static resource
|
|
7
|
+
* - URIs follow the pattern: "demo://static/docs/<filename>"
|
|
8
|
+
* - Markdown (.md) files are served as mime type "text/markdown"
|
|
9
|
+
* - Text (.txt) files are served as mime type "text/plain"
|
|
10
|
+
* - JSON (.json) files are served as mime type "application/json"
|
|
11
|
+
*
|
|
12
|
+
* @param server
|
|
13
|
+
*/
|
|
14
|
+
export const registerFileResources = (server) => {
|
|
15
|
+
// Read the entries in the docs directory
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
const docsDir = join(__dirname, "..", "docs");
|
|
19
|
+
let entries = [];
|
|
20
|
+
try {
|
|
21
|
+
entries = readdirSync(docsDir);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
// If docs/ folder is missing or unreadable, just skip registration
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Register each file as a static resource
|
|
28
|
+
for (const name of entries) {
|
|
29
|
+
// Only process files, not directories
|
|
30
|
+
const fullPath = join(docsDir, name);
|
|
31
|
+
try {
|
|
32
|
+
const st = statSync(fullPath);
|
|
33
|
+
if (!st.isFile())
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// Prepare file resource info
|
|
40
|
+
const uri = `demo://resource/static/document/${encodeURIComponent(name)}`;
|
|
41
|
+
const mimeType = getMimeType(name);
|
|
42
|
+
const description = `Static document file exposed from /docs: ${name}`;
|
|
43
|
+
// Register file resource
|
|
44
|
+
server.registerResource(name, uri, { mimeType, description }, async (uri) => {
|
|
45
|
+
const text = readFileSafe(fullPath);
|
|
46
|
+
return {
|
|
47
|
+
contents: [
|
|
48
|
+
{
|
|
49
|
+
uri: uri.toString(),
|
|
50
|
+
mimeType,
|
|
51
|
+
text,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Get the mimetype based on filename
|
|
60
|
+
* @param fileName
|
|
61
|
+
*/
|
|
62
|
+
function getMimeType(fileName) {
|
|
63
|
+
const lower = fileName.toLowerCase();
|
|
64
|
+
if (lower.endsWith(".md") || lower.endsWith(".markdown"))
|
|
65
|
+
return "text/markdown";
|
|
66
|
+
if (lower.endsWith(".txt"))
|
|
67
|
+
return "text/plain";
|
|
68
|
+
if (lower.endsWith(".json"))
|
|
69
|
+
return "application/json";
|
|
70
|
+
return "text/plain";
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read a file or return an error message if it fails
|
|
74
|
+
* @param path
|
|
75
|
+
*/
|
|
76
|
+
function readFileSafe(path) {
|
|
77
|
+
try {
|
|
78
|
+
return readFileSync(path, "utf-8");
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return `Error reading file: ${path}. ${e}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { registerResourceTemplates } from "./templates.js";
|
|
2
|
+
import { registerFileResources } from "./files.js";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
/**
|
|
7
|
+
* Register the resources with the MCP server.
|
|
8
|
+
* @param server
|
|
9
|
+
*/
|
|
10
|
+
export const registerResources = (server) => {
|
|
11
|
+
registerResourceTemplates(server);
|
|
12
|
+
registerFileResources(server);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Reads the server instructions from the corresponding markdown file.
|
|
16
|
+
* Attempts to load the content of the file located in the `docs` directory.
|
|
17
|
+
* If the file cannot be loaded, an error message is returned instead.
|
|
18
|
+
*
|
|
19
|
+
* @return {string} The content of the server instructions file, or an error message if reading fails.
|
|
20
|
+
*/
|
|
21
|
+
export function readInstructions() {
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
const filePath = join(__dirname, "..", "docs", "instructions.md");
|
|
25
|
+
let instructions;
|
|
26
|
+
try {
|
|
27
|
+
instructions = readFileSync(filePath, "utf-8");
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
instructions = "Server instructions not loaded: " + e;
|
|
31
|
+
}
|
|
32
|
+
return instructions;
|
|
33
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a session-scoped resource URI string based on the provided resource name.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} name - The name of the resource to create a URI for.
|
|
5
|
+
* @returns {string} The formatted session resource URI.
|
|
6
|
+
*/
|
|
7
|
+
export const getSessionResourceURI = (name) => {
|
|
8
|
+
return `demo://resource/session/${name}`;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Registers a session-scoped resource with the provided server and returns a resource link.
|
|
12
|
+
*
|
|
13
|
+
* The registered resource is available during the life of the session only; it is not otherwise persisted.
|
|
14
|
+
*
|
|
15
|
+
* @param {McpServer} server - The server instance responsible for handling the resource registration.
|
|
16
|
+
* @param {Resource} resource - The resource object containing metadata such as URI, name, description, and mimeType.
|
|
17
|
+
* @param {"text"|"blob"} type
|
|
18
|
+
* @param payload
|
|
19
|
+
* @returns {ResourceLink} An object representing the resource link, with associated metadata.
|
|
20
|
+
*/
|
|
21
|
+
export const registerSessionResource = (server, resource, type, payload) => {
|
|
22
|
+
// Destructure resource
|
|
23
|
+
const { uri, name, mimeType, description, title, annotations, icons, _meta } = resource;
|
|
24
|
+
// Prepare the resource content to return
|
|
25
|
+
// See https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-contents
|
|
26
|
+
const resourceContent = type === "text"
|
|
27
|
+
? {
|
|
28
|
+
uri: uri.toString(),
|
|
29
|
+
mimeType,
|
|
30
|
+
text: payload,
|
|
31
|
+
}
|
|
32
|
+
: {
|
|
33
|
+
uri: uri.toString(),
|
|
34
|
+
mimeType,
|
|
35
|
+
blob: payload,
|
|
36
|
+
};
|
|
37
|
+
// Register file resource
|
|
38
|
+
server.registerResource(name, uri, { mimeType, description, title, annotations, icons, _meta }, async (uri) => {
|
|
39
|
+
return {
|
|
40
|
+
contents: [resourceContent],
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
return { type: "resource_link", ...resource };
|
|
44
|
+
};
|
|
@@ -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
|
+
};
|