import * as Effect from "../../../Effect.ts" import * as Base64 from "../../../encoding/Base64.ts" import * as Match from "../../../Match.ts" import * as Predicate from "../../../Predicate.ts" import * as Schema from "../../../Schema.ts" import * as PublicMcpSchema from "../../McpSchema.ts" import * as McpCore from "../mcpCore.ts" import * as McpProtocol from "../mcpProtocol.ts" import * as McpRuntime from "../mcpRuntime.ts" import * as McpSchema from "../mcpSchema/v2025_11_25.ts" // Pings can arrive before initialize and do not require a client session. const ClientRequestRpcs = McpSchema.ClientRequestRpcs.omit("ping").middleware( PublicMcpSchema.McpServerClientMiddleware ).add(McpSchema.Ping) const ClientRpcs = ClientRequestRpcs.merge(McpSchema.ClientNotificationRpcs) const AdapterRpcs = ClientRpcs.omit("ping") const isToolOutputSchema = ( schema: PublicMcpSchema.ToolOutputJson | undefined ): schema is PublicMcpSchema.ToolJson => { if (schema?.type !== "object") { return false } const isPropertiesSupported = schema.properties === undefined || (Predicate.isReadonlyObject(schema.properties) && Object.values(schema.properties).every(Predicate.isReadonlyObject)) const isRequiredDefined = schema.required === undefined || (Array.isArray(schema.required) && schema.required.every(Predicate.isString)) return isPropertiesSupported && isRequiredDefined } const unsupported = ( operation: PublicMcpSchema.McpReverseOperationUnsupported["operation"], reason: string ) => new PublicMcpSchema.McpReverseOperationUnsupported({ operation, protocolVersion: McpSchema.protocolVersion, reason }) const requireCapability = ( profile: McpCore.NegotiatedProtocolProfile, operation: PublicMcpSchema.McpReverseOperationUnsupported["operation"], capability: "roots" | "sampling" | "elicitation" ) => Object.hasOwn(profile.clientCapabilities, capability) && profile.clientCapabilities[capability] !== undefined ? Effect.void : Effect.fail(unsupported(operation, `Client did not advertise the ${capability} capability`)) const resultRequiresSamplingTools = (result: typeof McpSchema.CreateMessage.successSchema.Type): boolean => result.stopReason === "toolUse" || ("type" in result.content ? result.content.type === "tool_use" || result.content.type === "tool_result" : result.content.some((block) => block.type === "tool_use" || block.type === "tool_result")) const hasElicitationModeCapability = ( profile: McpCore.NegotiatedProtocolProfile, mode: "form" | "url" ): boolean => { const elicitation = profile.clientCapabilities.elicitation if (elicitation === undefined) return false return mode === "form" ? elicitation.form !== undefined || elicitation.url === undefined : elicitation.url !== undefined } const projectContent = Effect.fnUntraced(function*(content: PublicMcpSchema.ContentBlock) { return Match.value(content).pipe( Match.when({ type: Match.is("text", "resource_link") }, (content) => content), Match.when({ type: Match.is("image", "audio") }, (content) => ({ type: content.type, mimeType: content.mimeType, data: Base64.encode(content.data), annotations: content.annotations, _meta: content._meta })), Match.when({ type: "resource" }, (content) => { const resource = content.resource if ("text" in resource) { return McpSchema.EmbeddedResource.make({ type: "resource", resource: { uri: resource.uri, mimeType: resource.mimeType, _meta: resource._meta, text: resource.text }, annotations: content.annotations, _meta: content._meta }) } return McpSchema.EmbeddedResource.make({ type: "resource", resource: { uri: resource.uri, mimeType: resource.mimeType, _meta: resource._meta, blob: Base64.encode(resource.blob) }, annotations: content.annotations, _meta: content._meta }) }), Match.exhaustive ) }) const projectStructuredContent = ( content: unknown ): Readonly> | undefined => content === undefined || isObject(content) ? content : undefined const isObject = (value: unknown): value is Readonly> => Predicate.isReadonlyObject(value) /** * @internal */ export const protocol = McpProtocol.make({ protocolVersion: McpSchema.protocolVersion, runtime: McpRuntime.stateful({ jsonRpc: { acceptsBatches: false }, http: { requiresVersionHeader: true } }), clientRpcs: ClientRpcs, clientNotificationRpcs: McpSchema.ClientNotificationRpcs, serverRequestRpcs: McpSchema.ServerRequestRpcs, serverNotificationRpcs: McpSchema.ServerNotificationRpcs, handlerRpcs: AdapterRpcs, makeHandlers: (core, lifecycle) => AdapterRpcs.of({ initialize: (request, { client }) => lifecycle.initialize(McpSchema.protocolVersion, { protocolVersion: McpSchema.protocolVersion, clientCapabilities: PublicMcpSchema.ClientCapabilities.make(request.capabilities), clientInfo: PublicMcpSchema.Implementation.make(request.clientInfo), requestMetadata: request._meta }, client.id).pipe( Effect.map((result) => McpSchema.InitializeResult.make({ protocolVersion: McpSchema.protocolVersion, capabilities: ({ experimental: result.capabilities.experimental, extensions: result.capabilities.extensions, logging: result.capabilities.logging ? {} : undefined, completions: result.capabilities.completions ? {} : undefined, prompts: result.capabilities.prompts, resources: result.capabilities.resources, tools: result.capabilities.tools }), serverInfo: result.serverInfo, instructions: result.instructions }) ) ), "logging/setLevel": ({ level }, { client, headers }) => lifecycle.setLogLevel(level, client.id, headers).pipe(Effect.as({})), "notifications/cancelled": () => Effect.void, "notifications/initialized": (_, { client, headers }) => lifecycle.clientNotification(McpCore.ClientNotification.Initialized(), client.id, headers), "notifications/progress": (progress, { client, headers }) => lifecycle.clientNotification( McpCore.ClientNotification.Progress({ progressToken: progress.progressToken, progress: progress.progress, total: progress.total, message: progress.message, metadata: progress._meta }), client.id, headers ), "notifications/roots/list_changed": (_, { client, headers }) => lifecycle.clientNotification(McpCore.ClientNotification.RootsChanged(), client.id, headers), "resources/list": () => PublicMcpSchema.McpServerClient.use((request) => core.resources.list(McpProtocol.profileFromClient(request))) .pipe( Effect.map((resources) => McpSchema.ListResourcesResult.make({ resources: resources.map((resource) => McpSchema.Resource.make(resource)) }) ) ), "resources/templates/list": () => PublicMcpSchema.McpServerClient.use((request) => core.resources.listTemplates(McpProtocol.profileFromClient(request)) ).pipe( Effect.map((resourceTemplates) => McpSchema.ListResourceTemplatesResult.make({ resourceTemplates: resourceTemplates.map((resourceTemplate) => McpSchema.ResourceTemplate.make(resourceTemplate) ) }) ) ), "resources/read": Effect.fnUntraced(function*({ uri }) { const request = yield* PublicMcpSchema.McpServerClient const result = yield* core.resources.read(uri, McpProtocol.invocationFromClient(request)).pipe( Effect.mapError(McpProtocol.ProtocolError.fromFeature) ) return McpSchema.ReadResourceResult.make({ contents: result.contents.map((content) => "text" in content ? { uri: content.uri, mimeType: content.mimeType, _meta: content._meta, text: content.text } : { uri: content.uri, mimeType: content.mimeType, _meta: content._meta, blob: Base64.encode(content.blob) } ), _meta: result._meta }) }), "resources/subscribe": ({ uri }, { client, headers }) => lifecycle.subscribe(uri, client.id, headers).pipe(Effect.as({})), "resources/unsubscribe": ({ uri }, { client, headers }) => lifecycle.unsubscribe(uri, client.id, headers).pipe(Effect.as({})), "prompts/list": () => PublicMcpSchema.McpServerClient.use((request) => core.prompts.list(McpProtocol.profileFromClient(request))) .pipe( Effect.map((prompts) => McpSchema.ListPromptsResult.make({ prompts: prompts.map((prompt) => McpSchema.Prompt.make(prompt)) }) ) ), "prompts/get": Effect.fnUntraced(function*({ arguments: args, name }) { const request = yield* PublicMcpSchema.McpServerClient const outcome = yield* core.prompts.get(name, args ?? {}, McpProtocol.invocationFromClient(request)).pipe( Effect.mapError(McpProtocol.ProtocolError.fromFeature) ) if (outcome._tag === "InputRequired") { return yield* McpProtocol.ProtocolError.fromFeature( new McpCore.UnsupportedByProtocol({ protocolVersion: McpSchema.protocolVersion, feature: "prompt input requirements" }) ) } const result = outcome.value const messages = yield* Effect.forEach(result.messages, (message) => projectContent(message.content).pipe( Effect.map((content) => ({ role: message.role, content })), Effect.mapError(McpProtocol.ProtocolError.fromTool) )) return McpSchema.GetPromptResult.make({ description: result.description, messages, _meta: result._meta }) }), "completion/complete": Effect.fnUntraced(function*(completeRequest) { const request = yield* PublicMcpSchema.McpServerClient const result = yield* core.completions.complete({ reference: completeRequest.ref.type === "ref/prompt" ? { type: "prompt", name: completeRequest.ref.name, title: completeRequest.ref.title } : { type: "resourceTemplate", uriTemplate: completeRequest.ref.uri }, argument: completeRequest.argument, context: completeRequest.context?.arguments === undefined ? undefined : { arguments: completeRequest.context.arguments }, metadata: completeRequest._meta }, McpProtocol.invocationFromClient(request)).pipe(Effect.mapError(McpProtocol.ProtocolError.fromFeature)) return McpSchema.CompleteResult.make({ completion: { values: Array.from(result.values), total: result.total, hasMore: result.hasMore }, _meta: result.metadata }) }), "tools/list": Effect.fnUntraced(function*() { const request = yield* PublicMcpSchema.McpServerClient const tools = yield* core.tools.list(McpProtocol.profileFromClient(request)) return McpSchema.ListToolsResult.make({ tools: tools.map((tool) => McpSchema.Tool.make({ name: tool.name, title: tool.title, description: tool.description, inputSchema: tool.inputSchema, outputSchema: isToolOutputSchema(tool.outputSchema) ? tool.outputSchema : undefined, icons: tool.icons, annotations: tool.annotations === undefined ? undefined : McpSchema.ToolAnnotations.make({ title: tool.annotations.title, readOnlyHint: tool.annotations.readOnlyHint, destructiveHint: tool.annotations.destructiveHint, idempotentHint: tool.annotations.idempotentHint, openWorldHint: tool.annotations.openWorldHint }), _meta: tool._meta }) ) }) }), "tools/call": Effect.fnUntraced(function*(call) { const request = yield* PublicMcpSchema.McpServerClient const result = yield* core.tools.call( { ...call, arguments: call.arguments ?? {} }, McpProtocol.invocationFromClient(request) ).pipe( Effect.flatMap((outcome) => McpProtocol.requireCompleteOperation(McpSchema.protocolVersion, outcome)), Effect.catchTag(["InvalidToolInput", "ToolExecutionError"], (error) => Effect.succeed(PublicMcpSchema.CallToolResult.make({ content: [PublicMcpSchema.TextContent.make({ type: "text", text: error.message })], isError: true }))), Effect.mapError(McpProtocol.ProtocolError.fromTool) ) const content = yield* Effect.forEach(McpProtocol.unwrapStringStructuredContent(result), projectContent).pipe( Effect.mapError(McpProtocol.ProtocolError.fromTool) ) const structuredContent = projectStructuredContent(result.structuredContent) return McpSchema.CallToolResult.make({ content, structuredContent, isError: result.isError, _meta: result._meta }) }) }), toReverseClient: (profile, client) => ({ listRoots: Effect.fnUntraced(function*(request) { yield* requireCapability(profile, "roots/list", "roots") const wireRequest = yield* McpProtocol.transcode( PublicMcpSchema.ListRoots.payloadSchema, McpSchema.ListRoots.payloadSchema, request ).pipe( Effect.mapError(() => unsupported("roots/list", "Request is not representable by this protocol")) ) const { roots } = yield* client["roots/list"](wireRequest).pipe( Effect.mapError(McpProtocol.reverseError("roots/list")) ) return new PublicMcpSchema.ListRootsResult({ roots }) }), createMessage: Effect.fnUntraced(function*(request) { yield* requireCapability(profile, "sampling/createMessage", "sampling") if ( McpProtocol.samplingRequestRequiresTools(request) && profile.clientCapabilities.sampling?.tools == undefined ) { return yield* unsupported("sampling/createMessage", "Client did not advertise the sampling.tools capability") } if ( (request.includeContext === "thisServer" || request.includeContext === "allServers") && profile.clientCapabilities.sampling?.context == undefined ) { return yield* unsupported("sampling/createMessage", "Client did not advertise the sampling.context capability") } const wireRequest = yield* McpProtocol.transcode( PublicMcpSchema.CreateMessage.payloadSchema, McpSchema.CreateMessage.payloadSchema, request ).pipe( Effect.mapError(() => unsupported("sampling/createMessage", "Request is not representable by this protocol")) ) const result = yield* client["sampling/createMessage"](wireRequest).pipe( Effect.mapError(McpProtocol.reverseError("sampling/createMessage")) ) if (resultRequiresSamplingTools(result) && profile.clientCapabilities.sampling?.tools == undefined) { return yield* unsupported("sampling/createMessage", "Client did not advertise the sampling.tools capability") } return yield* McpProtocol.transcode( McpSchema.CreateMessage.successSchema, PublicMcpSchema.CreateMessage.successSchema, result ).pipe( Effect.mapError(() => unsupported("sampling/createMessage", "Response is not representable by the canonical model") ) ) }), elicit: Effect.fnUntraced(function*(request) { yield* requireCapability(profile, "elicitation/create", "elicitation") const mode = request.mode === "url" ? "url" : "form" if (!hasElicitationModeCapability(profile, mode)) { return yield* unsupported("elicitation/create", `Client did not advertise the elicitation.${mode} capability`) } const wireRequest = yield* McpProtocol.transcode( PublicMcpSchema.Elicit.payloadSchema, McpSchema.Elicit.payloadSchema, request ).pipe( Effect.mapError(() => unsupported("elicitation/create", "Request is not representable by this protocol")) ) const result = yield* client["elicitation/create"](wireRequest).pipe( Effect.mapError(McpProtocol.reverseError("elicitation/create")) ) return yield* McpProtocol.transcode( McpSchema.Elicit.successSchema, PublicMcpSchema.Elicit.successSchema, result ).pipe( Effect.mapError(() => unsupported("elicitation/create", "Response is not representable by the canonical model")) ) }) }), projectNotification: (notification) => notification._tag === "ElicitationComplete" ? Effect.succeed({ tag: "notifications/elicitation/complete", payload: { elicitationId: notification.elicitationId } }) : McpProtocol.makeNotificationProjector({ supportsProgressMessage: true }, notification), normalizeCancellation: (payload) => Schema.decodeUnknownEffect(McpSchema.CancelledNotification.payloadSchema)(payload).pipe( Effect.map((request) => ({ requestId: request.requestId, reason: request.reason, metadata: request._meta })) ) })