@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
376 lines (375 loc) • 11.3 kB
JavaScript
/**
* MCP Elicitation Protocol
*
* Protocol-level interface for the MCP elicitation system that enables
* tools to request interactive user input during execution.
*
* This module provides:
* - Protocol message types for MCP elicitation requests/responses
* - Protocol-level handlers for different transport types
* - Utility functions for protocol message construction
* - Integration with the ElicitationManager
*
* Implements MCP 2024-11-05 elicitation specification.
*
* @module mcp/elicitationProtocol
* @since 8.39.0
*/
import { randomUUID } from "crypto";
import { logger } from "../utils/logger.js";
import { withTimeout } from "../utils/async/withTimeout.js";
import { globalElicitationManager, } from "./elicitation/elicitationManager.js";
/**
* Create an elicitation request protocol message
*/
export function createElicitationRequest(params) {
return {
jsonrpc: "2.0",
id: randomUUID(),
method: "elicitation/request",
params: {
type: params.type,
message: params.message,
toolName: params.toolName,
serverId: params.serverId,
timeout: params.timeout,
optional: params.optional,
defaultValue: params.defaultValue,
options: params.options ?? {},
},
};
}
/**
* Create an elicitation response protocol message
*/
export function createElicitationResponse(requestId, response) {
return {
jsonrpc: "2.0",
id: randomUUID(),
method: "elicitation/response",
params: {
requestId,
responded: response.responded,
value: response.value,
cancelled: response.cancelled,
timedOut: response.timedOut,
error: response.error,
},
};
}
/**
* Create an elicitation cancel protocol message
*/
export function createElicitationCancel(requestId, reason) {
return {
jsonrpc: "2.0",
id: randomUUID(),
method: "elicitation/cancel",
params: {
requestId,
reason,
},
};
}
/**
* Check if a message is an elicitation protocol message
*/
export function isElicitationProtocolMessage(message) {
if (typeof message !== "object" || message === null) {
return false;
}
const msg = message;
return (msg.jsonrpc === "2.0" &&
typeof msg.id === "string" &&
(msg.method === "elicitation/request" ||
msg.method === "elicitation/response" ||
msg.method === "elicitation/cancel") &&
typeof msg.params === "object" &&
msg.params !== null);
}
/**
* Convert protocol message to Elicitation type
*/
export function protocolMessageToElicitation(message) {
const { type, message: displayMessage, toolName, serverId, timeout, optional, defaultValue, options, } = message.params;
const base = {
id: message.id,
type,
message: displayMessage,
toolName,
serverId,
timeout,
optional,
defaultValue,
};
// Add type-specific options
switch (type) {
case "confirmation":
return {
...base,
type: "confirmation",
confirmLabel: options?.confirmLabel,
cancelLabel: options?.cancelLabel,
};
case "text":
return {
...base,
type: "text",
placeholder: options?.placeholder,
minLength: options?.minLength,
maxLength: options?.maxLength,
pattern: options?.pattern,
multiline: options?.multiline,
};
case "select":
return {
...base,
type: "select",
options: options?.options ?? [],
};
case "multiselect":
return {
...base,
type: "multiselect",
options: options?.options ?? [],
minSelections: options?.minSelections,
maxSelections: options?.maxSelections,
};
case "form":
return {
...base,
type: "form",
fields: options?.fields ?? [],
submitLabel: options?.submitLabel,
};
case "file":
return {
...base,
type: "file",
accept: options?.accept,
multiple: options?.multiple,
maxSize: options?.maxSize,
};
case "secret":
return {
...base,
type: "secret",
hint: options?.hint,
};
default:
return base;
}
}
/**
* Convert ElicitationResponse to protocol message
*/
export function elicitationResponseToProtocol(response) {
return createElicitationResponse(response.requestId, {
responded: response.responded,
value: response.value,
cancelled: response.cancelled,
timedOut: response.timedOut,
error: response.error,
});
}
/**
* Elicitation Protocol Adapter
*
* Bridges protocol-level messages with the ElicitationManager
*/
export class ElicitationProtocolAdapter {
manager;
config;
constructor(config = {}) {
this.manager = config.manager ?? globalElicitationManager;
this.config = {
manager: this.manager,
defaultTimeout: config.defaultTimeout ?? 60000,
enableLogging: config.enableLogging ?? false,
customHandler: config.customHandler,
};
}
/**
* Handle incoming protocol message
*/
async handleMessage(message) {
if (this.config.enableLogging) {
logger.info("[ElicitationProtocol] Received:", message.method);
}
// Use custom handler if provided
if (this.config.customHandler) {
return await withTimeout(this.config.customHandler(message), this.config.defaultTimeout, `[ElicitationProtocol] Custom handler timed out after ${this.config.defaultTimeout}ms`);
}
switch (message.method) {
case "elicitation/request":
return this.handleRequest(message);
case "elicitation/response":
return this.handleResponse(message);
case "elicitation/cancel":
return this.handleCancel(message);
default:
if (this.config.enableLogging) {
logger.warn(`[ElicitationProtocol] Unhandled method: ${message.method}`);
}
return;
}
}
/**
* Handle elicitation request
*/
async handleRequest(message) {
const elicitation = protocolMessageToElicitation(message);
// Use the manager to process the request
const response = await this.manager.request({
...elicitation,
timeout: elicitation.timeout ?? this.config.defaultTimeout,
});
return elicitationResponseToProtocol(response);
}
/**
* Handle elicitation response (for external responses)
*/
async handleResponse(_message) {
// Responses are typically handled by the manager internally
// This is for external systems sending responses
if (this.config.enableLogging) {
logger.info("[ElicitationProtocol] Response received (no action needed)");
}
}
/**
* Handle elicitation cancel
*/
async handleCancel(message) {
this.manager.cancel(message.params.requestId, message.params.reason);
}
/**
* Send an elicitation request through the protocol
*/
async requestElicitation(params) {
const message = createElicitationRequest(params);
const elicitation = protocolMessageToElicitation(message);
return this.manager.request({
...elicitation,
timeout: elicitation.timeout ?? this.config.defaultTimeout,
});
}
/**
* Cancel a pending elicitation
*/
cancelElicitation(requestId, reason) {
this.manager.cancel(requestId, reason);
}
/**
* Get the underlying manager
*/
getManager() {
return this.manager;
}
/**
* Set protocol handler for the manager
*/
setHandler(handler) {
this.manager.setHandler(handler);
}
/**
* Enable/disable the protocol
*/
setEnabled(enabled) {
this.manager.setEnabled(enabled);
}
/**
* Check if protocol is enabled
*/
isEnabled() {
return this.manager.isEnabled();
}
}
/**
* Create protocol-compliant confirmation request
*/
export function createConfirmationRequest(message, options) {
const requestOptions = {};
if (options.confirmLabel !== undefined) {
requestOptions.confirmLabel = options.confirmLabel;
}
if (options.cancelLabel !== undefined) {
requestOptions.cancelLabel = options.cancelLabel;
}
return createElicitationRequest({
type: "confirmation",
message,
toolName: options.toolName,
serverId: options.serverId,
timeout: options.timeout,
options: requestOptions,
});
}
/**
* Create protocol-compliant text input request
*/
export function createTextInputRequest(message, options) {
const requestOptions = {};
if (options.placeholder !== undefined) {
requestOptions.placeholder = options.placeholder;
}
if (options.minLength !== undefined) {
requestOptions.minLength = options.minLength;
}
if (options.maxLength !== undefined) {
requestOptions.maxLength = options.maxLength;
}
if (options.pattern !== undefined) {
requestOptions.pattern = options.pattern;
}
if (options.multiline !== undefined) {
requestOptions.multiline = options.multiline;
}
return createElicitationRequest({
type: "text",
message,
toolName: options.toolName,
serverId: options.serverId,
defaultValue: options.defaultValue,
timeout: options.timeout,
options: requestOptions,
});
}
/**
* Create protocol-compliant select request
*/
export function createSelectRequest(message, selectOptions, options) {
return createElicitationRequest({
type: "select",
message,
toolName: options.toolName,
serverId: options.serverId,
defaultValue: options.defaultValue,
timeout: options.timeout,
options: {
options: selectOptions,
},
});
}
/**
* Create protocol-compliant form request
*/
export function createFormRequest(message, fields, options) {
const requestOptions = {
fields: fields,
};
if (options.submitLabel !== undefined) {
requestOptions.submitLabel = options.submitLabel;
}
return createElicitationRequest({
type: "form",
message,
toolName: options.toolName,
serverId: options.serverId,
timeout: options.timeout,
options: requestOptions,
});
}
/**
* Global protocol adapter instance
*/
export const globalElicitationProtocol = new ElicitationProtocolAdapter();