[WIP] Refactor everything server to be more modular and use recommended APIs.
Adding Trigger Elicitation Request tool
* Updated architecture.md
* Added trigger-elicitation-request.ts
- registerTriggerElicitationRequestTool
- registered tool sends an elicitation request that exercises all supported field types
* In tools/index.ts
- imports registerTriggerElicitationRequestTool
- in registerTools
- call registerTriggerElicitationRequestTool passing server
This commit is contained in:
parent
339e056ea0
commit
ea6fe271cd
2 changed files with 206 additions and 0 deletions
|
|
@ -12,6 +12,7 @@ import { registerLongRunningOperationTool } from "./long-running-operation.js";
|
||||||
import { registerToggleLoggingTool } from "./toggle-logging.js";
|
import { registerToggleLoggingTool } from "./toggle-logging.js";
|
||||||
import { registerToggleSubscriberUpdatesTool } from "./toggle-subscriber-updates.js";
|
import { registerToggleSubscriberUpdatesTool } from "./toggle-subscriber-updates.js";
|
||||||
import { registerTriggerSamplingRequestTool } from "./trigger-sampling-request.js";
|
import { registerTriggerSamplingRequestTool } from "./trigger-sampling-request.js";
|
||||||
|
import { registerTriggerElicitationRequestTool } from "./trigger-elicitation-request.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the tools with the MCP server.
|
* Register the tools with the MCP server.
|
||||||
|
|
@ -30,5 +31,6 @@ export const registerTools = (server: McpServer) => {
|
||||||
registerLongRunningOperationTool(server);
|
registerLongRunningOperationTool(server);
|
||||||
registerToggleLoggingTool(server);
|
registerToggleLoggingTool(server);
|
||||||
registerToggleSubscriberUpdatesTool(server);
|
registerToggleSubscriberUpdatesTool(server);
|
||||||
|
registerTriggerElicitationRequestTool(server);
|
||||||
registerTriggerSamplingRequestTool(server);
|
registerTriggerSamplingRequestTool(server);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
204
src/everything/tools/trigger-elicitation-request.ts
Normal file
204
src/everything/tools/trigger-elicitation-request.ts
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import { ElicitResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
// Tool configuration
|
||||||
|
const name = "trigger-elicitation-request";
|
||||||
|
const config = {
|
||||||
|
title: "Trigger Elicitation Request Tool",
|
||||||
|
description: "Trigger a Request from the Server for User Elicitation",
|
||||||
|
inputSchema: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the 'trigger-elicitation-request' tool within the provided McpServer instance.
|
||||||
|
*
|
||||||
|
* The registered tool performs the following operations:
|
||||||
|
*
|
||||||
|
* This tool sends a detailed request for the user to provide information based
|
||||||
|
* on a pre-defined schema of fields including text inputs, booleans, numbers,
|
||||||
|
* email, dates, etc. It uses validation and handles multiple possible outcomes
|
||||||
|
* from the user's response, such as acceptance with content, decline, or
|
||||||
|
* cancellation of the dialog. The process also ensures parsing and validating
|
||||||
|
* the elicitation input arguments at runtime.
|
||||||
|
*
|
||||||
|
* The tool resolves the elicitation dialog response into a structured result,
|
||||||
|
* which contains both user-submitted input data (if provided) and debugging
|
||||||
|
* information, including raw results.
|
||||||
|
*
|
||||||
|
* @param {McpServer} server - The MCP server instance to which the tool will be registered.
|
||||||
|
*/
|
||||||
|
export const registerTriggerElicitationRequestTool = (server: McpServer) => {
|
||||||
|
server.registerTool(
|
||||||
|
name,
|
||||||
|
config,
|
||||||
|
async (args, extra): Promise<CallToolResult> => {
|
||||||
|
const elicitationResult = await extra.sendRequest(
|
||||||
|
{
|
||||||
|
method: "elicitation/create",
|
||||||
|
params: {
|
||||||
|
message: "Please provide inputs for the following fields:",
|
||||||
|
requestedSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
title: 'String',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Your full, legal name',
|
||||||
|
},
|
||||||
|
check: {
|
||||||
|
title: 'Boolean',
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Agree to the terms and conditions',
|
||||||
|
},
|
||||||
|
firstLine: {
|
||||||
|
title: 'String with default',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Favorite first line of a story',
|
||||||
|
default: 'It was a dark and stormy night.',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
title: 'String with email format',
|
||||||
|
type: 'string',
|
||||||
|
format: 'email',
|
||||||
|
description: 'Your email address (will be verified, and never shared with anyone else)',
|
||||||
|
},
|
||||||
|
homepage: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'uri',
|
||||||
|
title: 'String with uri format',
|
||||||
|
description: 'Portfolio / personal website',
|
||||||
|
},
|
||||||
|
birthdate: {
|
||||||
|
title: 'String with date format',
|
||||||
|
type: 'string',
|
||||||
|
format: 'date',
|
||||||
|
description: 'Your date of birth',
|
||||||
|
},
|
||||||
|
integer: {
|
||||||
|
title: 'Integer',
|
||||||
|
type: 'integer',
|
||||||
|
description: 'Your favorite integer (do not give us your phone number, pin, or other sensitive info)',
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 100,
|
||||||
|
default: 42,
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
title: 'Number in range 1-1000',
|
||||||
|
type: 'number',
|
||||||
|
description: 'Favorite number (there are no wrong answers)',
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1000,
|
||||||
|
default: 3.14,
|
||||||
|
},
|
||||||
|
untitledSingleSelectEnum: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Untitled Single Select Enum',
|
||||||
|
description: 'Choose your favorite friend',
|
||||||
|
enum: ['Monica', 'Rachel', 'Joey', 'Chandler', 'Ross', 'Phoebe'],
|
||||||
|
default: 'Monica'
|
||||||
|
},
|
||||||
|
untitledMultipleSelectEnum: {
|
||||||
|
type: 'array',
|
||||||
|
title: 'Untitled Multiple Select Enum',
|
||||||
|
description: 'Choose your favorite instruments',
|
||||||
|
minItems: 1,
|
||||||
|
maxItems: 3,
|
||||||
|
items: { type: 'string', enum: ['Guitar', 'Piano', 'Violin', 'Drums', 'Bass'] },
|
||||||
|
default: ['Guitar']
|
||||||
|
},
|
||||||
|
titledSingleSelectEnum: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Titled Single Select Enum',
|
||||||
|
description: 'Choose your favorite hero',
|
||||||
|
oneOf: [
|
||||||
|
{ const: 'hero-1', title: 'Superman' },
|
||||||
|
{ const: 'hero-2', title: 'Green Lantern' },
|
||||||
|
{ const: 'hero-3', title: 'Wonder Woman' }
|
||||||
|
],
|
||||||
|
default: 'hero-1'
|
||||||
|
},
|
||||||
|
titledMultipleSelectEnum: {
|
||||||
|
type: 'array',
|
||||||
|
title: 'Titled Multiple Select Enum',
|
||||||
|
description: 'Choose your favorite types of fish',
|
||||||
|
minItems: 1,
|
||||||
|
maxItems: 3,
|
||||||
|
items: {
|
||||||
|
anyOf: [
|
||||||
|
{ const: 'fish-1', title: 'Tuna' },
|
||||||
|
{ const: 'fish-2', title: 'Salmon' },
|
||||||
|
{ const: 'fish-3', title: 'Trout' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
default: ['fish-1']
|
||||||
|
},
|
||||||
|
legacyTitledEnum: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Legacy Titled Single Select Enum',
|
||||||
|
description: 'Choose your favorite type of pet',
|
||||||
|
enum: ['pet-1', 'pet-2', 'pet-3', 'pet-4', 'pet-5'],
|
||||||
|
enumNames: ['Cats', 'Dogs', 'Birds', 'Fish', 'Reptiles'],
|
||||||
|
default: 'pet-1',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ElicitResultSchema,
|
||||||
|
{ timeout: 10 * 60 * 1000 /* 10 minutes */ }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle different response actions
|
||||||
|
const content: CallToolResult["content"] = [];
|
||||||
|
|
||||||
|
if (elicitationResult.action === "accept" && elicitationResult.content) {
|
||||||
|
content.push({
|
||||||
|
type: "text",
|
||||||
|
text: `✅ User provided the requested information!`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only access elicitationResult.content when action is accept
|
||||||
|
const userData = elicitationResult.content;
|
||||||
|
const lines = [];
|
||||||
|
if (userData.name) lines.push(`- Name: ${userData.name}`);
|
||||||
|
if (userData.check !== undefined)
|
||||||
|
lines.push(`- Agreed to terms: ${userData.check}`);
|
||||||
|
if (userData.color) lines.push(`- Favorite Color: ${userData.color}`);
|
||||||
|
if (userData.email) lines.push(`- Email: ${userData.email}`);
|
||||||
|
if (userData.homepage) lines.push(`- Homepage: ${userData.homepage}`);
|
||||||
|
if (userData.birthdate)
|
||||||
|
lines.push(`- Birthdate: ${userData.birthdate}`);
|
||||||
|
if (userData.integer !== undefined)
|
||||||
|
lines.push(`- Favorite Integer: ${userData.integer}`);
|
||||||
|
if (userData.number !== undefined)
|
||||||
|
lines.push(`- Favorite Number: ${userData.number}`);
|
||||||
|
if (userData.petType) lines.push(`- Pet Type: ${userData.petType}`);
|
||||||
|
|
||||||
|
content.push({
|
||||||
|
type: "text",
|
||||||
|
text: `User inputs:\n${lines.join("\n")}`,
|
||||||
|
});
|
||||||
|
} else if (elicitationResult.action === "decline") {
|
||||||
|
content.push({
|
||||||
|
type: "text",
|
||||||
|
text: `❌ User declined to provide the requested information.`,
|
||||||
|
});
|
||||||
|
} else if (elicitationResult.action === "cancel") {
|
||||||
|
content.push({
|
||||||
|
type: "text",
|
||||||
|
text: `⚠️ User cancelled the elicitation dialog.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include raw result for debugging
|
||||||
|
content.push({
|
||||||
|
type: "text",
|
||||||
|
text: `\nRaw result: ${JSON.stringify(elicitationResult, null, 2)}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { content };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue