1/**
2 * Runtime descriptors for MCP protocol adapters.
3 *
4 * @internal
5 */
6import type { NonEmptyReadonlyArray } from "../../Array.ts"
7import * as Cause from "../../Cause.ts"
8import * as Context from "../../Context.ts"
9import * as Effect from "../../Effect.ts"
10import type * as Headers from "../../http/Headers.ts"
11import { appendPreResponseHandlerUnsafe } from "../../http/HttpEffect.ts"
12import * as HttpServerRequest from "../../http/HttpServerRequest.ts"
13import * as HttpServerResponse from "../../http/HttpServerResponse.ts"
14import * as Layer from "../../Layer.ts"
15import type * as LogLevel from "../../LogLevel.ts"
16import * as Predicate from "../../Predicate.ts"
17import * as Result from "../../Result.ts"
18import * as RpcGroup from "../../rpc/RpcGroup.ts"
19import type * as RpcMessage from "../../rpc/RpcMessage.ts"
20import type * as PublicMcpProtocol from "../McpProtocol.ts"
21import * as PublicMcpSchema from "../McpSchema.ts"
22import type * as McpCore from "./mcpCore.ts"
23import * as McpProtocol from "./mcpProtocol.ts"
24import * as McpProtocolRegistry from "./mcpProtocolRegistry.ts"
25import * as McpStatefulRuntime from "./mcpStatefulRuntime.ts"
26
27const MCP_SESSION_ID_HEADER = "mcp-session-id"
28const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version"
29const MCP_METHOD_HEADER = "mcp-method"
30const MCP_NAME_HEADER = "mcp-name"
31const PROTOCOL_VERSION_METADATA_KEY = "io.modelcontextprotocol/protocolVersion"
32const CLIENT_CAPABILITIES_METADATA_KEY = "io.modelcontextprotocol/clientCapabilities"
33const UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE = -32022
34
35const asRecord = (input: unknown): Record<string, unknown> | undefined =>
36 typeof input === "object" && input !== null ? input as Record<string, unknown> : undefined
37
38const protocolVersionClaim = (input: unknown): { readonly present: boolean; readonly value: unknown } => {
39 const metadata = asRecord(input)
40 return metadata !== undefined && PROTOCOL_VERSION_METADATA_KEY in metadata
41 ? { present: true, value: metadata[PROTOCOL_VERSION_METADATA_KEY] }
42 : { present: false, value: undefined }
43}
44
45/**
46 * @internal
47 */
48export const hasRequestProtocolVersion = (input: unknown): boolean =>
49 protocolVersionClaim(asRecord(asRecord(input)?.params)?._meta).present
50
51const routingNameKey = (method: string): "name" | "uri" | undefined => {
52 switch (method) {
53 case "tools/call":
54 case "prompts/get":
55 return "name"
56 case "resources/read":
57 return "uri"
58 default:
59 return undefined
60 }
61}
62
63const headerMismatch = (message: string): HttpProtocolSelection => ({
64 _tag: "Rejected",
65 status: 400,
66 error: { code: PublicMcpSchema.HEADER_MISMATCH_ERROR_CODE, message }
67})
68
69const PingRpcs = RpcGroup.make(PublicMcpSchema.Ping)
70
71/**
72 * @internal
73 */
74export interface RequestBinding {
75 readonly initializePayload: typeof PublicMcpSchema.Initialize.payloadSchema.Type
76 readonly negotiatedProfile: McpCore.NegotiatedProtocolProfile
77 readonly protocol: PublicMcpProtocol.AnyProtocolAdapter
78}
79
80/**
81 * @internal
82 */
83export interface PreparedRequest {
84 readonly protocol: PublicMcpProtocol.AnyProtocolAdapter
85 readonly binding?: RequestBinding | undefined
86 readonly profile?: McpCore.NegotiatedProtocolProfile<string> | undefined
87 readonly requestContext?: PublicMcpSchema.McpRequestContext["Service"] | undefined
88}
89
90type HttpProtocolSelection =
91 | {
92 readonly _tag: "Accepted"
93 readonly binding: RequestBinding | undefined
94 readonly protocol?: PublicMcpProtocol.AnyProtocolAdapter | undefined
95 }
96 | {
97 readonly _tag: "Rejected"
98 readonly status: 400 | 404
99 readonly error?: {
100 readonly code: number
101 readonly message: string
102 readonly data?: unknown
103 } | undefined
104 }
105
106/**
107 * @internal
108 */
109export type HttpAdmission =
110 | {
111 readonly _tag: "Accepted"
112 readonly acknowledge: boolean
113 readonly isSubscription: boolean
114 }
115 | {
116 readonly _tag: "Rejected"
117 readonly response: HttpServerResponse.HttpServerResponse
118 }
119
120/**
121 * @internal
122 */
123export interface HandlerInstallationOptions {
124 readonly core: McpCore.McpCore
125 readonly subscribeServerNotifications: McpProtocol.HandlerInstallationContext["subscribeServerNotifications"]
126 readonly sendNotification?: NonNullable<McpProtocol.HandlerInstallationContext["sendNotification"]>
127 readonly markSubscriptionCancelled?: NonNullable<
128 McpProtocol.HandlerInstallationContext["markSubscriptionCancelled"]
129 >
130 readonly terminateSubscription?: NonNullable<McpProtocol.HandlerInstallationContext["terminateSubscription"]>
131 readonly defaultLogLevel: LogLevel.LogLevel
132 readonly serverInfo: {
133 readonly name: string
134 readonly version: string
135 readonly instructions?: string | undefined
136 readonly description?: string | undefined
137 readonly websiteUrl?: string | undefined
138 readonly icons?: ReadonlyArray<PublicMcpSchema.Icon> | undefined
139 readonly extensions?: NonNullable<PublicMcpSchema.ServerCapabilities["extensions"]> | undefined
140 }
141}
142
143/**
144 * @internal
145 */
146export const stateful = (
147 transport: PublicMcpProtocol.TransportPolicy
148): PublicMcpProtocol.StatefulRuntimeDescriptor => ({
149 _tag: "Stateful",
150 transport
151})
152
153/**
154 * @internal
155 */
156export interface ServerRuntimeShape {
157 readonly protocols: NonEmptyReadonlyArray<PublicMcpProtocol.AnyProtocolAdapter>
158 readonly clientRpcs: McpProtocolRegistry.ProtocolRegistry<PublicMcpProtocol.AnyProtocolAdapter>["clientRpcs"]
159 readonly selectProtocol: (offeredVersion: string) => PublicMcpProtocol.AnyProtocolAdapter
160 readonly protocolForInternalTag: (tag: string) => PublicMcpProtocol.AnyProtocolAdapter
161 readonly routeClientRequest: (
162 protocol: PublicMcpProtocol.AnyProtocolAdapter,
163 request: RpcMessage.RequestEncoded
164 ) => RpcMessage.RequestEncoded
165 readonly prepareRequest: (
166 clientId: number,
167 headers: Headers.Headers,
168 request: RpcMessage.RequestEncoded
169 ) => Effect.Effect<PreparedRequest, unknown>
170 readonly resolveRequest: (clientId: number, headers: Headers.Headers) => RequestBinding | undefined
171 readonly admitHttp: (headers: Headers.Headers, input: Result.Result<unknown, unknown>) => HttpAdmission
172 readonly effectLogLevel: (
173 clientId: number,
174 headers: Headers.Headers,
175 fallback: LogLevel.LogLevel
176 ) => LogLevel.LogLevel
177 readonly disconnect: (clientId: number) => void
178 readonly deliveryClientIds: () => Iterable<number>
179 readonly canDeliver: (
180 clientId: number,
181 headers: Headers.Headers,
182 notification: McpCore.ServerNotification,
183 fallbackLogLevel: LogLevel.LogLevel
184 ) => boolean
185 readonly installHandlers: (
186 options: HandlerInstallationOptions
187 ) => Effect.Effect<Context.Context<never>, never, unknown>
188}
189
190/**
191 * @internal
192 */
193export class ServerRuntime extends Context.Service<ServerRuntime, ServerRuntimeShape>()(
194 "effect/ai/McpRuntime/ServerRuntime"
195) {}
196
197/**
198 * @internal
199 */
200export const selectStatefulProtocol = <Protocol extends PublicMcpProtocol.AnyProtocolAdapter>(
201 protocols: ReadonlyArray<Protocol>,
202 offeredVersion: unknown
203): Protocol | undefined =>
204 protocols.find((protocol) => protocol.runtime._tag === "Stateful" && protocol.protocolVersion === offeredVersion) ??
205 protocols.find((protocol) => protocol.runtime._tag === "Stateful")
206
207/**
208 * @internal
209 */
210export const make = Effect.fnUntraced(function*(
211 protocols: NonEmptyReadonlyArray<PublicMcpProtocol.AnyProtocolAdapter>
212) {
213 const statefulProtocol = protocols.find((protocol) => protocol.runtime._tag === "Stateful")
214 const stateful = statefulProtocol === undefined ? undefined : McpStatefulRuntime.make()
215 const protocolVersions = protocols.map((protocol) => protocol.protocolVersion)
216 let statelessDescriptor: PublicMcpProtocol.StatelessRuntimeDescriptor | undefined
217 let statelessProtocol: PublicMcpProtocol.AnyProtocolAdapter | undefined
218 for (const protocol of protocols) {
219 if (protocol.runtime._tag !== "Stateless") {
220 continue
221 }
222 if (statelessDescriptor !== undefined) {
223 return yield* new Cause.IllegalArgumentError(
224 "MCP runtime supports at most one stateless protocol"
225 )
226 }
227 statelessDescriptor = protocol.runtime
228 statelessProtocol = protocol
229 }
230 const registry = yield* McpProtocolRegistry.make(protocols)
231 const selectHttpProtocol = (headers: Headers.Headers, input: unknown): HttpProtocolSelection => {
232 const protocolVersion = headers[MCP_PROTOCOL_VERSION_HEADER]
233 const sessionId = headers[MCP_SESSION_ID_HEADER]
234 const inputRecord = asRecord(input)
235 const metadata = asRecord(asRecord(inputRecord?.params)?._meta)
236 const claim = protocolVersionClaim(metadata)
237 const id = inputRecord?.id
238 const isInitialize = inputRecord?.jsonrpc === "2.0" && inputRecord.method === "initialize" &&
239 (typeof id === "string" || typeof id === "number")
240 const isCancellationNotification = inputRecord?.jsonrpc === "2.0" &&
241 inputRecord.method === "notifications/cancelled" && id === undefined
242 const isStatelessRequest = claim.present || ((!isInitialize || stateful === undefined) &&
243 statelessProtocol !== undefined &&
244 (protocolVersion === statelessProtocol.protocolVersion ||
245 (stateful === undefined && inputRecord?.jsonrpc === "2.0" &&
246 typeof inputRecord.method === "string" && (typeof id === "string" || typeof id === "number"))))
247 if (isStatelessRequest) {
248 if (protocolVersion === undefined) {
249 return headerMismatch("MCP-Protocol-Version header is required")
250 }
251 if (!isCancellationNotification && (metadata === undefined || typeof claim.value !== "string")) {
252 return {
253 _tag: "Rejected",
254 status: 400,
255 error: {
256 code: PublicMcpSchema.INVALID_PARAMS_ERROR_CODE,
257 message: "Required request metadata is missing"
258 }
259 }
260 }
261 if ((!isCancellationNotification || claim.present) && claim.value !== protocolVersion) {
262 return headerMismatch("MCP-Protocol-Version header does not match request metadata")
263 }
264 if (!isCancellationNotification && asRecord(metadata?.[CLIENT_CAPABILITIES_METADATA_KEY]) === undefined) {
265 return {
266 _tag: "Rejected",
267 status: 400,
268 error: {
269 code: PublicMcpSchema.INVALID_PARAMS_ERROR_CODE,
270 message: `${CLIENT_CAPABILITIES_METADATA_KEY} request metadata is required`
271 }
272 }
273 }
274 if (statelessProtocol === undefined || protocolVersion !== statelessProtocol.protocolVersion) {
275 return {
276 _tag: "Rejected",
277 status: 400,
278 error: {
279 code: UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE,
280 message: `Unsupported protocol version '${protocolVersion}'`,
281 data: {
282 supported: protocolVersions,
283 requested: protocolVersion
284 }
285 }
286 }
287 }
288 const method = inputRecord?.method
289 if (typeof method !== "string" || headers[MCP_METHOD_HEADER] !== method) {
290 return headerMismatch("Mcp-Method header does not match request method")
291 }
292 const nameKey = routingNameKey(method)
293 if (nameKey !== undefined) {
294 const name = asRecord(inputRecord?.params)?.[nameKey]
295 const header = headers[MCP_NAME_HEADER]
296 if (typeof name !== "string" || header === undefined || McpProtocol.decodeRoutingHeader(header) !== name) {
297 return headerMismatch("Mcp-Name header does not match request parameters")
298 }
299 }
300 return { _tag: "Accepted", binding: undefined, protocol: statelessProtocol }
301 }
302 const binding = sessionId === undefined ? undefined : stateful?.resolveSessionId(sessionId)
303 if (sessionId !== undefined && binding === undefined) {
304 return { _tag: "Rejected", status: 404 }
305 }
306 if (
307 !isInitialize &&
308 protocolVersion !== undefined &&
309 !registry.protocols.some((protocol) => protocol.protocolVersion === protocolVersion)
310 ) {
311 return { _tag: "Rejected", status: 400 }
312 }
313 if (
314 !isInitialize &&
315 binding?.protocol.runtime.transport.http.requiresVersionHeader === true &&
316 protocolVersion !== binding.protocol.protocolVersion
317 ) {
318 return { _tag: "Rejected", status: 400 }
319 }
320 return { _tag: "Accepted", binding, protocol: binding?.protocol }
321 }
322 return ServerRuntime.of({
323 protocols: registry.protocols,
324 clientRpcs: registry.clientRpcs,
325 selectProtocol: registry.select,
326 protocolForInternalTag: registry.protocolForInternalTag,
327 routeClientRequest: registry.routeClientRequest,
328 prepareRequest: Effect.fnUntraced(function*(clientId, headers, request) {
329 const metadata = asRecord(request.payload)?._meta
330 const claim = protocolVersionClaim(metadata)
331 const requestedVersion = typeof claim.value === "string" ? claim.value : undefined
332 const binding = claim.present ? undefined : stateful?.resolve(clientId, headers)
333 let protocol: PublicMcpProtocol.AnyProtocolAdapter
334 if (claim.present) {
335 protocol = registry.protocols.find((protocol) => protocol.protocolVersion === requestedVersion) ??
336 statelessProtocol ?? registry.protocols[0]
337 } else if (binding !== undefined) {
338 protocol = binding.protocol
339 } else if (request.tag === "initialize") {
340 const offeredVersion = (request.payload as any)?.protocolVersion
341 const selected = selectStatefulProtocol(registry.protocols, offeredVersion)
342 if (selected === undefined) {
343 return yield* new McpProtocol.ProtocolError({
344 code: UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE,
345 message: `initialize is not supported by the configured MCP protocols (requested '${offeredVersion}')`
346 })
347 }
348 protocol = selected
349 } else if (request.tag === "ping") {
350 // An unversioned ping may precede initialize, even with a stateless adapter first.
351 protocol = selectStatefulProtocol(registry.protocols, undefined) ?? registry.protocols[0]
352 } else {
353 protocol = registry.protocols[0]
354 }
355 if (protocol.runtime._tag === "Stateful") {
356 return { protocol, binding }
357 }
358 if (request.isNotification && request.tag === "notifications/cancelled") {
359 return { protocol }
360 }
361 if (statelessDescriptor === undefined) {
362 return yield* Effect.die("MCP stateless runtime invariant failed")
363 }
364 if (requestedVersion !== undefined && requestedVersion !== protocol.protocolVersion) {
365 return yield* new McpProtocol.ProtocolError({
366 code: UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE,
367 message: `Unsupported protocol version '${requestedVersion}'`,
368 data: { supported: protocolVersions, requested: requestedVersion }
369 })
370 }
371 const decodedProfile = yield* statelessDescriptor.profileFromRequestMetadata(metadata)
372 const profile: McpCore.NegotiatedProtocolProfile<string> = {
373 protocolVersion: decodedProfile.protocolVersion,
374 clientCapabilities: decodedProfile.clientCapabilities,
375 clientInfo: decodedProfile.clientInfo,
376 requestMetadata: decodedProfile.requestMetadata
377 }
378 const requestContext = PublicMcpSchema.McpRequestContext.of({
379 clientId,
380 protocolVersion: profile.protocolVersion,
381 clientCapabilities: profile.clientCapabilities,
382 clientInfo: profile.clientInfo,
383 requestMetadata: profile.requestMetadata
384 })
385 return { protocol, profile, requestContext }
386 }),
387 resolveRequest: (clientId, headers) => stateful?.resolve(clientId, headers),
388 admitHttp: (headers, parsed) => {
389 const reject = (status: number, error?: unknown, id: string | number | null = null): HttpAdmission => ({
390 _tag: "Rejected",
391 response: error === undefined
392 ? HttpServerResponse.empty({ status })
393 : HttpServerResponse.jsonUnsafe({ jsonrpc: "2.0", id, error }, { status })
394 })
395 if (Result.isFailure(parsed)) {
396 const version = headers[MCP_PROTOCOL_VERSION_HEADER]
397 if (version !== undefined && !registry.protocols.some((protocol) => protocol.protocolVersion === version)) {
398 return reject(400)
399 }
400 const admission = selectHttpProtocol(headers, undefined)
401 return admission._tag === "Rejected" && admission.error === undefined
402 ? reject(admission.status)
403 : reject(200, new PublicMcpSchema.ParseError({ message: "Parse error" }))
404 }
405 const input = parsed.success
406 if (Array.isArray(input)) {
407 if (input.length === 0) {
408 return reject(400, new PublicMcpSchema.InvalidRequest({ message: "Invalid Request" }))
409 }
410 const admission = selectHttpProtocol(headers, input)
411 if (
412 admission._tag === "Rejected" ||
413 input.some(hasRequestProtocolVersion) ||
414 input.some((message) => Predicate.hasProperty(message, "method") && message.method === "initialize") ||
415 admission.binding?.protocol.runtime.transport.jsonRpc.acceptsBatches !== true
416 ) {
417 return reject(400)
418 }
419 return {
420 _tag: "Accepted",
421 acknowledge: !input.some((message) =>
422 Predicate.hasProperty(message, "method") && Predicate.hasProperty(message, "id")
423 ),
424 isSubscription: false
425 }
426 }
427 const hasId = Predicate.hasProperty(input, "id")
428 const id = hasId && (typeof input.id === "string" || typeof input.id === "number") ? input.id : null
429 const isJsonRpc = Predicate.hasProperty(input, "jsonrpc") && input.jsonrpc === "2.0"
430 const hasValidRequestId = !hasId || typeof input.id === "string" || typeof input.id === "number"
431 const isRequest = isJsonRpc && hasValidRequestId &&
432 Predicate.hasProperty(input, "method") && typeof input.method === "string"
433 const hasValidResponseId = hasId &&
434 (typeof input.id === "string" || typeof input.id === "number" || input.id === null)
435 const hasResult = Predicate.hasProperty(input, "result")
436 const hasError = Predicate.hasProperty(input, "error")
437 const isResponse = isJsonRpc && hasValidResponseId && hasResult !== hasError
438 const admission = selectHttpProtocol(headers, input)
439 if (admission._tag === "Rejected") {
440 return reject(admission.status, admission.error, id)
441 }
442 if (!isRequest && !isResponse) {
443 return reject(200, new PublicMcpSchema.InvalidRequest({ message: "Invalid Request" }), id)
444 }
445 const isInitialize = Predicate.hasProperty(input, "method") && input.method === "initialize"
446 const hasSession = headers[MCP_SESSION_ID_HEADER] !== undefined
447 if (isInitialize ? hasSession : !hasSession && admission.protocol?.runtime._tag !== "Stateless") {
448 return reject(400)
449 }
450 if (
451 isRequest && admission.protocol?.runtime._tag === "Stateless" &&
452 !((admission.protocol as unknown as McpProtocol.ProtocolAdapter).handlerRpcs?.requests.has(
453 input.method as string
454 ) ??
455 admission.protocol.clientRpcs.requests.has(input.method as string))
456 ) {
457 return reject(404, new PublicMcpSchema.MethodNotFound({ message: `Method not found: ${input.method}` }), id)
458 }
459 return {
460 _tag: "Accepted",
461 acknowledge: !isRequest || !hasId,
462 isSubscription: Predicate.hasProperty(input, "method") && input.method === "subscriptions/listen"
463 }
464 },
465 effectLogLevel: (clientId, headers, fallback) => stateful?.effectLogLevel(clientId, headers, fallback) ?? fallback,
466 disconnect: (clientId) => stateful?.disconnect(clientId),
467 deliveryClientIds: () => stateful?.initializedClientIds() ?? [],
468 canDeliver: (clientId, headers, notification, fallback) =>
469 stateful?.canDeliver(clientId, headers, notification, fallback) ?? true,
470 installHandlers: Effect.fnUntraced(function*(options) {
471 const contextMap = new Map<string, unknown>()
472 const installationContext: McpProtocol.HandlerInstallationContext = {
473 subscribeServerNotifications: options.subscribeServerNotifications,
474 ...(options.sendNotification === undefined ? {} : { sendNotification: options.sendNotification }),
475 ...(options.markSubscriptionCancelled === undefined
476 ? {}
477 : { markSubscriptionCancelled: options.markSubscriptionCancelled }),
478 ...(options.terminateSubscription === undefined
479 ? {}
480 : { terminateSubscription: options.terminateSubscription }),
481 supportedVersions: protocolVersions,
482 serverInfo: options.serverInfo,
483 registrationPresence: options.core.registrationPresence
484 }
485 const handlerTarget = registry.handlerTarget(contextMap, installationContext)
486 for (const protocol of registry.protocols) {
487 if (protocol.runtime._tag === "Stateless") {
488 yield* protocol.installHandlers(options.core, undefined, handlerTarget)
489 continue
490 }
491 if (stateful === undefined) {
492 return yield* Effect.die("MCP sessionful runtime invariant failed")
493 }
494 yield* handlerTarget.install(protocol, PingRpcs, PingRpcs.of({ ping: () => Effect.succeed({}) }))
495 const lifecycle: McpProtocol.LifecycleRuntime = {
496 initialize: Effect.fnUntraced(function*(protocolVersion, profile, clientId) {
497 const presence = yield* options.core.registrationPresence
498 return yield* Effect.withFiber((fiber) => {
499 const httpRequest = Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest)
500 const capabilities: McpCore.CanonicalServerCapabilities = {
501 completions: true,
502 logging: true,
503 ...(presence.tools ? { tools: { listChanged: true } } : {}),
504 ...(presence.resources
505 ? { resources: { listChanged: true, subscribe: httpRequest === undefined } }
506 : {}),
507 ...(presence.prompts ? { prompts: { listChanged: true } } : {}),
508 ...(options.serverInfo.extensions ? { extensions: options.serverInfo.extensions } : {})
509 }
510 const initializePayload = PublicMcpSchema.Initialize.payloadSchema.make({
511 protocolVersion,
512 capabilities: profile.clientCapabilities,
513 clientInfo: profile.clientInfo!,
514 _meta: profile.requestMetadata
515 })
516 const registration: McpStatefulRuntime.Registration = {
517 initializePayload,
518 negotiatedProfile: profile,
519 protocol: protocol as PublicMcpProtocol.ProtocolAdapter,
520 supportsResourceSubscriptions: httpRequest === undefined &&
521 capabilities.resources?.subscribe === true,
522 logLevel: options.defaultLogLevel
523 }
524 if (httpRequest !== undefined) {
525 const sessionId = crypto.randomUUID()
526 stateful.registerHttp(sessionId, registration)
527 appendPreResponseHandlerUnsafe(
528 httpRequest,
529 (_request, response) =>
530 Effect.succeed(HttpServerResponse.setHeaders(response, {
531 [MCP_SESSION_ID_HEADER]: sessionId,
532 [MCP_PROTOCOL_VERSION_HEADER]: protocol.protocolVersion
533 }))
534 )
535 } else {
536 stateful.registerConnection(clientId, registration)
537 }
538 return Effect.succeed({
539 capabilities,
540 instructions: options.serverInfo.instructions,
541 serverInfo: PublicMcpSchema.Implementation.make({
542 name: options.serverInfo.name,
543 version: options.serverInfo.version,
544 description: options.serverInfo.description,
545 websiteUrl: options.serverInfo.websiteUrl,
546 icons: options.serverInfo.icons
547 })
548 })
549 })
550 }),
551 setLogLevel: stateful.setLogLevel,
552 subscribe: stateful.subscribe,
553 unsubscribe: stateful.unsubscribe,
554 clientNotification: Effect.fnUntraced(function*(notification, clientId) {
555 if (notification._tag === "Initialized") {
556 stateful.markInitialized(clientId)
557 }
558 })
559 }
560 yield* protocol.installHandlers(options.core, lifecycle, handlerTarget)
561 }
562 return Context.makeUnsafe(contextMap)
563 })
564 })
565})
566
567/**
568 * @internal
569 */
570export const layer = (
571 protocols: NonEmptyReadonlyArray<PublicMcpProtocol.AnyProtocolAdapter>
572): Layer.Layer<ServerRuntime, Cause.IllegalArgumentError> => Layer.effect(ServerRuntime)(make(protocols))