@modelcontextprotocol/server-everything 2026.1.14 → 2026.7.4

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.
Files changed (34) hide show
  1. package/README.md +35 -1
  2. package/dist/__tests__/prompts.test.js +131 -0
  3. package/dist/__tests__/registrations.test.js +121 -0
  4. package/dist/__tests__/resources.test.js +240 -0
  5. package/dist/__tests__/server.test.js +31 -0
  6. package/dist/__tests__/tools.test.js +904 -0
  7. package/dist/docs/features.md +55 -2
  8. package/dist/docs/structure.md +18 -6
  9. package/dist/resources/session.js +15 -1
  10. package/dist/server/index.js +21 -1
  11. package/dist/server/roots.js +1 -5
  12. package/dist/tools/echo.js +6 -0
  13. package/dist/tools/get-annotated-message.js +6 -0
  14. package/dist/tools/get-env.js +6 -0
  15. package/dist/tools/get-resource-links.js +6 -0
  16. package/dist/tools/get-resource-reference.js +6 -0
  17. package/dist/tools/get-roots-list.js +6 -0
  18. package/dist/tools/get-structured-content.js +6 -0
  19. package/dist/tools/get-sum.js +6 -0
  20. package/dist/tools/get-tiny-image.js +6 -0
  21. package/dist/tools/gzip-file-as-resource.js +6 -1
  22. package/dist/tools/index.js +10 -0
  23. package/dist/tools/simulate-research-query.js +248 -0
  24. package/dist/tools/toggle-simulated-logging.js +6 -0
  25. package/dist/tools/toggle-subscriber-updates.js +6 -0
  26. package/dist/tools/trigger-elicitation-request-async.js +206 -0
  27. package/dist/tools/trigger-elicitation-request.js +7 -1
  28. package/dist/tools/trigger-long-running-operation.js +6 -0
  29. package/dist/tools/trigger-sampling-request-async.js +172 -0
  30. package/dist/tools/trigger-sampling-request.js +6 -0
  31. package/dist/tools/trigger-url-elicitation.js +169 -0
  32. package/dist/transports/streamableHttp.js +23 -2
  33. package/dist/vitest.config.js +13 -0
  34. package/package.json +10 -8
@@ -0,0 +1,248 @@
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, sends elicitation via sendRequest with relatedTask,
26
+ * which queues the request in the task message queue. The SDK delivers it through
27
+ * the tasks/result stream when the client calls tasks/result (per spec input_required flow).
28
+ * This works on all transports (STDIO, SSE, Streamable HTTP).
29
+ */
30
+ async function runResearchProcess(taskId, args, taskStore,
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ sendRequest) {
33
+ const state = researchStates.get(taskId);
34
+ if (!state)
35
+ return;
36
+ // Process each stage
37
+ for (let i = state.currentStage; i < STAGES.length; i++) {
38
+ state.currentStage = i;
39
+ // Check if task was cancelled externally
40
+ if (state.completed)
41
+ return;
42
+ // Update status message for current stage
43
+ await taskStore.updateTaskStatus(taskId, "working", `${STAGES[i]}...`);
44
+ // At synthesis stage (index 2), check if clarification is needed
45
+ if (i === 2 && state.ambiguous && !state.clarification) {
46
+ // Update status to show we're requesting input (spec SHOULD)
47
+ await taskStore.updateTaskStatus(taskId, "input_required", `Found multiple interpretations for "${state.topic}". Requesting clarification...`);
48
+ try {
49
+ // relatedTask queues elicitation via task message queue → delivered through tasks/result on all transports
50
+ const elicitResult = await sendRequest({
51
+ method: "elicitation/create",
52
+ params: {
53
+ message: `The research query "${state.topic}" could have multiple interpretations. Please clarify what you're looking for:`,
54
+ requestedSchema: {
55
+ type: "object",
56
+ properties: {
57
+ interpretation: {
58
+ type: "string",
59
+ title: "Clarification",
60
+ description: "Which interpretation of the topic do you mean?",
61
+ oneOf: getInterpretationsForTopic(state.topic),
62
+ },
63
+ },
64
+ required: ["interpretation"],
65
+ },
66
+ },
67
+ }, ElicitResultSchema, { relatedTask: { taskId } });
68
+ // Process elicitation response
69
+ if (elicitResult.action === "accept" && elicitResult.content) {
70
+ state.clarification =
71
+ elicitResult.content
72
+ .interpretation || "User accepted without selection";
73
+ }
74
+ else if (elicitResult.action === "decline") {
75
+ state.clarification = "User declined - using default interpretation";
76
+ }
77
+ else {
78
+ state.clarification = "User cancelled - using default interpretation";
79
+ }
80
+ }
81
+ catch (error) {
82
+ // Elicitation failed - use default interpretation and continue
83
+ console.warn(`Elicitation failed for task ${taskId}:`, error instanceof Error ? error.message : String(error));
84
+ state.clarification = "technical (default - elicitation unavailable)";
85
+ }
86
+ // Resume with working status (spec SHOULD)
87
+ await taskStore.updateTaskStatus(taskId, "working", `Continuing with interpretation: "${state.clarification}"...`);
88
+ // Continue processing (no return - just keep going through the loop)
89
+ }
90
+ // Simulate work for this stage
91
+ await new Promise((resolve) => setTimeout(resolve, STAGE_DURATION));
92
+ }
93
+ // All stages complete - generate result
94
+ state.completed = true;
95
+ const result = generateResearchReport(state);
96
+ state.result = result;
97
+ await taskStore.storeTaskResult(taskId, "completed", result);
98
+ }
99
+ /**
100
+ * Generates the final research report with educational content about tasks.
101
+ */
102
+ function generateResearchReport(state) {
103
+ const topic = state.clarification
104
+ ? `${state.topic} (${state.clarification})`
105
+ : state.topic;
106
+ const report = `# Research Report: ${topic}
107
+
108
+ ## Research Parameters
109
+ - **Topic**: ${state.topic}
110
+ ${state.clarification ? `- **Clarification**: ${state.clarification}` : ""}
111
+
112
+ ## Synthesis
113
+ This research query was processed through ${STAGES.length} stages:
114
+ ${STAGES.map((s, i) => `- Stage ${i + 1}: ${s} ✓`).join("\n")}
115
+
116
+ ---
117
+
118
+ ## About This Demo (SEP-1686: Tasks)
119
+
120
+ This tool demonstrates MCP's task-based execution pattern for long-running operations:
121
+
122
+ **Task Lifecycle Demonstrated:**
123
+ 1. \`tools/call\` with \`task\` parameter → Server returns \`CreateTaskResult\` (not the final result)
124
+ 2. Client polls \`tasks/get\` → Server returns current status and \`statusMessage\`
125
+ 3. Status progressed: \`working\` → ${state.clarification ? `\`input_required\` → \`working\` → ` : ""}\`completed\`
126
+ 4. Client calls \`tasks/result\` → Server returns this final result
127
+
128
+ ${state.clarification
129
+ ? `**Elicitation Flow:**
130
+ When the query was ambiguous, the server sent an \`elicitation/create\` request
131
+ to the client. The task status changed to \`input_required\` while awaiting user input.
132
+ ${state.clarification.includes("unavailable")
133
+ ? `**Note:** Elicitation failed and a default interpretation was used.`
134
+ : `After receiving clarification ("${state.clarification}"), the task resumed processing and completed.`}
135
+ `
136
+ : ""}
137
+ **Key Concepts:**
138
+ - Tasks enable "call now, fetch later" patterns
139
+ - \`statusMessage\` provides human-readable progress updates
140
+ - Tasks have TTL (time-to-live) for automatic cleanup
141
+ - \`pollInterval\` suggests how often to check status
142
+ - Elicitation requests use \`relatedTask\` to queue via tasks/result (works on all transports)
143
+
144
+ *This is a simulated research report from the Everything MCP Server.*
145
+ `;
146
+ return {
147
+ content: [
148
+ {
149
+ type: "text",
150
+ text: report,
151
+ },
152
+ ],
153
+ };
154
+ }
155
+ /**
156
+ * Registers the 'simulate-research-query' tool as a task-based tool.
157
+ *
158
+ * This tool demonstrates the MCP Tasks feature (SEP-1686) with a real-world scenario:
159
+ * a research tool that gathers and synthesizes information from multiple sources.
160
+ * If the query is ambiguous, it pauses to ask for clarification before completing.
161
+ *
162
+ * @param {McpServer} server - The McpServer instance where the tool will be registered.
163
+ */
164
+ export const registerSimulateResearchQueryTool = (server) => {
165
+ // Check if client supports elicitation (needed for input_required flow)
166
+ const clientCapabilities = server.server.getClientCapabilities() || {};
167
+ const clientSupportsElicitation = clientCapabilities.elicitation !== undefined;
168
+ server.experimental.tasks.registerToolTask("simulate-research-query", {
169
+ title: "Simulate Research Query",
170
+ description: "Simulates a deep research operation that gathers, analyzes, and synthesizes information. " +
171
+ "Demonstrates MCP task-based operations with progress through multiple stages. " +
172
+ "If 'ambiguous' is true and client supports elicitation, sends an elicitation request for clarification.",
173
+ inputSchema: SimulateResearchQuerySchema,
174
+ execution: { taskSupport: "required" },
175
+ annotations: {
176
+ readOnlyHint: false,
177
+ destructiveHint: false,
178
+ idempotentHint: false,
179
+ openWorldHint: false,
180
+ },
181
+ }, {
182
+ /**
183
+ * Creates a new research task and starts background processing.
184
+ */
185
+ createTask: async (args, extra) => {
186
+ const validatedArgs = SimulateResearchQuerySchema.parse(args);
187
+ // Create the task in the store
188
+ const task = await extra.taskStore.createTask({
189
+ ttl: 300000, // 5 minutes
190
+ pollInterval: 1000,
191
+ });
192
+ // Initialize research state
193
+ const state = {
194
+ topic: validatedArgs.topic,
195
+ ambiguous: validatedArgs.ambiguous && clientSupportsElicitation,
196
+ currentStage: 0,
197
+ completed: false,
198
+ };
199
+ researchStates.set(task.taskId, state);
200
+ // Start background research (don't await - runs asynchronously)
201
+ // Pass sendRequest for elicitation (queued via task message queue, works on all transports)
202
+ runResearchProcess(task.taskId, validatedArgs, extra.taskStore, extra.sendRequest).catch((error) => {
203
+ console.error(`Research task ${task.taskId} failed:`, error);
204
+ extra.taskStore
205
+ .updateTaskStatus(task.taskId, "failed", String(error))
206
+ .catch(console.error);
207
+ });
208
+ return { task };
209
+ },
210
+ /**
211
+ * Returns the current status of the research task.
212
+ */
213
+ getTask: async (args, extra) => {
214
+ return await extra.taskStore.getTask(extra.taskId);
215
+ },
216
+ /**
217
+ * Returns the task result.
218
+ * Elicitation is now handled directly in the background process.
219
+ */
220
+ getTaskResult: async (args, extra) => {
221
+ // Return the stored result
222
+ const result = await extra.taskStore.getTaskResult(extra.taskId);
223
+ // Clean up state
224
+ researchStates.delete(extra.taskId);
225
+ return result;
226
+ },
227
+ });
228
+ };
229
+ /**
230
+ * Returns contextual interpretation options based on the topic.
231
+ */
232
+ function getInterpretationsForTopic(topic) {
233
+ const lowerTopic = topic.toLowerCase();
234
+ // Example: contextual interpretations for "python"
235
+ if (lowerTopic.includes("python")) {
236
+ return [
237
+ { const: "programming", title: "Python programming language" },
238
+ { const: "snake", title: "Python snake species" },
239
+ { const: "comedy", title: "Monty Python comedy group" },
240
+ ];
241
+ }
242
+ // Default generic interpretations
243
+ return [
244
+ { const: "technical", title: "Technical/scientific perspective" },
245
+ { const: "historical", title: "Historical perspective" },
246
+ { const: "current", title: "Current events/news perspective" },
247
+ ];
248
+ }
@@ -5,6 +5,12 @@ const config = {
5
5
  title: "Toggle Simulated Logging",
6
6
  description: "Toggles simulated, random-leveled logging on or off.",
7
7
  inputSchema: {},
8
+ annotations: {
9
+ readOnlyHint: false,
10
+ destructiveHint: false,
11
+ idempotentHint: false,
12
+ openWorldHint: false,
13
+ },
8
14
  };
9
15
  // Track enabled clients by session id
10
16
  const clients = new Set();
@@ -5,6 +5,12 @@ const config = {
5
5
  title: "Toggle Subscriber Updates",
6
6
  description: "Toggles simulated resource subscription updates on or off.",
7
7
  inputSchema: {},
8
+ annotations: {
9
+ readOnlyHint: false,
10
+ destructiveHint: false,
11
+ idempotentHint: false,
12
+ openWorldHint: false,
13
+ },
8
14
  };
9
15
  // Track enabled clients by session id
10
16
  const clients = new Set();
@@ -0,0 +1,206 @@
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
+ annotations: {
11
+ readOnlyHint: false,
12
+ destructiveHint: false,
13
+ idempotentHint: false,
14
+ openWorldHint: false,
15
+ },
16
+ };
17
+ // Poll interval in milliseconds
18
+ const POLL_INTERVAL = 1000;
19
+ // Maximum poll attempts before timeout (10 minutes for user input)
20
+ const MAX_POLL_ATTEMPTS = 600;
21
+ /**
22
+ * Registers the 'trigger-elicitation-request-async' tool.
23
+ *
24
+ * This tool demonstrates bidirectional MCP tasks for elicitation:
25
+ * - Server sends elicitation request to client with task metadata
26
+ * - Client creates a task and returns CreateTaskResult
27
+ * - Client prompts user for input (task status: input_required)
28
+ * - Server polls client's tasks/get endpoint for status
29
+ * - Server fetches final result from client's tasks/result endpoint
30
+ *
31
+ * @param {McpServer} server - The McpServer instance where the tool will be registered.
32
+ */
33
+ export const registerTriggerElicitationRequestAsyncTool = (server) => {
34
+ // Check client capabilities
35
+ const clientCapabilities = server.server.getClientCapabilities() || {};
36
+ // Client must support elicitation AND tasks.requests.elicitation
37
+ const clientSupportsElicitation = clientCapabilities.elicitation !== undefined;
38
+ const clientTasksCapability = clientCapabilities.tasks;
39
+ const clientSupportsAsyncElicitation = clientTasksCapability?.requests?.elicitation?.create !== undefined;
40
+ if (clientSupportsElicitation && clientSupportsAsyncElicitation) {
41
+ server.registerTool(name, config, async (args, extra) => {
42
+ // Create the elicitation request WITH task metadata
43
+ // Using z.any() schema to avoid complex type matching with _meta
44
+ const request = {
45
+ method: "elicitation/create",
46
+ params: {
47
+ task: {
48
+ ttl: 600000, // 10 minutes (user input may take a while)
49
+ },
50
+ message: "Please provide inputs for the following fields (async task demo):",
51
+ requestedSchema: {
52
+ type: "object",
53
+ properties: {
54
+ name: {
55
+ title: "Your Name",
56
+ type: "string",
57
+ description: "Your full name",
58
+ },
59
+ favoriteColor: {
60
+ title: "Favorite Color",
61
+ type: "string",
62
+ description: "What is your favorite color?",
63
+ enum: ["Red", "Blue", "Green", "Yellow", "Purple"],
64
+ },
65
+ agreeToTerms: {
66
+ title: "Terms Agreement",
67
+ type: "boolean",
68
+ description: "Do you agree to the terms and conditions?",
69
+ },
70
+ },
71
+ required: ["name"],
72
+ },
73
+ },
74
+ };
75
+ // Send the elicitation request
76
+ // Client may return either:
77
+ // - ElicitResult (synchronous execution)
78
+ // - CreateTaskResult (task-based execution with { task } object)
79
+ const elicitResponse = await extra.sendRequest(request, z.union([
80
+ // CreateTaskResult - client created a task
81
+ z.object({
82
+ task: z.object({
83
+ taskId: z.string(),
84
+ status: z.string(),
85
+ pollInterval: z.number().optional(),
86
+ statusMessage: z.string().optional(),
87
+ }),
88
+ }),
89
+ // ElicitResult - synchronous execution
90
+ z.object({
91
+ action: z.string(),
92
+ content: z.any().optional(),
93
+ }),
94
+ ]));
95
+ // Check if client returned CreateTaskResult (has task object)
96
+ const isTaskResult = "task" in elicitResponse && elicitResponse.task;
97
+ if (!isTaskResult) {
98
+ // Client executed synchronously - return the direct response
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: `[SYNC] Client executed synchronously:\n${JSON.stringify(elicitResponse, null, 2)}`,
104
+ },
105
+ ],
106
+ };
107
+ }
108
+ const taskId = elicitResponse.task.taskId;
109
+ const statusMessages = [];
110
+ statusMessages.push(`Task created: ${taskId}`);
111
+ // Poll for task completion
112
+ let attempts = 0;
113
+ let taskStatus = elicitResponse.task.status;
114
+ let taskStatusMessage;
115
+ while (taskStatus !== "completed" &&
116
+ taskStatus !== "failed" &&
117
+ taskStatus !== "cancelled" &&
118
+ attempts < MAX_POLL_ATTEMPTS) {
119
+ // Wait before polling
120
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
121
+ attempts++;
122
+ // Get task status from client
123
+ const pollResult = await extra.sendRequest({
124
+ method: "tasks/get",
125
+ params: { taskId },
126
+ }, z.looseObject({
127
+ status: z.string(),
128
+ statusMessage: z.string().optional(),
129
+ }));
130
+ taskStatus = pollResult.status;
131
+ taskStatusMessage = pollResult.statusMessage;
132
+ // Only log status changes or every 10 polls to avoid spam
133
+ if (attempts === 1 ||
134
+ attempts % 10 === 0 ||
135
+ taskStatus !== "input_required") {
136
+ statusMessages.push(`Poll ${attempts}: ${taskStatus}${taskStatusMessage ? ` - ${taskStatusMessage}` : ""}`);
137
+ }
138
+ }
139
+ // Check for timeout
140
+ if (attempts >= MAX_POLL_ATTEMPTS) {
141
+ return {
142
+ content: [
143
+ {
144
+ type: "text",
145
+ text: `[TIMEOUT] Task timed out after ${MAX_POLL_ATTEMPTS} poll attempts\n\nProgress:\n${statusMessages.join("\n")}`,
146
+ },
147
+ ],
148
+ };
149
+ }
150
+ // Check for failure/cancellation
151
+ if (taskStatus === "failed" || taskStatus === "cancelled") {
152
+ return {
153
+ content: [
154
+ {
155
+ type: "text",
156
+ text: `[${taskStatus.toUpperCase()}] ${taskStatusMessage || "No message"}\n\nProgress:\n${statusMessages.join("\n")}`,
157
+ },
158
+ ],
159
+ };
160
+ }
161
+ // Fetch the final result
162
+ const result = await extra.sendRequest({
163
+ method: "tasks/result",
164
+ params: { taskId },
165
+ }, z.any());
166
+ // Format the elicitation result
167
+ const content = [];
168
+ if (result.action === "accept" && result.content) {
169
+ content.push({
170
+ type: "text",
171
+ text: `[COMPLETED] User provided the requested information!`,
172
+ });
173
+ const userData = result.content;
174
+ const lines = [];
175
+ if (userData.name)
176
+ lines.push(`- Name: ${userData.name}`);
177
+ if (userData.favoriteColor)
178
+ lines.push(`- Favorite Color: ${userData.favoriteColor}`);
179
+ if (userData.agreeToTerms !== undefined)
180
+ lines.push(`- Agreed to terms: ${userData.agreeToTerms}`);
181
+ content.push({
182
+ type: "text",
183
+ text: `User inputs:\n${lines.join("\n")}`,
184
+ });
185
+ }
186
+ else if (result.action === "decline") {
187
+ content.push({
188
+ type: "text",
189
+ text: `[DECLINED] User declined to provide the requested information.`,
190
+ });
191
+ }
192
+ else if (result.action === "cancel") {
193
+ content.push({
194
+ type: "text",
195
+ text: `[CANCELLED] User cancelled the elicitation dialog.`,
196
+ });
197
+ }
198
+ // Include progress and raw result for debugging
199
+ content.push({
200
+ type: "text",
201
+ text: `\nProgress:\n${statusMessages.join("\n")}\n\nRaw result: ${JSON.stringify(result, null, 2)}`,
202
+ });
203
+ return { content };
204
+ });
205
+ }
206
+ };
@@ -1,10 +1,16 @@
1
- import { ElicitResultSchema } from "@modelcontextprotocol/sdk/types.js";
1
+ import { ElicitResultSchema, } from "@modelcontextprotocol/sdk/types.js";
2
2
  // Tool configuration
3
3
  const name = "trigger-elicitation-request";
4
4
  const config = {
5
5
  title: "Trigger Elicitation Request Tool",
6
6
  description: "Trigger a Request from the Server for User Elicitation",
7
7
  inputSchema: {},
8
+ annotations: {
9
+ readOnlyHint: false,
10
+ destructiveHint: false,
11
+ idempotentHint: false,
12
+ openWorldHint: false,
13
+ },
8
14
  };
9
15
  /**
10
16
  * Registers the 'trigger-elicitation-request' tool.
@@ -13,6 +13,12 @@ const config = {
13
13
  title: "Trigger Long Running Operation Tool",
14
14
  description: "Demonstrates a long running operation with progress updates.",
15
15
  inputSchema: TriggerLongRunningOperationSchema,
16
+ annotations: {
17
+ readOnlyHint: true,
18
+ destructiveHint: false,
19
+ idempotentHint: true,
20
+ openWorldHint: false,
21
+ },
16
22
  };
17
23
  /**
18
24
  * Registers the 'trigger-tong-running-operation' tool.