| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- "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");
- /**
- * An MCP server on top of a pluggable transport.
- *
- * This server will automatically respond to the initialization flow as initiated from the client.
- *
- * To use with custom types, extend the base Request/Notification/Result types and pass them as type parameters:
- *
- * ```typescript
- * // Custom schemas
- * const CustomRequestSchema = RequestSchema.extend({...})
- * const CustomNotificationSchema = NotificationSchema.extend({...})
- * const CustomResultSchema = ResultSchema.extend({...})
- *
- * // Type aliases
- * type CustomRequest = z.infer<typeof CustomRequestSchema>
- * type CustomNotification = z.infer<typeof CustomNotificationSchema>
- * type CustomResult = z.infer<typeof CustomResultSchema>
- *
- * // Create typed server
- * const server = new Server<CustomRequest, CustomNotification, CustomResult>({
- * name: "CustomServer",
- * version: "1.0.0"
- * })
- * ```
- * @deprecated Use `McpServer` instead for the high-level API. Only use `Server` for advanced use cases.
- */
- class Server extends protocol_js_1.Protocol {
- /**
- * Initializes this server with the given name and version information.
- */
- constructor(_serverInfo, options) {
- super(options);
- this._serverInfo = _serverInfo;
- // Map log levels by session id
- this._loggingLevels = new Map();
- // Map LogLevelSchema to severity index
- this.LOG_LEVEL_SEVERITY = new Map(types_js_1.LoggingLevelSchema.options.map((level, index) => [level, index]));
- // Is a message with the given level ignored in the log level set for the given session id?
- 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 {};
- });
- }
- }
- /**
- * Access experimental features.
- *
- * WARNING: These APIs are experimental and may change without notice.
- *
- * @experimental
- */
- get experimental() {
- if (!this._experimental) {
- this._experimental = {
- tasks: new server_js_1.ExperimentalServerTasks(this)
- };
- }
- return this._experimental;
- }
- /**
- * Registers new capabilities. This can only be called before connecting to a transport.
- *
- * The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
- */
- 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);
- }
- /**
- * Override request handler registration to enforce server-side validation for tools/call.
- */
- 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');
- }
- // Extract literal value using type-safe property access
- 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));
- // When task creation is requested, validate and return CreateTaskResult
- 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;
- }
- // For non-task requests, validate against CallToolResultSchema
- 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;
- };
- // Install the wrapped handler
- return super.setRequestHandler(requestSchema, wrappedHandler);
- }
- // Other handlers use default behavior
- 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':
- // No specific capability required for 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':
- // Cancellation notifications are always allowed
- break;
- case 'notifications/progress':
- // Progress notifications are always allowed
- break;
- }
- }
- assertRequestHandlerCapability(method) {
- // Task handlers are registered in Protocol constructor before _capabilities is initialized
- // Skip capability check for task methods during initialization
- 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':
- // No specific capability required for these methods
- break;
- }
- }
- assertTaskCapability(method) {
- (0, helpers_js_1.assertClientRequestTaskCapability)(this._clientCapabilities?.tasks?.requests, method, 'Client');
- }
- assertTaskHandlerCapability(method) {
- // Task handlers are registered in Protocol constructor before _capabilities is initialized
- // Skip capability check for task methods during initialization
- 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 })
- };
- }
- /**
- * After initialization has completed, this will be populated with the client's reported capabilities.
- */
- getClientCapabilities() {
- return this._clientCapabilities;
- }
- /**
- * After initialization has completed, this will be populated with information about the client's name and version.
- */
- getClientVersion() {
- return this._clientVersion;
- }
- getCapabilities() {
- return this._capabilities;
- }
- async ping() {
- return this.request({ method: 'ping' }, types_js_1.EmptyResultSchema);
- }
- // Implementation
- async createMessage(params, options) {
- // Capability check - only required when tools/toolChoice are provided
- if (params.tools || params.toolChoice) {
- if (!this._clientCapabilities?.sampling?.tools) {
- throw new Error('Client does not support sampling tools capability.');
- }
- }
- // Message structure validation - always validate tool_use/tool_result pairs.
- // These may appear even without tools/toolChoice in the current request when
- // a previous sampling request returned tool_use and this is a follow-up with results.
- 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');
- }
- }
- }
- // Use different schemas based on whether tools are provided
- 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);
- }
- /**
- * Creates an elicitation request for the given parameters.
- * For backwards compatibility, `mode` may be omitted for form requests and will default to `'form'`.
- * @param params The parameters for the elicitation request.
- * @param options Optional request options.
- * @returns The result of the elicitation request.
- */
- 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;
- }
- }
- }
- /**
- * Creates a reusable callback that, when invoked, will send a `notifications/elicitation/complete`
- * notification for the specified elicitation ID.
- *
- * @param elicitationId The ID of the elicitation to mark as complete.
- * @param options Optional notification options. Useful when the completion notification should be related to a prior request.
- * @returns A function that emits the completion notification when awaited.
- */
- 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);
- }
- /**
- * Sends a logging message to the client, if connected.
- * Note: You only need to send the parameters object, not the entire JSON RPC message
- * @see LoggingMessageNotification
- * @param params
- * @param sessionId optional for stateless and backward compatibility
- */
- 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;
- //# sourceMappingURL=index.js.map
|