@modelcontextprotocol/server-everything 2026.1.14 → 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/dist/docs/features.md +52 -1
- package/dist/server/index.js +21 -1
- package/dist/server/roots.js +1 -5
- package/dist/tools/index.js +8 -0
- package/dist/tools/simulate-research-query.js +249 -0
- package/dist/tools/trigger-elicitation-request-async.js +202 -0
- package/dist/tools/trigger-elicitation-request.js +1 -1
- package/dist/tools/trigger-sampling-request-async.js +168 -0
- package/dist/transports/streamableHttp.js +23 -2
- package/package.json +1 -1
package/dist/docs/features.md
CHANGED
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
- `trigger-long-running-operation` (tools/trigger-trigger-long-running-operation.ts): Simulates a multi-step operation over a given `duration` and number of `steps`; reports progress via `notifications/progress` when a `progressToken` is provided by the client.
|
|
23
23
|
- `toggle-simulated-logging` (tools/toggle-simulated-logging.ts): Starts or stops simulated, random‑leveled logging for the invoking session. Respects the client’s selected minimum logging level.
|
|
24
24
|
- `toggle-subscriber-updates` (tools/toggle-subscriber-updates.ts): Starts or stops simulated resource update notifications for URIs the invoking session has subscribed to.
|
|
25
|
-
- `trigger-sampling-request` (tools/trigger-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLM
|
|
25
|
+
- `trigger-sampling-request` (tools/trigger-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLM's response payload.
|
|
26
|
+
- `simulate-research-query` (tools/simulate-research-query.ts): Demonstrates MCP Tasks (SEP-1686) with a simulated multi-stage research operation. Accepts `topic` and `ambiguous` parameters. Returns a task that progresses through stages with status updates. If `ambiguous` is true and client supports elicitation, sends an elicitation request directly to gather clarification before completing.
|
|
27
|
+
- `trigger-sampling-request-async` (tools/trigger-sampling-request-async.ts): Demonstrates bidirectional tasks where the server sends a sampling request that the client executes as a background task. Server polls for status and retrieves the LLM result when complete. Requires client to support `tasks.requests.sampling.createMessage`.
|
|
28
|
+
- `trigger-elicitation-request-async` (tools/trigger-elicitation-request-async.ts): Demonstrates bidirectional tasks where the server sends an elicitation request that the client executes as a background task. Server polls while waiting for user input. Requires client to support `tasks.requests.elicitation.create`.
|
|
26
29
|
|
|
27
30
|
## Prompts
|
|
28
31
|
|
|
@@ -50,3 +53,51 @@
|
|
|
50
53
|
- Simulated logging is available but off by default.
|
|
51
54
|
- Use the `toggle-simulated-logging` tool to start/stop periodic log messages of varying levels (debug, info, notice, warning, error, critical, alert, emergency) per session.
|
|
52
55
|
- Clients can control the minimum level they receive via the standard MCP `logging/setLevel` request.
|
|
56
|
+
|
|
57
|
+
## Tasks (SEP-1686)
|
|
58
|
+
|
|
59
|
+
The server advertises support for MCP Tasks, enabling long-running operations with status tracking:
|
|
60
|
+
|
|
61
|
+
- **Capabilities advertised**: `tasks.list`, `tasks.cancel`, `tasks.requests.tools.call`
|
|
62
|
+
- **Task Store**: Uses `InMemoryTaskStore` from SDK experimental for task lifecycle management
|
|
63
|
+
- **Message Queue**: Uses `InMemoryTaskMessageQueue` for task-related messaging
|
|
64
|
+
|
|
65
|
+
### Task Lifecycle
|
|
66
|
+
|
|
67
|
+
1. Client calls `tools/call` with `task: true` parameter
|
|
68
|
+
2. Server returns `CreateTaskResult` with `taskId` instead of immediate result
|
|
69
|
+
3. Client polls `tasks/get` to check status and receive `statusMessage` updates
|
|
70
|
+
4. When status is `completed`, client calls `tasks/result` to retrieve the final result
|
|
71
|
+
|
|
72
|
+
### Task Statuses
|
|
73
|
+
|
|
74
|
+
- `working`: Task is actively processing
|
|
75
|
+
- `input_required`: Task needs additional input (server sends elicitation request directly)
|
|
76
|
+
- `completed`: Task finished successfully
|
|
77
|
+
- `failed`: Task encountered an error
|
|
78
|
+
- `cancelled`: Task was cancelled by client
|
|
79
|
+
|
|
80
|
+
### Demo Tools
|
|
81
|
+
|
|
82
|
+
**Server-side tasks (client calls server):**
|
|
83
|
+
Use the `simulate-research-query` tool to exercise the full task lifecycle. Set `ambiguous: true` to trigger elicitation - the server will send an `elicitation/create` request directly and await the response before completing.
|
|
84
|
+
|
|
85
|
+
**Client-side tasks (server calls client):**
|
|
86
|
+
Use `trigger-sampling-request-async` or `trigger-elicitation-request-async` to demonstrate bidirectional tasks where the server sends requests that the client executes as background tasks. These require the client to advertise `tasks.requests.sampling.createMessage` or `tasks.requests.elicitation.create` capabilities respectively.
|
|
87
|
+
|
|
88
|
+
### Bidirectional Task Flow
|
|
89
|
+
|
|
90
|
+
MCP Tasks are bidirectional - both server and client can be task executors:
|
|
91
|
+
|
|
92
|
+
| Direction | Request Type | Task Executor | Demo Tool |
|
|
93
|
+
| ---------------- | ------------------------ | ------------- | ----------------------------------- |
|
|
94
|
+
| Client -> Server | `tools/call` | Server | `simulate-research-query` |
|
|
95
|
+
| Server -> Client | `sampling/createMessage` | Client | `trigger-sampling-request-async` |
|
|
96
|
+
| Server -> Client | `elicitation/create` | Client | `trigger-elicitation-request-async` |
|
|
97
|
+
|
|
98
|
+
For client-side tasks:
|
|
99
|
+
|
|
100
|
+
1. Server sends request with task metadata (e.g., `params.task.ttl`)
|
|
101
|
+
2. Client creates task and returns `CreateTaskResult` with `taskId`
|
|
102
|
+
3. Server polls `tasks/get` for status updates
|
|
103
|
+
4. When complete, server calls `tasks/result` to retrieve the result
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { InMemoryTaskStore, InMemoryTaskMessageQueue, } from "@modelcontextprotocol/sdk/experimental/tasks";
|
|
2
3
|
import { setSubscriptionHandlers, stopSimulatedResourceUpdates, } from "../resources/subscriptions.js";
|
|
3
4
|
import { registerConditionalTools, registerTools } from "../tools/index.js";
|
|
4
5
|
import { registerResources, readInstructions } from "../resources/index.js";
|
|
@@ -21,6 +22,10 @@ import { syncRoots } from "./roots.js";
|
|
|
21
22
|
export const createServer = () => {
|
|
22
23
|
// Read the server instructions
|
|
23
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;
|
|
24
29
|
// Create the server
|
|
25
30
|
const server = new McpServer({
|
|
26
31
|
name: "mcp-servers/everything",
|
|
@@ -39,8 +44,19 @@ export const createServer = () => {
|
|
|
39
44
|
listChanged: true,
|
|
40
45
|
},
|
|
41
46
|
logging: {},
|
|
47
|
+
tasks: {
|
|
48
|
+
list: {},
|
|
49
|
+
cancel: {},
|
|
50
|
+
requests: {
|
|
51
|
+
tools: {
|
|
52
|
+
call: {},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
42
56
|
},
|
|
43
57
|
instructions,
|
|
58
|
+
taskStore,
|
|
59
|
+
taskMessageQueue,
|
|
44
60
|
});
|
|
45
61
|
// Register the tools
|
|
46
62
|
registerTools(server);
|
|
@@ -59,7 +75,7 @@ export const createServer = () => {
|
|
|
59
75
|
// This is delayed until after the `notifications/initialized` handler finishes,
|
|
60
76
|
// otherwise, the request gets lost.
|
|
61
77
|
const sessionId = server.server.transport?.sessionId;
|
|
62
|
-
setTimeout(() => syncRoots(server, sessionId), 350);
|
|
78
|
+
initializeTimeout = setTimeout(() => syncRoots(server, sessionId), 350);
|
|
63
79
|
};
|
|
64
80
|
// Return the ServerFactoryResponse
|
|
65
81
|
return {
|
|
@@ -68,6 +84,10 @@ export const createServer = () => {
|
|
|
68
84
|
// Stop any simulated logging or resource updates that may have been initiated.
|
|
69
85
|
stopSimulatedLogging(sessionId);
|
|
70
86
|
stopSimulatedResourceUpdates(sessionId);
|
|
87
|
+
// Clean up task store timers
|
|
88
|
+
taskStore.cleanup();
|
|
89
|
+
if (initializeTimeout)
|
|
90
|
+
clearTimeout(initializeTimeout);
|
|
71
91
|
},
|
|
72
92
|
};
|
|
73
93
|
};
|
package/dist/server/roots.js
CHANGED
|
@@ -48,11 +48,7 @@ export const syncRoots = async (server, sessionId) => {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
51
|
-
|
|
52
|
-
level: "error",
|
|
53
|
-
logger: "everything-server",
|
|
54
|
-
data: `Failed to request roots from client: ${error instanceof Error ? error.message : String(error)}`,
|
|
55
|
-
}, sessionId);
|
|
51
|
+
console.error(`Failed to request roots from client ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
52
|
}
|
|
57
53
|
};
|
|
58
54
|
// If the roots have not been synced for this client,
|
package/dist/tools/index.js
CHANGED
|
@@ -13,6 +13,9 @@ import { registerToggleSubscriberUpdatesTool } from "./toggle-subscriber-updates
|
|
|
13
13
|
import { registerTriggerElicitationRequestTool } from "./trigger-elicitation-request.js";
|
|
14
14
|
import { registerTriggerLongRunningOperationTool } from "./trigger-long-running-operation.js";
|
|
15
15
|
import { registerTriggerSamplingRequestTool } from "./trigger-sampling-request.js";
|
|
16
|
+
import { registerTriggerSamplingRequestAsyncTool } from "./trigger-sampling-request-async.js";
|
|
17
|
+
import { registerTriggerElicitationRequestAsyncTool } from "./trigger-elicitation-request-async.js";
|
|
18
|
+
import { registerSimulateResearchQueryTool } from "./simulate-research-query.js";
|
|
16
19
|
/**
|
|
17
20
|
* Register the tools with the MCP server.
|
|
18
21
|
* @param server
|
|
@@ -39,4 +42,9 @@ export const registerConditionalTools = (server) => {
|
|
|
39
42
|
registerGetRootsListTool(server);
|
|
40
43
|
registerTriggerElicitationRequestTool(server);
|
|
41
44
|
registerTriggerSamplingRequestTool(server);
|
|
45
|
+
// Task-based research tool (uses experimental tasks API)
|
|
46
|
+
registerSimulateResearchQueryTool(server);
|
|
47
|
+
// Bidirectional task tools - server sends requests that client executes as tasks
|
|
48
|
+
registerTriggerSamplingRequestAsyncTool(server);
|
|
49
|
+
registerTriggerElicitationRequestAsyncTool(server);
|
|
42
50
|
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ElicitResultSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
// Tool input schema
|
|
4
|
+
const SimulateResearchQuerySchema = z.object({
|
|
5
|
+
topic: z.string().describe("The research topic to investigate"),
|
|
6
|
+
ambiguous: z
|
|
7
|
+
.boolean()
|
|
8
|
+
.default(false)
|
|
9
|
+
.describe("Simulate an ambiguous query that requires clarification (triggers input_required status)"),
|
|
10
|
+
});
|
|
11
|
+
// Research stages
|
|
12
|
+
const STAGES = [
|
|
13
|
+
"Gathering sources",
|
|
14
|
+
"Analyzing content",
|
|
15
|
+
"Synthesizing findings",
|
|
16
|
+
"Generating report",
|
|
17
|
+
];
|
|
18
|
+
// Duration per stage in milliseconds
|
|
19
|
+
const STAGE_DURATION = 1000;
|
|
20
|
+
// Map to store research state per task
|
|
21
|
+
const researchStates = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Runs the background research process.
|
|
24
|
+
* Updates task status as it progresses through stages.
|
|
25
|
+
* If clarification is needed, attempts elicitation via sendRequest.
|
|
26
|
+
*
|
|
27
|
+
* Note: Elicitation only works on STDIO transport. On HTTP transport,
|
|
28
|
+
* sendRequest will fail and the task will use a default interpretation.
|
|
29
|
+
* Full HTTP support requires SDK PR #1210's elicitInputStream API.
|
|
30
|
+
*/
|
|
31
|
+
async function runResearchProcess(taskId, args, taskStore,
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
sendRequest) {
|
|
34
|
+
const state = researchStates.get(taskId);
|
|
35
|
+
if (!state)
|
|
36
|
+
return;
|
|
37
|
+
// Process each stage
|
|
38
|
+
for (let i = state.currentStage; i < STAGES.length; i++) {
|
|
39
|
+
state.currentStage = i;
|
|
40
|
+
// Check if task was cancelled externally
|
|
41
|
+
if (state.completed)
|
|
42
|
+
return;
|
|
43
|
+
// Update status message for current stage
|
|
44
|
+
await taskStore.updateTaskStatus(taskId, "working", `${STAGES[i]}...`);
|
|
45
|
+
// At synthesis stage (index 2), check if clarification is needed
|
|
46
|
+
if (i === 2 && state.ambiguous && !state.clarification) {
|
|
47
|
+
// Update status to show we're requesting input (spec SHOULD)
|
|
48
|
+
await taskStore.updateTaskStatus(taskId, "input_required", `Found multiple interpretations for "${state.topic}". Requesting clarification...`);
|
|
49
|
+
try {
|
|
50
|
+
// Try elicitation via sendRequest (works on STDIO, fails on HTTP)
|
|
51
|
+
const elicitResult = await sendRequest({
|
|
52
|
+
method: "elicitation/create",
|
|
53
|
+
params: {
|
|
54
|
+
message: `The research query "${state.topic}" could have multiple interpretations. Please clarify what you're looking for:`,
|
|
55
|
+
requestedSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
interpretation: {
|
|
59
|
+
type: "string",
|
|
60
|
+
title: "Clarification",
|
|
61
|
+
description: "Which interpretation of the topic do you mean?",
|
|
62
|
+
oneOf: getInterpretationsForTopic(state.topic),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ["interpretation"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}, ElicitResultSchema);
|
|
69
|
+
// Process elicitation response
|
|
70
|
+
if (elicitResult.action === "accept" && elicitResult.content) {
|
|
71
|
+
state.clarification =
|
|
72
|
+
elicitResult.content
|
|
73
|
+
.interpretation || "User accepted without selection";
|
|
74
|
+
}
|
|
75
|
+
else if (elicitResult.action === "decline") {
|
|
76
|
+
state.clarification = "User declined - using default interpretation";
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
state.clarification = "User cancelled - using default interpretation";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
// Elicitation failed (likely HTTP transport without streaming support)
|
|
84
|
+
// Use default interpretation and continue - task should still complete
|
|
85
|
+
console.warn(`Elicitation failed for task ${taskId} (HTTP transport?):`, error instanceof Error ? error.message : String(error));
|
|
86
|
+
state.clarification =
|
|
87
|
+
"technical (default - elicitation unavailable on HTTP)";
|
|
88
|
+
}
|
|
89
|
+
// Resume with working status (spec SHOULD)
|
|
90
|
+
await taskStore.updateTaskStatus(taskId, "working", `Continuing with interpretation: "${state.clarification}"...`);
|
|
91
|
+
// Continue processing (no return - just keep going through the loop)
|
|
92
|
+
}
|
|
93
|
+
// Simulate work for this stage
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, STAGE_DURATION));
|
|
95
|
+
}
|
|
96
|
+
// All stages complete - generate result
|
|
97
|
+
state.completed = true;
|
|
98
|
+
const result = generateResearchReport(state);
|
|
99
|
+
state.result = result;
|
|
100
|
+
await taskStore.storeTaskResult(taskId, "completed", result);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Generates the final research report with educational content about tasks.
|
|
104
|
+
*/
|
|
105
|
+
function generateResearchReport(state) {
|
|
106
|
+
const topic = state.clarification
|
|
107
|
+
? `${state.topic} (${state.clarification})`
|
|
108
|
+
: state.topic;
|
|
109
|
+
const report = `# Research Report: ${topic}
|
|
110
|
+
|
|
111
|
+
## Research Parameters
|
|
112
|
+
- **Topic**: ${state.topic}
|
|
113
|
+
${state.clarification ? `- **Clarification**: ${state.clarification}` : ""}
|
|
114
|
+
|
|
115
|
+
## Synthesis
|
|
116
|
+
This research query was processed through ${STAGES.length} stages:
|
|
117
|
+
${STAGES.map((s, i) => `- Stage ${i + 1}: ${s} ✓`).join("\n")}
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## About This Demo (SEP-1686: Tasks)
|
|
122
|
+
|
|
123
|
+
This tool demonstrates MCP's task-based execution pattern for long-running operations:
|
|
124
|
+
|
|
125
|
+
**Task Lifecycle Demonstrated:**
|
|
126
|
+
1. \`tools/call\` with \`task\` parameter → Server returns \`CreateTaskResult\` (not the final result)
|
|
127
|
+
2. Client polls \`tasks/get\` → Server returns current status and \`statusMessage\`
|
|
128
|
+
3. Status progressed: \`working\` → ${state.clarification ? `\`input_required\` → \`working\` → ` : ""}\`completed\`
|
|
129
|
+
4. Client calls \`tasks/result\` → Server returns this final result
|
|
130
|
+
|
|
131
|
+
${state.clarification
|
|
132
|
+
? `**Elicitation Flow:**
|
|
133
|
+
When the query was ambiguous, the server sent an \`elicitation/create\` request
|
|
134
|
+
to the client. The task status changed to \`input_required\` while awaiting user input.
|
|
135
|
+
${state.clarification.includes("unavailable on HTTP")
|
|
136
|
+
? `
|
|
137
|
+
**Note:** Elicitation was skipped because this server is running over HTTP transport.
|
|
138
|
+
The current SDK's \`sendRequest\` only works over STDIO. Full HTTP elicitation support
|
|
139
|
+
requires SDK PR #1210's streaming \`elicitInputStream\` API.
|
|
140
|
+
`
|
|
141
|
+
: `After receiving clarification ("${state.clarification}"), the task resumed processing and completed.`}
|
|
142
|
+
`
|
|
143
|
+
: ""}
|
|
144
|
+
**Key Concepts:**
|
|
145
|
+
- Tasks enable "call now, fetch later" patterns
|
|
146
|
+
- \`statusMessage\` provides human-readable progress updates
|
|
147
|
+
- Tasks have TTL (time-to-live) for automatic cleanup
|
|
148
|
+
- \`pollInterval\` suggests how often to check status
|
|
149
|
+
- Elicitation requests can be sent directly during task execution
|
|
150
|
+
|
|
151
|
+
*This is a simulated research report from the Everything MCP Server.*
|
|
152
|
+
`;
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: report,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Registers the 'simulate-research-query' tool as a task-based tool.
|
|
164
|
+
*
|
|
165
|
+
* This tool demonstrates the MCP Tasks feature (SEP-1686) with a real-world scenario:
|
|
166
|
+
* a research tool that gathers and synthesizes information from multiple sources.
|
|
167
|
+
* If the query is ambiguous, it pauses to ask for clarification before completing.
|
|
168
|
+
*
|
|
169
|
+
* @param {McpServer} server - The McpServer instance where the tool will be registered.
|
|
170
|
+
*/
|
|
171
|
+
export const registerSimulateResearchQueryTool = (server) => {
|
|
172
|
+
// Check if client supports elicitation (needed for input_required flow)
|
|
173
|
+
const clientCapabilities = server.server.getClientCapabilities() || {};
|
|
174
|
+
const clientSupportsElicitation = clientCapabilities.elicitation !== undefined;
|
|
175
|
+
server.experimental.tasks.registerToolTask("simulate-research-query", {
|
|
176
|
+
title: "Simulate Research Query",
|
|
177
|
+
description: "Simulates a deep research operation that gathers, analyzes, and synthesizes information. " +
|
|
178
|
+
"Demonstrates MCP task-based operations with progress through multiple stages. " +
|
|
179
|
+
"If 'ambiguous' is true and client supports elicitation, sends an elicitation request for clarification.",
|
|
180
|
+
inputSchema: SimulateResearchQuerySchema,
|
|
181
|
+
execution: { taskSupport: "required" },
|
|
182
|
+
}, {
|
|
183
|
+
/**
|
|
184
|
+
* Creates a new research task and starts background processing.
|
|
185
|
+
*/
|
|
186
|
+
createTask: async (args, extra) => {
|
|
187
|
+
const validatedArgs = SimulateResearchQuerySchema.parse(args);
|
|
188
|
+
// Create the task in the store
|
|
189
|
+
const task = await extra.taskStore.createTask({
|
|
190
|
+
ttl: 300000, // 5 minutes
|
|
191
|
+
pollInterval: 1000,
|
|
192
|
+
});
|
|
193
|
+
// Initialize research state
|
|
194
|
+
const state = {
|
|
195
|
+
topic: validatedArgs.topic,
|
|
196
|
+
ambiguous: validatedArgs.ambiguous && clientSupportsElicitation,
|
|
197
|
+
currentStage: 0,
|
|
198
|
+
completed: false,
|
|
199
|
+
};
|
|
200
|
+
researchStates.set(task.taskId, state);
|
|
201
|
+
// Start background research (don't await - runs asynchronously)
|
|
202
|
+
// Pass sendRequest for elicitation (works on STDIO, gracefully degrades on HTTP)
|
|
203
|
+
runResearchProcess(task.taskId, validatedArgs, extra.taskStore, extra.sendRequest).catch((error) => {
|
|
204
|
+
console.error(`Research task ${task.taskId} failed:`, error);
|
|
205
|
+
extra.taskStore
|
|
206
|
+
.updateTaskStatus(task.taskId, "failed", String(error))
|
|
207
|
+
.catch(console.error);
|
|
208
|
+
});
|
|
209
|
+
return { task };
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* Returns the current status of the research task.
|
|
213
|
+
*/
|
|
214
|
+
getTask: async (args, extra) => {
|
|
215
|
+
return await extra.taskStore.getTask(extra.taskId);
|
|
216
|
+
},
|
|
217
|
+
/**
|
|
218
|
+
* Returns the task result.
|
|
219
|
+
* Elicitation is now handled directly in the background process.
|
|
220
|
+
*/
|
|
221
|
+
getTaskResult: async (args, extra) => {
|
|
222
|
+
// Return the stored result
|
|
223
|
+
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
|
224
|
+
// Clean up state
|
|
225
|
+
researchStates.delete(extra.taskId);
|
|
226
|
+
return result;
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* Returns contextual interpretation options based on the topic.
|
|
232
|
+
*/
|
|
233
|
+
function getInterpretationsForTopic(topic) {
|
|
234
|
+
const lowerTopic = topic.toLowerCase();
|
|
235
|
+
// Example: contextual interpretations for "python"
|
|
236
|
+
if (lowerTopic.includes("python")) {
|
|
237
|
+
return [
|
|
238
|
+
{ const: "programming", title: "Python programming language" },
|
|
239
|
+
{ const: "snake", title: "Python snake species" },
|
|
240
|
+
{ const: "comedy", title: "Monty Python comedy group" },
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
// Default generic interpretations
|
|
244
|
+
return [
|
|
245
|
+
{ const: "technical", title: "Technical/scientific perspective" },
|
|
246
|
+
{ const: "historical", title: "Historical perspective" },
|
|
247
|
+
{ const: "current", title: "Current events/news perspective" },
|
|
248
|
+
];
|
|
249
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Tool configuration
|
|
3
|
+
const name = "trigger-elicitation-request-async";
|
|
4
|
+
const config = {
|
|
5
|
+
title: "Trigger Async Elicitation Request Tool",
|
|
6
|
+
description: "Trigger an async elicitation request that the CLIENT executes as a background task. " +
|
|
7
|
+
"Demonstrates bidirectional MCP tasks where the server sends an elicitation request and " +
|
|
8
|
+
"the client handles user input asynchronously, allowing the server to poll for completion.",
|
|
9
|
+
inputSchema: {},
|
|
10
|
+
};
|
|
11
|
+
// Poll interval in milliseconds
|
|
12
|
+
const POLL_INTERVAL = 1000;
|
|
13
|
+
// Maximum poll attempts before timeout (10 minutes for user input)
|
|
14
|
+
const MAX_POLL_ATTEMPTS = 600;
|
|
15
|
+
/**
|
|
16
|
+
* Registers the 'trigger-elicitation-request-async' tool.
|
|
17
|
+
*
|
|
18
|
+
* This tool demonstrates bidirectional MCP tasks for elicitation:
|
|
19
|
+
* - Server sends elicitation request to client with task metadata
|
|
20
|
+
* - Client creates a task and returns CreateTaskResult
|
|
21
|
+
* - Client prompts user for input (task status: input_required)
|
|
22
|
+
* - Server polls client's tasks/get endpoint for status
|
|
23
|
+
* - Server fetches final result from client's tasks/result endpoint
|
|
24
|
+
*
|
|
25
|
+
* @param {McpServer} server - The McpServer instance where the tool will be registered.
|
|
26
|
+
*/
|
|
27
|
+
export const registerTriggerElicitationRequestAsyncTool = (server) => {
|
|
28
|
+
// Check client capabilities
|
|
29
|
+
const clientCapabilities = server.server.getClientCapabilities() || {};
|
|
30
|
+
// Client must support elicitation AND tasks.requests.elicitation
|
|
31
|
+
const clientSupportsElicitation = clientCapabilities.elicitation !== undefined;
|
|
32
|
+
const clientTasksCapability = clientCapabilities.tasks;
|
|
33
|
+
const clientSupportsAsyncElicitation = clientTasksCapability?.requests?.elicitation?.create !== undefined;
|
|
34
|
+
if (clientSupportsElicitation && clientSupportsAsyncElicitation) {
|
|
35
|
+
server.registerTool(name, config, async (args, extra) => {
|
|
36
|
+
// Create the elicitation request WITH task metadata
|
|
37
|
+
// Using z.any() schema to avoid complex type matching with _meta
|
|
38
|
+
const request = {
|
|
39
|
+
method: "elicitation/create",
|
|
40
|
+
params: {
|
|
41
|
+
task: {
|
|
42
|
+
ttl: 600000, // 10 minutes (user input may take a while)
|
|
43
|
+
},
|
|
44
|
+
message: "Please provide inputs for the following fields (async task demo):",
|
|
45
|
+
requestedSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
name: {
|
|
49
|
+
title: "Your Name",
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Your full name",
|
|
52
|
+
},
|
|
53
|
+
favoriteColor: {
|
|
54
|
+
title: "Favorite Color",
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "What is your favorite color?",
|
|
57
|
+
enum: ["Red", "Blue", "Green", "Yellow", "Purple"],
|
|
58
|
+
},
|
|
59
|
+
agreeToTerms: {
|
|
60
|
+
title: "Terms Agreement",
|
|
61
|
+
type: "boolean",
|
|
62
|
+
description: "Do you agree to the terms and conditions?",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ["name"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
// Send the elicitation request
|
|
70
|
+
// Client may return either:
|
|
71
|
+
// - ElicitResult (synchronous execution)
|
|
72
|
+
// - CreateTaskResult (task-based execution with { task } object)
|
|
73
|
+
const elicitResponse = await extra.sendRequest(request, z.union([
|
|
74
|
+
// CreateTaskResult - client created a task
|
|
75
|
+
z.object({
|
|
76
|
+
task: z.object({
|
|
77
|
+
taskId: z.string(),
|
|
78
|
+
status: z.string(),
|
|
79
|
+
pollInterval: z.number().optional(),
|
|
80
|
+
statusMessage: z.string().optional(),
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
// ElicitResult - synchronous execution
|
|
84
|
+
z.object({
|
|
85
|
+
action: z.string(),
|
|
86
|
+
content: z.any().optional(),
|
|
87
|
+
}),
|
|
88
|
+
]));
|
|
89
|
+
// Check if client returned CreateTaskResult (has task object)
|
|
90
|
+
const isTaskResult = "task" in elicitResponse && elicitResponse.task;
|
|
91
|
+
if (!isTaskResult) {
|
|
92
|
+
// Client executed synchronously - return the direct response
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: `[SYNC] Client executed synchronously:\n${JSON.stringify(elicitResponse, null, 2)}`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const taskId = elicitResponse.task.taskId;
|
|
103
|
+
const statusMessages = [];
|
|
104
|
+
statusMessages.push(`Task created: ${taskId}`);
|
|
105
|
+
// Poll for task completion
|
|
106
|
+
let attempts = 0;
|
|
107
|
+
let taskStatus = elicitResponse.task.status;
|
|
108
|
+
let taskStatusMessage;
|
|
109
|
+
while (taskStatus !== "completed" &&
|
|
110
|
+
taskStatus !== "failed" &&
|
|
111
|
+
taskStatus !== "cancelled" &&
|
|
112
|
+
attempts < MAX_POLL_ATTEMPTS) {
|
|
113
|
+
// Wait before polling
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
115
|
+
attempts++;
|
|
116
|
+
// Get task status from client
|
|
117
|
+
const pollResult = await extra.sendRequest({
|
|
118
|
+
method: "tasks/get",
|
|
119
|
+
params: { taskId },
|
|
120
|
+
}, z
|
|
121
|
+
.object({
|
|
122
|
+
status: z.string(),
|
|
123
|
+
statusMessage: z.string().optional(),
|
|
124
|
+
})
|
|
125
|
+
.passthrough());
|
|
126
|
+
taskStatus = pollResult.status;
|
|
127
|
+
taskStatusMessage = pollResult.statusMessage;
|
|
128
|
+
// Only log status changes or every 10 polls to avoid spam
|
|
129
|
+
if (attempts === 1 ||
|
|
130
|
+
attempts % 10 === 0 ||
|
|
131
|
+
taskStatus !== "input_required") {
|
|
132
|
+
statusMessages.push(`Poll ${attempts}: ${taskStatus}${taskStatusMessage ? ` - ${taskStatusMessage}` : ""}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Check for timeout
|
|
136
|
+
if (attempts >= MAX_POLL_ATTEMPTS) {
|
|
137
|
+
return {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: `[TIMEOUT] Task timed out after ${MAX_POLL_ATTEMPTS} poll attempts\n\nProgress:\n${statusMessages.join("\n")}`,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Check for failure/cancellation
|
|
147
|
+
if (taskStatus === "failed" || taskStatus === "cancelled") {
|
|
148
|
+
return {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: `[${taskStatus.toUpperCase()}] ${taskStatusMessage || "No message"}\n\nProgress:\n${statusMessages.join("\n")}`,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Fetch the final result
|
|
158
|
+
const result = await extra.sendRequest({
|
|
159
|
+
method: "tasks/result",
|
|
160
|
+
params: { taskId },
|
|
161
|
+
}, z.any());
|
|
162
|
+
// Format the elicitation result
|
|
163
|
+
const content = [];
|
|
164
|
+
if (result.action === "accept" && result.content) {
|
|
165
|
+
content.push({
|
|
166
|
+
type: "text",
|
|
167
|
+
text: `[COMPLETED] User provided the requested information!`,
|
|
168
|
+
});
|
|
169
|
+
const userData = result.content;
|
|
170
|
+
const lines = [];
|
|
171
|
+
if (userData.name)
|
|
172
|
+
lines.push(`- Name: ${userData.name}`);
|
|
173
|
+
if (userData.favoriteColor)
|
|
174
|
+
lines.push(`- Favorite Color: ${userData.favoriteColor}`);
|
|
175
|
+
if (userData.agreeToTerms !== undefined)
|
|
176
|
+
lines.push(`- Agreed to terms: ${userData.agreeToTerms}`);
|
|
177
|
+
content.push({
|
|
178
|
+
type: "text",
|
|
179
|
+
text: `User inputs:\n${lines.join("\n")}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
else if (result.action === "decline") {
|
|
183
|
+
content.push({
|
|
184
|
+
type: "text",
|
|
185
|
+
text: `[DECLINED] User declined to provide the requested information.`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else if (result.action === "cancel") {
|
|
189
|
+
content.push({
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `[CANCELLED] User cancelled the elicitation dialog.`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Include progress and raw result for debugging
|
|
195
|
+
content.push({
|
|
196
|
+
type: "text",
|
|
197
|
+
text: `\nProgress:\n${statusMessages.join("\n")}\n\nRaw result: ${JSON.stringify(result, null, 2)}`,
|
|
198
|
+
});
|
|
199
|
+
return { content };
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Tool input schema
|
|
3
|
+
const TriggerSamplingRequestAsyncSchema = z.object({
|
|
4
|
+
prompt: z.string().describe("The prompt to send to the LLM"),
|
|
5
|
+
maxTokens: z
|
|
6
|
+
.number()
|
|
7
|
+
.default(100)
|
|
8
|
+
.describe("Maximum number of tokens to generate"),
|
|
9
|
+
});
|
|
10
|
+
// Tool configuration
|
|
11
|
+
const name = "trigger-sampling-request-async";
|
|
12
|
+
const config = {
|
|
13
|
+
title: "Trigger Async Sampling Request Tool",
|
|
14
|
+
description: "Trigger an async sampling request that the CLIENT executes as a background task. " +
|
|
15
|
+
"Demonstrates bidirectional MCP tasks where the server sends a request and the client " +
|
|
16
|
+
"executes it asynchronously, allowing the server to poll for progress and results.",
|
|
17
|
+
inputSchema: TriggerSamplingRequestAsyncSchema,
|
|
18
|
+
};
|
|
19
|
+
// Poll interval in milliseconds
|
|
20
|
+
const POLL_INTERVAL = 1000;
|
|
21
|
+
// Maximum poll attempts before timeout
|
|
22
|
+
const MAX_POLL_ATTEMPTS = 60;
|
|
23
|
+
/**
|
|
24
|
+
* Registers the 'trigger-sampling-request-async' tool.
|
|
25
|
+
*
|
|
26
|
+
* This tool demonstrates bidirectional MCP tasks:
|
|
27
|
+
* - Server sends sampling request to client with task metadata
|
|
28
|
+
* - Client creates a task and returns CreateTaskResult
|
|
29
|
+
* - Server polls client's tasks/get endpoint for status
|
|
30
|
+
* - Server fetches final result from client's tasks/result endpoint
|
|
31
|
+
*
|
|
32
|
+
* @param {McpServer} server - The McpServer instance where the tool will be registered.
|
|
33
|
+
*/
|
|
34
|
+
export const registerTriggerSamplingRequestAsyncTool = (server) => {
|
|
35
|
+
// Check client capabilities
|
|
36
|
+
const clientCapabilities = server.server.getClientCapabilities() || {};
|
|
37
|
+
// Client must support sampling AND tasks.requests.sampling
|
|
38
|
+
const clientSupportsSampling = clientCapabilities.sampling !== undefined;
|
|
39
|
+
const clientTasksCapability = clientCapabilities.tasks;
|
|
40
|
+
const clientSupportsAsyncSampling = clientTasksCapability?.requests?.sampling?.createMessage !== undefined;
|
|
41
|
+
if (clientSupportsSampling && clientSupportsAsyncSampling) {
|
|
42
|
+
server.registerTool(name, config, async (args, extra) => {
|
|
43
|
+
const validatedArgs = TriggerSamplingRequestAsyncSchema.parse(args);
|
|
44
|
+
const { prompt, maxTokens } = validatedArgs;
|
|
45
|
+
// Create the sampling request WITH task metadata
|
|
46
|
+
// The params.task field signals to the client that this should be executed as a task
|
|
47
|
+
const request = {
|
|
48
|
+
method: "sampling/createMessage",
|
|
49
|
+
params: {
|
|
50
|
+
task: {
|
|
51
|
+
ttl: 300000, // 5 minutes
|
|
52
|
+
},
|
|
53
|
+
messages: [
|
|
54
|
+
{
|
|
55
|
+
role: "user",
|
|
56
|
+
content: {
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Resource ${name} context: ${prompt}`,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
systemPrompt: "You are a helpful test server.",
|
|
63
|
+
maxTokens,
|
|
64
|
+
temperature: 0.7,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
// Send the sampling request
|
|
68
|
+
// Client may return either:
|
|
69
|
+
// - CreateMessageResult (synchronous execution)
|
|
70
|
+
// - CreateTaskResult (task-based execution with { task } object)
|
|
71
|
+
const samplingResponse = await extra.sendRequest(request, z.union([
|
|
72
|
+
// CreateTaskResult - client created a task
|
|
73
|
+
z.object({
|
|
74
|
+
task: z.object({
|
|
75
|
+
taskId: z.string(),
|
|
76
|
+
status: z.string(),
|
|
77
|
+
pollInterval: z.number().optional(),
|
|
78
|
+
statusMessage: z.string().optional(),
|
|
79
|
+
}),
|
|
80
|
+
}),
|
|
81
|
+
// CreateMessageResult - synchronous execution
|
|
82
|
+
z.object({
|
|
83
|
+
role: z.string(),
|
|
84
|
+
content: z.any(),
|
|
85
|
+
model: z.string(),
|
|
86
|
+
stopReason: z.string().optional(),
|
|
87
|
+
}),
|
|
88
|
+
]));
|
|
89
|
+
// Check if client returned CreateTaskResult (has task object)
|
|
90
|
+
const isTaskResult = "task" in samplingResponse && samplingResponse.task;
|
|
91
|
+
if (!isTaskResult) {
|
|
92
|
+
// Client executed synchronously - return the direct response
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: `[SYNC] Client executed synchronously:\n${JSON.stringify(samplingResponse, null, 2)}`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const taskId = samplingResponse.task.taskId;
|
|
103
|
+
const statusMessages = [];
|
|
104
|
+
statusMessages.push(`Task created: ${taskId}`);
|
|
105
|
+
// Poll for task completion
|
|
106
|
+
let attempts = 0;
|
|
107
|
+
let taskStatus = samplingResponse.task.status;
|
|
108
|
+
let taskStatusMessage;
|
|
109
|
+
while (taskStatus !== "completed" &&
|
|
110
|
+
taskStatus !== "failed" &&
|
|
111
|
+
taskStatus !== "cancelled" &&
|
|
112
|
+
attempts < MAX_POLL_ATTEMPTS) {
|
|
113
|
+
// Wait before polling
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
115
|
+
attempts++;
|
|
116
|
+
// Get task status from client
|
|
117
|
+
const pollResult = await extra.sendRequest({
|
|
118
|
+
method: "tasks/get",
|
|
119
|
+
params: { taskId },
|
|
120
|
+
}, z
|
|
121
|
+
.object({
|
|
122
|
+
status: z.string(),
|
|
123
|
+
statusMessage: z.string().optional(),
|
|
124
|
+
})
|
|
125
|
+
.passthrough());
|
|
126
|
+
taskStatus = pollResult.status;
|
|
127
|
+
taskStatusMessage = pollResult.statusMessage;
|
|
128
|
+
statusMessages.push(`Poll ${attempts}: ${taskStatus}${taskStatusMessage ? ` - ${taskStatusMessage}` : ""}`);
|
|
129
|
+
}
|
|
130
|
+
// Check for timeout
|
|
131
|
+
if (attempts >= MAX_POLL_ATTEMPTS) {
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: `[TIMEOUT] Task timed out after ${MAX_POLL_ATTEMPTS} poll attempts\n\nProgress:\n${statusMessages.join("\n")}`,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Check for failure/cancellation
|
|
142
|
+
if (taskStatus === "failed" || taskStatus === "cancelled") {
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: `[${taskStatus.toUpperCase()}] ${taskStatusMessage || "No message"}\n\nProgress:\n${statusMessages.join("\n")}`,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// Fetch the final result
|
|
153
|
+
const result = await extra.sendRequest({
|
|
154
|
+
method: "tasks/result",
|
|
155
|
+
params: { taskId },
|
|
156
|
+
}, z.any());
|
|
157
|
+
// Return the result with status history
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `[COMPLETED] Async sampling completed!\n\n**Progress:**\n${statusMessages.join("\n")}\n\n**Result:**\n${JSON.stringify(result, null, 2)}`,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
@@ -1,9 +1,30 @@
|
|
|
1
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2
|
-
import { InMemoryEventStore } from "@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js";
|
|
1
|
+
import { StreamableHTTPServerTransport, } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
2
|
import express from "express";
|
|
4
3
|
import { createServer } from "../server/index.js";
|
|
5
4
|
import { randomUUID } from "node:crypto";
|
|
6
5
|
import cors from "cors";
|
|
6
|
+
// Simple in-memory event store for SSE resumability
|
|
7
|
+
class InMemoryEventStore {
|
|
8
|
+
events = new Map();
|
|
9
|
+
async storeEvent(streamId, message) {
|
|
10
|
+
const eventId = randomUUID();
|
|
11
|
+
this.events.set(eventId, { streamId, message });
|
|
12
|
+
return eventId;
|
|
13
|
+
}
|
|
14
|
+
async replayEventsAfter(lastEventId, { send }) {
|
|
15
|
+
const entries = Array.from(this.events.entries());
|
|
16
|
+
const startIndex = entries.findIndex(([id]) => id === lastEventId);
|
|
17
|
+
if (startIndex === -1)
|
|
18
|
+
return lastEventId;
|
|
19
|
+
let lastId = lastEventId;
|
|
20
|
+
for (let i = startIndex + 1; i < entries.length; i++) {
|
|
21
|
+
const [eventId, { message }] = entries[i];
|
|
22
|
+
await send(eventId, message);
|
|
23
|
+
lastId = eventId;
|
|
24
|
+
}
|
|
25
|
+
return lastId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
7
28
|
console.log("Starting Streamable HTTP server...");
|
|
8
29
|
// Express app with permissive CORS for testing with Inspector direct connect mode
|
|
9
30
|
const app = express();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelcontextprotocol/server-everything",
|
|
3
|
-
"version": "2026.1.
|
|
3
|
+
"version": "2026.1.26",
|
|
4
4
|
"description": "MCP server that exercises all the features of the MCP protocol",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"mcpName": "io.github.modelcontextprotocol/server-everything",
|