| "use strict";
|
| Object.defineProperty(exports, "__esModule", { value: true });
|
| exports.Server = void 0;
|
| const protocol_js_1 = require("../shared/protocol.js");
|
| const types_js_1 = require("../types.js");
|
| const ajv_provider_js_1 = require("../validation/ajv-provider.js");
|
| const zod_compat_js_1 = require("./zod-compat.js");
|
| const server_js_1 = require("../experimental/tasks/server.js");
|
| const helpers_js_1 = require("../experimental/tasks/helpers.js");
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| class Server extends protocol_js_1.Protocol {
|
| |
| |
|
|
| constructor(_serverInfo, options) {
|
| super(options);
|
| this._serverInfo = _serverInfo;
|
|
|
| this._loggingLevels = new Map();
|
|
|
| this.LOG_LEVEL_SEVERITY = new Map(types_js_1.LoggingLevelSchema.options.map((level, index) => [level, index]));
|
|
|
| this.isMessageIgnored = (level, sessionId) => {
|
| const currentLevel = this._loggingLevels.get(sessionId);
|
| return currentLevel ? this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(currentLevel) : false;
|
| };
|
| this._capabilities = options?.capabilities ?? {};
|
| this._instructions = options?.instructions;
|
| this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new ajv_provider_js_1.AjvJsonSchemaValidator();
|
| this.setRequestHandler(types_js_1.InitializeRequestSchema, request => this._oninitialize(request));
|
| this.setNotificationHandler(types_js_1.InitializedNotificationSchema, () => this.oninitialized?.());
|
| if (this._capabilities.logging) {
|
| this.setRequestHandler(types_js_1.SetLevelRequestSchema, async (request, extra) => {
|
| const transportSessionId = extra.sessionId || extra.requestInfo?.headers['mcp-session-id'] || undefined;
|
| const { level } = request.params;
|
| const parseResult = types_js_1.LoggingLevelSchema.safeParse(level);
|
| if (parseResult.success) {
|
| this._loggingLevels.set(transportSessionId, parseResult.data);
|
| }
|
| return {};
|
| });
|
| }
|
| }
|
| |
| |
| |
| |
| |
| |
|
|
| get experimental() {
|
| if (!this._experimental) {
|
| this._experimental = {
|
| tasks: new server_js_1.ExperimentalServerTasks(this)
|
| };
|
| }
|
| return this._experimental;
|
| }
|
| |
| |
| |
| |
|
|
| registerCapabilities(capabilities) {
|
| if (this.transport) {
|
| throw new Error('Cannot register capabilities after connecting to transport');
|
| }
|
| this._capabilities = (0, protocol_js_1.mergeCapabilities)(this._capabilities, capabilities);
|
| }
|
| |
| |
|
|
| setRequestHandler(requestSchema, handler) {
|
| const shape = (0, zod_compat_js_1.getObjectShape)(requestSchema);
|
| const methodSchema = shape?.method;
|
| if (!methodSchema) {
|
| throw new Error('Schema is missing a method literal');
|
| }
|
|
|
| let methodValue;
|
| if ((0, zod_compat_js_1.isZ4Schema)(methodSchema)) {
|
| const v4Schema = methodSchema;
|
| const v4Def = v4Schema._zod?.def;
|
| methodValue = v4Def?.value ?? v4Schema.value;
|
| }
|
| else {
|
| const v3Schema = methodSchema;
|
| const legacyDef = v3Schema._def;
|
| methodValue = legacyDef?.value ?? v3Schema.value;
|
| }
|
| if (typeof methodValue !== 'string') {
|
| throw new Error('Schema method literal must be a string');
|
| }
|
| const method = methodValue;
|
| if (method === 'tools/call') {
|
| const wrappedHandler = async (request, extra) => {
|
| const validatedRequest = (0, zod_compat_js_1.safeParse)(types_js_1.CallToolRequestSchema, request);
|
| if (!validatedRequest.success) {
|
| const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
| throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage}`);
|
| }
|
| const { params } = validatedRequest.data;
|
| const result = await Promise.resolve(handler(request, extra));
|
|
|
| if (params.task) {
|
| const taskValidationResult = (0, zod_compat_js_1.safeParse)(types_js_1.CreateTaskResultSchema, result);
|
| if (!taskValidationResult.success) {
|
| const errorMessage = taskValidationResult.error instanceof Error
|
| ? taskValidationResult.error.message
|
| : String(taskValidationResult.error);
|
| throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
| }
|
| return taskValidationResult.data;
|
| }
|
|
|
| const validationResult = (0, zod_compat_js_1.safeParse)(types_js_1.CallToolResultSchema, result);
|
| if (!validationResult.success) {
|
| const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
| throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage}`);
|
| }
|
| return validationResult.data;
|
| };
|
|
|
| return super.setRequestHandler(requestSchema, wrappedHandler);
|
| }
|
|
|
| return super.setRequestHandler(requestSchema, handler);
|
| }
|
| assertCapabilityForMethod(method) {
|
| switch (method) {
|
| case 'sampling/createMessage':
|
| if (!this._clientCapabilities?.sampling) {
|
| throw new Error(`Client does not support sampling (required for ${method})`);
|
| }
|
| break;
|
| case 'elicitation/create':
|
| if (!this._clientCapabilities?.elicitation) {
|
| throw new Error(`Client does not support elicitation (required for ${method})`);
|
| }
|
| break;
|
| case 'roots/list':
|
| if (!this._clientCapabilities?.roots) {
|
| throw new Error(`Client does not support listing roots (required for ${method})`);
|
| }
|
| break;
|
| case 'ping':
|
|
|
| break;
|
| }
|
| }
|
| assertNotificationCapability(method) {
|
| switch (method) {
|
| case 'notifications/message':
|
| if (!this._capabilities.logging) {
|
| throw new Error(`Server does not support logging (required for ${method})`);
|
| }
|
| break;
|
| case 'notifications/resources/updated':
|
| case 'notifications/resources/list_changed':
|
| if (!this._capabilities.resources) {
|
| throw new Error(`Server does not support notifying about resources (required for ${method})`);
|
| }
|
| break;
|
| case 'notifications/tools/list_changed':
|
| if (!this._capabilities.tools) {
|
| throw new Error(`Server does not support notifying of tool list changes (required for ${method})`);
|
| }
|
| break;
|
| case 'notifications/prompts/list_changed':
|
| if (!this._capabilities.prompts) {
|
| throw new Error(`Server does not support notifying of prompt list changes (required for ${method})`);
|
| }
|
| break;
|
| case 'notifications/elicitation/complete':
|
| if (!this._clientCapabilities?.elicitation?.url) {
|
| throw new Error(`Client does not support URL elicitation (required for ${method})`);
|
| }
|
| break;
|
| case 'notifications/cancelled':
|
|
|
| break;
|
| case 'notifications/progress':
|
|
|
| break;
|
| }
|
| }
|
| assertRequestHandlerCapability(method) {
|
|
|
|
|
| if (!this._capabilities) {
|
| return;
|
| }
|
| switch (method) {
|
| case 'completion/complete':
|
| if (!this._capabilities.completions) {
|
| throw new Error(`Server does not support completions (required for ${method})`);
|
| }
|
| break;
|
| case 'logging/setLevel':
|
| if (!this._capabilities.logging) {
|
| throw new Error(`Server does not support logging (required for ${method})`);
|
| }
|
| break;
|
| case 'prompts/get':
|
| case 'prompts/list':
|
| if (!this._capabilities.prompts) {
|
| throw new Error(`Server does not support prompts (required for ${method})`);
|
| }
|
| break;
|
| case 'resources/list':
|
| case 'resources/templates/list':
|
| case 'resources/read':
|
| if (!this._capabilities.resources) {
|
| throw new Error(`Server does not support resources (required for ${method})`);
|
| }
|
| break;
|
| case 'tools/call':
|
| case 'tools/list':
|
| if (!this._capabilities.tools) {
|
| throw new Error(`Server does not support tools (required for ${method})`);
|
| }
|
| break;
|
| case 'tasks/get':
|
| case 'tasks/list':
|
| case 'tasks/result':
|
| case 'tasks/cancel':
|
| if (!this._capabilities.tasks) {
|
| throw new Error(`Server does not support tasks capability (required for ${method})`);
|
| }
|
| break;
|
| case 'ping':
|
| case 'initialize':
|
|
|
| break;
|
| }
|
| }
|
| assertTaskCapability(method) {
|
| (0, helpers_js_1.assertClientRequestTaskCapability)(this._clientCapabilities?.tasks?.requests, method, 'Client');
|
| }
|
| assertTaskHandlerCapability(method) {
|
|
|
|
|
| if (!this._capabilities) {
|
| return;
|
| }
|
| (0, helpers_js_1.assertToolsCallTaskCapability)(this._capabilities.tasks?.requests, method, 'Server');
|
| }
|
| async _oninitialize(request) {
|
| const requestedVersion = request.params.protocolVersion;
|
| this._clientCapabilities = request.params.capabilities;
|
| this._clientVersion = request.params.clientInfo;
|
| const protocolVersion = types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : types_js_1.LATEST_PROTOCOL_VERSION;
|
| return {
|
| protocolVersion,
|
| capabilities: this.getCapabilities(),
|
| serverInfo: this._serverInfo,
|
| ...(this._instructions && { instructions: this._instructions })
|
| };
|
| }
|
| |
| |
|
|
| getClientCapabilities() {
|
| return this._clientCapabilities;
|
| }
|
| |
| |
|
|
| getClientVersion() {
|
| return this._clientVersion;
|
| }
|
| getCapabilities() {
|
| return this._capabilities;
|
| }
|
| async ping() {
|
| return this.request({ method: 'ping' }, types_js_1.EmptyResultSchema);
|
| }
|
|
|
| async createMessage(params, options) {
|
|
|
| if (params.tools || params.toolChoice) {
|
| if (!this._clientCapabilities?.sampling?.tools) {
|
| throw new Error('Client does not support sampling tools capability.');
|
| }
|
| }
|
|
|
|
|
|
|
| if (params.messages.length > 0) {
|
| const lastMessage = params.messages[params.messages.length - 1];
|
| const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
| const hasToolResults = lastContent.some(c => c.type === 'tool_result');
|
| const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
| const previousContent = previousMessage
|
| ? Array.isArray(previousMessage.content)
|
| ? previousMessage.content
|
| : [previousMessage.content]
|
| : [];
|
| const hasPreviousToolUse = previousContent.some(c => c.type === 'tool_use');
|
| if (hasToolResults) {
|
| if (lastContent.some(c => c.type !== 'tool_result')) {
|
| throw new Error('The last message must contain only tool_result content if any is present');
|
| }
|
| if (!hasPreviousToolUse) {
|
| throw new Error('tool_result blocks are not matching any tool_use from the previous message');
|
| }
|
| }
|
| if (hasPreviousToolUse) {
|
| const toolUseIds = new Set(previousContent.filter(c => c.type === 'tool_use').map(c => c.id));
|
| const toolResultIds = new Set(lastContent.filter(c => c.type === 'tool_result').map(c => c.toolUseId));
|
| if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every(id => toolResultIds.has(id))) {
|
| throw new Error('ids of tool_result blocks and tool_use blocks from previous message do not match');
|
| }
|
| }
|
| }
|
|
|
| if (params.tools) {
|
| return this.request({ method: 'sampling/createMessage', params }, types_js_1.CreateMessageResultWithToolsSchema, options);
|
| }
|
| return this.request({ method: 'sampling/createMessage', params }, types_js_1.CreateMessageResultSchema, options);
|
| }
|
| |
| |
| |
| |
| |
| |
|
|
| async elicitInput(params, options) {
|
| const mode = (params.mode ?? 'form');
|
| switch (mode) {
|
| case 'url': {
|
| if (!this._clientCapabilities?.elicitation?.url) {
|
| throw new Error('Client does not support url elicitation.');
|
| }
|
| const urlParams = params;
|
| return this.request({ method: 'elicitation/create', params: urlParams }, types_js_1.ElicitResultSchema, options);
|
| }
|
| case 'form': {
|
| if (!this._clientCapabilities?.elicitation?.form) {
|
| throw new Error('Client does not support form elicitation.');
|
| }
|
| const formParams = params.mode === 'form' ? params : { ...params, mode: 'form' };
|
| const result = await this.request({ method: 'elicitation/create', params: formParams }, types_js_1.ElicitResultSchema, options);
|
| if (result.action === 'accept' && result.content && formParams.requestedSchema) {
|
| try {
|
| const validator = this._jsonSchemaValidator.getValidator(formParams.requestedSchema);
|
| const validationResult = validator(result.content);
|
| if (!validationResult.valid) {
|
| throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Elicitation response content does not match requested schema: ${validationResult.errorMessage}`);
|
| }
|
| }
|
| catch (error) {
|
| if (error instanceof types_js_1.McpError) {
|
| throw error;
|
| }
|
| throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Error validating elicitation response: ${error instanceof Error ? error.message : String(error)}`);
|
| }
|
| }
|
| return result;
|
| }
|
| }
|
| }
|
| |
| |
| |
| |
| |
| |
| |
|
|
| createElicitationCompletionNotifier(elicitationId, options) {
|
| if (!this._clientCapabilities?.elicitation?.url) {
|
| throw new Error('Client does not support URL elicitation (required for notifications/elicitation/complete)');
|
| }
|
| return () => this.notification({
|
| method: 'notifications/elicitation/complete',
|
| params: {
|
| elicitationId
|
| }
|
| }, options);
|
| }
|
| async listRoots(params, options) {
|
| return this.request({ method: 'roots/list', params }, types_js_1.ListRootsResultSchema, options);
|
| }
|
| |
| |
| |
| |
| |
| |
|
|
| async sendLoggingMessage(params, sessionId) {
|
| if (this._capabilities.logging) {
|
| if (!this.isMessageIgnored(params.level, sessionId)) {
|
| return this.notification({ method: 'notifications/message', params });
|
| }
|
| }
|
| }
|
| async sendResourceUpdated(params) {
|
| return this.notification({
|
| method: 'notifications/resources/updated',
|
| params
|
| });
|
| }
|
| async sendResourceListChanged() {
|
| return this.notification({
|
| method: 'notifications/resources/list_changed'
|
| });
|
| }
|
| async sendToolListChanged() {
|
| return this.notification({ method: 'notifications/tools/list_changed' });
|
| }
|
| async sendPromptListChanged() {
|
| return this.notification({ method: 'notifications/prompts/list_changed' });
|
| }
|
| }
|
| exports.Server = Server;
|
| |