/**
 * The `McpServer` module provides Effect services and layers for building
 * Model Context Protocol servers. It keeps track of registered tools,
 * resources, resource templates, prompts, completions, and server
 * notifications, then exposes them through the MCP request handlers.
 *
 * **Common tasks**
 *
 * - Start a server over stdio with {@link layerStdio}
 * - Register HTTP routes for an existing `HttpRouter` with {@link layerHttp}
 * - Expose Effect AI toolkits as MCP tools with {@link registerToolkit}
 * - Register resources, resource templates, and prompts with {@link resource}
 *   and {@link prompt}
 * - Ask the connected MCP client for structured input with {@link elicit}
 *
 * **Gotchas**
 *
 * - Registration helpers require an `McpServer` service, usually provided by
 *   one of this module's layers.
 * - HTTP clients must complete MCP initialization before other requests; the
 *   server tracks initialized sessions with the `Mcp-Session-Id` header.
 * - Resource template parameters are decoded with the schemas embedded in the
 *   template literal.
 *
 * @since 4.0.0
 */
import * as Arr from "../../Array.js";
import * as Cause from "../../Cause.js";
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as Exit from "../../Exit.js";
import * as Fiber from "../../Fiber.js";
import * as Layer from "../../Layer.js";
import * as Option from "../../Option.js";
import * as Queue from "../../Queue.js";
import * as RcMap from "../../RcMap.js";
import { CurrentLogLevel } from "../../References.js";
import * as Schema from "../../Schema.js";
import * as AST from "../../SchemaAST.js";
import * as Sink from "../../Sink.js";
import * as Stream from "../../Stream.js";
import * as FindMyWay from "../http/FindMyWay.js";
import * as Headers from "../http/Headers.js";
import { appendPreResponseHandlerUnsafe } from "../http/HttpEffect.js";
import * as HttpRouter from "../http/HttpRouter.js";
import * as HttpServerRequest from "../http/HttpServerRequest.js";
import * as HttpServerResponse from "../http/HttpServerResponse.js";
import * as Rpc from "../rpc/Rpc.js";
import * as RpcClient from "../rpc/RpcClient.js";
import * as RpcMessage from "../rpc/RpcMessage.js";
import * as RpcSerialization from "../rpc/RpcSerialization.js";
import * as RpcServer from "../rpc/RpcServer.js";
import { CallToolResult, ClientNotificationRpcs, ClientRpcs, CompleteResult, Elicit, ElicitationDeclined, EnabledWhen, GetPromptResult, InternalError, InvalidParams, isParam, ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult, ListToolsResult, McpServerClient, McpServerClientMiddleware, Prompt, Resource, ResourceTemplate, ServerNotificationRpcs, ServerRequestRpcs, TextContent, Tool as McpTool } from "./McpSchema.js";
import * as Tool from "./Tool.js";
/**
 * Service that stores and serves an MCP server's registered tools, resources,
 * prompts, completions, and outgoing notifications.
 *
 * **Details**
 *
 * Handlers use this service to register capabilities and resolve incoming MCP
 * requests.
 *
 * @category server
 * @since 4.0.0
 */
export class McpServer extends /*#__PURE__*/Context.Service()("effect/ai/McpServer") {
  /**
   * Builds an MCP server service from registered tools, prompts, resources, and completions.
   *
   * @since 4.0.0
   */
  static make = /*#__PURE__*/Effect.gen(function* () {
    const matcher = makeUriMatcher();
    const tools = Arr.empty();
    const toolMap = new Map();
    const resources = [];
    const resourceTemplates = [];
    const prompts = [];
    const promptMap = new Map();
    const completionsMap = new Map();
    const notificationsQueue = yield* Queue.make();
    const listChangedHandles = new Map();
    const notifications = yield* RpcClient.makeNoSerialization(ServerNotificationRpcs, {
      spanPrefix: "McpServer/Notifications",
      onFromClient: options => Effect.suspend(() => {
        const message = options.message;
        if (message._tag !== "Request") {
          return Effect.void;
        }
        if (message.tag.includes("list_changed")) {
          if (!listChangedHandles.has(message.tag)) {
            listChangedHandles.set(message.tag, setTimeout(() => {
              Queue.offerUnsafe(notificationsQueue, message);
              listChangedHandles.delete(message.tag);
            }, 0));
          }
        } else {
          Queue.offerUnsafe(notificationsQueue, message);
        }
        return notifications.write({
          clientId: 0,
          requestId: message.id,
          _tag: "Exit",
          exit: Exit.void
        });
      })
    });
    return McpServer.of({
      notifications: notifications.client,
      notificationsQueue,
      initializedClients: new Set(),
      get tools() {
        return tools;
      },
      addTool: options => Effect.suspend(() => {
        tools.push(options);
        toolMap.set(options.tool.name, options.handle);
        return notifications.client["notifications/tools/list_changed"]({});
      }),
      callTool: request => Effect.suspend(() => {
        const handle = toolMap.get(request.name);
        if (!handle) {
          return Effect.fail(new InvalidParams({
            message: `Tool '${request.name}' not found`
          }));
        }
        return handle(request.arguments);
      }),
      get resources() {
        return resources;
      },
      get resourceTemplates() {
        return resourceTemplates;
      },
      addResource: options => Effect.suspend(() => {
        resources.push(options);
        matcher.add(options.resource.uri, {
          _tag: "Resource",
          effect: options.handle
        });
        return notifications.client["notifications/resources/list_changed"]({});
      }),
      addResourceTemplate: ({
        annotations,
        completions,
        handle,
        routerPath,
        template
      }) => Effect.suspend(() => {
        resourceTemplates.push({
          template,
          annotations
        });
        matcher.add(routerPath, {
          _tag: "ResourceTemplate",
          handle
        });
        for (const [param, handle] of Object.entries(completions)) {
          completionsMap.set(`ref/resource/${template.uriTemplate}/${param}`, handle);
        }
        return notifications.client["notifications/resources/list_changed"]({});
      }),
      findResource: uri => Effect.suspend(() => {
        const match = matcher.find(uri);
        if (!match) {
          return Effect.succeed({
            contents: []
          });
        } else if (match.handler._tag === "Resource") {
          return match.handler.effect;
        }
        const params = [];
        for (const key of Object.keys(match.params)) {
          params[Number(key)] = match.params[key];
        }
        return match.handler.handle(uri, params);
      }),
      get prompts() {
        return prompts;
      },
      addPrompt: options => Effect.suspend(() => {
        prompts.push(options);
        promptMap.set(options.prompt.name, options.handle);
        for (const [param, handle] of Object.entries(options.completions)) {
          completionsMap.set(`ref/prompt/${options.prompt.name}/${param}`, handle);
        }
        return notifications.client["notifications/prompts/list_changed"]({});
      }),
      getPromptResult: Effect.fnUntraced(function* ({
        arguments: params,
        name
      }) {
        const handler = promptMap.get(name);
        if (!handler) {
          return yield* new InvalidParams({
            message: `Prompt '${name}' not found`
          });
        }
        return yield* handler(params ?? {});
      }),
      completion: Effect.fnUntraced(function* (complete) {
        const ref = complete.ref;
        const key = ref.type === "ref/resource" ? `ref/resource/${ref.uri}/${complete.argument.name}` : `ref/prompt/${ref.name}/${complete.argument.name}`;
        const handler = completionsMap.get(key);
        return handler ? yield* handler(complete.argument.value) : CompleteResult.empty;
      })
    });
  });
  /**
   * Layer that provides the MCP server and client services.
   *
   * @since 4.0.0
   */
  static layer = /*#__PURE__*/Layer.effect(McpServer)(McpServer.make);
}
const LATEST_PROTOCOL_VERSION = "2025-06-18";
const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-03-26", "2024-11-05", "2024-10-07"];
const mcpSessionIdHeader = "mcp-session-id";
const mcpProtocolVersionHeader = "mcp-protocol-version";
/**
 * Runs an MCP server over the current `RpcServer.Protocol`.
 *
 * **Details**
 *
 * The server performs initialization and session handling, serves registered
 * tools, resources, and prompts, and forwards queued server notifications to
 * initialized clients.
 *
 * @category constructors
 * @since 4.0.0
 */
export const run = /*#__PURE__*/Effect.fnUntraced(function* (options) {
  const protocol = yield* RpcServer.Protocol;
  const server = yield* McpServer;
  const isHttp = Option.isSome(yield* Effect.serviceOption(HttpRouter.HttpRouter));
  const clientSessions = new Map();
  const handlers = yield* Layer.build(layerHandlers(options, {
    clientSessions
  }));
  const clients = yield* RcMap.make({
    lookup: Effect.fnUntraced(function* (clientId) {
      let write;
      const client = yield* RpcClient.make(ServerRequestRpcs, {
        spanPrefix: "McpServer/Client"
      }).pipe(Effect.provideServiceEffect(RpcClient.Protocol, RpcClient.Protocol.make(Effect.fnUntraced(function* (writeResponse) {
        let cid = 0;
        write = message => writeResponse(cid, message);
        return {
          send(id, request, _transferables) {
            cid = id;
            return protocol.send(clientId, {
              ...request,
              headers: undefined,
              traceId: undefined,
              spanId: undefined,
              sampled: undefined
            });
          },
          supportsAck: true,
          supportsTransferables: false,
          supportsStructuredClone: false
        };
      }))));
      return {
        client,
        write
      };
    }),
    idleTimeToLive: 10000
  });
  const clientMiddleware = McpServerClientMiddleware.of((effect, {
    client,
    headers,
    rpc
  }) => {
    const initializePayload = getInitializedClient(clientSessions, client.id, headers);
    const isInitialize = rpc._tag === "initialize";
    if (!isInitialize && !initializePayload) {
      const fiber = Fiber.getCurrent();
      const httpRequest = Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest);
      if (httpRequest) {
        appendPreResponseHandlerUnsafe(httpRequest, () => Effect.succeed(HttpServerResponse.empty({
          status: 404
        })));
      }
      return Effect.die(new Error(`Mcp-Session-Id does not exist`));
    }
    return Effect.provideService(effect, McpServerClient, McpServerClient.of({
      clientId: client.id,
      initializePayload: initializePayload,
      getClient: RcMap.get(clients, client.id).pipe(Effect.map(({
        client
      }) => client))
    }));
  });
  const patchedProtocol = RpcServer.Protocol.of({
    ...protocol,
    run: f => protocol.run((clientId, request_) => {
      const request = request_;
      switch (request._tag) {
        case "Request":
          {
            if (isHttp) {
              const fiber = Fiber.getCurrent();
              const httpRequest = Context.getUnsafe(fiber.context, HttpServerRequest.HttpServerRequest);
              const client = getInitializedClient(clientSessions, clientId, httpRequest.headers);
              if (client) {
                appendPreResponseHandlerUnsafe(httpRequest, (_, res) => Effect.succeed(HttpServerResponse.setHeader(res, mcpProtocolVersionHeader, client.protocolVersion)));
              }
            }
            const rpc = ClientNotificationRpcs.requests.get(request.tag);
            if (rpc) {
              if (request.tag === "notifications/cancelled") {
                return f(clientId, {
                  _tag: "Interrupt",
                  requestId: String(request.payload.requestId)
                });
              }
              const handler = handlers.mapUnsafe.get(request.tag);
              return handler ? handler.handler(request.payload, {
                rpc,
                requestId: RpcMessage.RequestId(request.id),
                client: new Rpc.ServerClient(clientId),
                headers: Headers.fromInput(request.headers)
              }) : Effect.void;
            }
            return f(clientId, request);
          }
        case "Ping":
        case "Ack":
        case "Interrupt":
        case "Eof":
          return f(clientId, request);
        case "Pong":
        case "Exit":
        case "Chunk":
        case "ClientProtocolError":
        case "Defect":
          return RcMap.get(clients, clientId).pipe(Effect.flatMap(({
            write
          }) => write(request)), Effect.scoped);
      }
    })
  });
  const encodeNotification = Schema.encodeUnknownEffect(Schema.Union(Array.from(ServerNotificationRpcs.requests.values(), rpc => rpc.payloadSchema)));
  yield* Queue.take(server.notificationsQueue).pipe(Effect.flatMap(Effect.fnUntraced(function* (request) {
    const encoded = yield* encodeNotification(request.payload);
    const message = {
      _tag: "Request",
      tag: request.tag,
      payload: encoded
    };
    const clientIds = yield* patchedProtocol.clientIds;
    for (const clientId of server.initializedClients.keys()) {
      if (!clientIds.has(clientId)) {
        server.initializedClients.delete(clientId);
        continue;
      }
      yield* patchedProtocol.send(clientId, message);
    }
  })), Effect.catchCause(() => Effect.void), Effect.forever, Effect.forkScoped);
  return yield* RpcServer.make(ClientRpcs, {
    spanPrefix: "McpServer",
    disableFatalDefects: true
  }).pipe(Effect.provideService(RpcServer.Protocol, patchedProtocol), Effect.provideService(McpServerClientMiddleware, clientMiddleware), Effect.provide(handlers));
}, Effect.scoped);
/**
 * Creates a layer that starts an MCP server over an existing
 * `RpcServer.Protocol` and provides the `McpServer` and `McpServerClient`
 * services.
 *
 * **When to use**
 *
 * Use when you already have a custom or externally provided
 * `RpcServer.Protocol` and want to start an MCP server as part of a layer
 * graph.
 *
 * **Details**
 *
 * The returned layer forks `run(options)` in the layer scope and merges
 * `McpServer.layer`, so registration layers can use the `McpServer` service
 * while the server is running.
 *
 * **Gotchas**
 *
 * Unlike `layerStdio` and `layerHttp`, this layer does not install a concrete
 * transport. The surrounding layer graph must provide `RpcServer.Protocol`.
 *
 * @see {@link run} for the effect form used by this layer
 * @see {@link layerStdio} for a stdio-backed layer that installs the MCP protocol and NDJSON-RPC serialization
 * @see {@link layerHttp} for an HTTP-backed layer that registers with `HttpRouter` and installs JSON-RPC serialization
 *
 * @category layers
 * @since 4.0.0
 */
export const layer = options => Layer.effectDiscard(Effect.forkScoped(run(options))).pipe(Layer.provideMerge(McpServer.layer));
/**
 * Runs the McpServer, using stdio for input and output.
 *
 * **Example** (Running an MCP server over stdio)
 *
 * ```ts
 * import { Effect, Layer, Logger, Schema } from "effect"
 * import { NodeRuntime, NodeStdio } from "@effect/platform-node"
 * import { McpSchema, McpServer } from "effect/unstable/ai"
 *
 * const idParam = McpSchema.param("id", Schema.Number)
 *
 * // Define a resource template for a README file
 * const ReadmeTemplate = McpServer.resource`file://readme/${idParam}`({
 *   name: "README Template",
 *   // You can add auto-completion for the ID parameter
 *   completion: {
 *     id: (_) => Effect.succeed([1, 2, 3, 4, 5])
 *   },
 *   content: Effect.fn(function*(_uri, id) {
 *     return `# MCP Server Demo - ID: ${id}`
 *   })
 * })
 *
 * // Define a test prompt with parameters
 * const TestPrompt = McpServer.prompt({
 *   name: "Test Prompt",
 *   description: "A test prompt to demonstrate MCP server capabilities",
 *   parameters: {
 *     flightNumber: Schema.String
 *   },
 *   completion: {
 *     flightNumber: () => Effect.succeed(["FL123", "FL456", "FL789"])
 *   },
 *   content: ({ flightNumber }) =>
 *     Effect.succeed(`Get the booking details for flight number: ${flightNumber}`)
 * })
 *
 * // Merge all the resources and prompts into a single server layer
 * const ServerLayer = Layer.mergeAll(
 *   ReadmeTemplate,
 *   TestPrompt
 * ).pipe(
 *   // Provide the MCP server implementation
 *   Layer.provide(McpServer.layerStdio({
 *     name: "Demo Server",
 *     version: "1.0.0",
 *   })),
 *   Layer.provide(NodeStdio.layer),
 *   Layer.provide(Layer.succeed(Logger.LogToStderr)(true))
 * )
 *
 * Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)
 * ```
 *
 * @category layers
 * @since 4.0.0
 */
export const layerStdio = options => layer(options).pipe(Layer.provide(RpcServer.layerProtocolStdio), Layer.provide(RpcSerialization.layerNdJsonRpc()));
/**
 * Registers an HTTP POST JSON-RPC route at `options.path` on the current
 * `HttpRouter`.
 *
 * **When to use**
 *
 * Use to expose an MCP server through an existing `HttpRouter`.
 *
 * **Details**
 *
 * This layer composes `layer(options)`, `RpcServer.layerProtocolHttp(options)`,
 * and `RpcSerialization.layerJsonRpc()`.
 *
 * @see {@link layerStdio} for exposing the server over stdio
 * @see {@link layer} for the base MCP server layer without a transport protocol
 *
 * @category layers
 * @since 4.0.0
 */
export const layerHttp = options => layer(options).pipe(Layer.provide(RpcServer.layerProtocolHttp(options)), Layer.provide(RpcSerialization.layerJsonRpc()));
/**
 * Registers a `Toolkit` with the `McpServer`.
 *
 * @category tools
 * @since 4.0.0
 */
export const registerToolkit = /*#__PURE__*/Effect.fnUntraced(function* (toolkit) {
  const registry = yield* McpServer;
  const built = yield* toolkit;
  const services = yield* Effect.context();
  for (const tool of Object.values(built.tools)) {
    const annotations = tool.annotations;
    const toolMeta = Context.getOrUndefined(annotations, Tool.Meta);
    const mcpTool = new McpTool({
      name: tool.name,
      description: Tool.getDescription(tool),
      inputSchema: Tool.getJsonSchema(tool),
      annotations: {
        ...Context.getOption(tool.annotations, Tool.Title).pipe(Option.map(title => ({
          title
        })), Option.getOrUndefined),
        readOnlyHint: Context.get(tool.annotations, Tool.Readonly),
        destructiveHint: Context.get(tool.annotations, Tool.Destructive),
        idempotentHint: Context.get(tool.annotations, Tool.Idempotent),
        openWorldHint: Context.get(tool.annotations, Tool.OpenWorld)
      },
      _meta: toolMeta
    });
    yield* registry.addTool({
      tool: mcpTool,
      annotations,
      handle(payload) {
        return built.handle(tool.name, payload).pipe(Stream.unwrap, Stream.run(Sink.last()), Effect.flatMap(Effect.fromOption), Effect.provideContext(services), Effect.matchCause({
          onFailure: cause => new CallToolResult({
            isError: true,
            content: [{
              type: "text",
              text: Cause.pretty(cause)
            }]
          }),
          onSuccess: result => new CallToolResult({
            isError: false,
            structuredContent: typeof result.encodedResult === "object" ? result.encodedResult : undefined,
            content: [{
              type: "text",
              text: JSON.stringify(result.encodedResult)
            }]
          })
        }), Effect.tapCause(Effect.log));
      }
    });
  }
});
/**
 * Registers an `AiToolkit` with the `McpServer`.
 *
 * @category tools
 * @since 4.0.0
 */
export const toolkit = toolkit => Layer.effectDiscard(registerToolkit(toolkit)).pipe(Layer.provide(McpServer.layer));
/**
 * Registers an MCP resource or resource template from an Effect program.
 *
 * **When to use**
 *
 * Use to add a resource to an existing MCP server from an Effect program.
 *
 * @see {@link resource} for the layer-based resource registration wrapper
 *
 * @category resources
 * @since 4.0.0
 */
export const registerResource = function () {
  if (arguments.length === 1) {
    const options = arguments[0];
    return Effect.gen(function* () {
      const services = yield* Effect.context();
      const registry = yield* McpServer;
      yield* registry.addResource({
        resource: new Resource({
          ...options,
          annotations: options
        }),
        handle: options.content.pipe(Effect.provideContext(services), Effect.map(content => resolveResourceContent(options.uri, content)), Effect.catchCause(cause => {
          const prettyError = Cause.prettyErrors(cause)[0];
          return Effect.fail(new InternalError({
            message: prettyError.message
          }));
        })),
        annotations: options.annotations ?? Context.empty()
      });
    });
  }
  const {
    params,
    routerPath,
    schema,
    uriPath
  } = compileUriTemplate(...arguments);
  return Effect.fnUntraced(function* (options) {
    const services = yield* Effect.context();
    const registry = yield* McpServer;
    const decode = Schema.decodeUnknownEffect(schema);
    const template = new ResourceTemplate({
      ...options,
      uriTemplate: uriPath,
      annotations: options
    });
    const completions = {};
    for (const [param, handle] of Object.entries(options.completion ?? {})) {
      const encodeArray = Schema.encodeUnknownEffect(Schema.Array(params[param]));
      const handler = input => handle(input).pipe(Effect.flatMap(encodeArray), Effect.map(values => ({
        completion: {
          values: values,
          total: values.length,
          hasMore: false
        }
      })), Effect.catchCause(cause => {
        const prettyError = Cause.prettyErrors(cause)[0];
        return Effect.fail(new InternalError({
          message: prettyError.message
        }));
      }), Effect.provideContext(services));
      completions[param] = handler;
    }
    yield* registry.addResourceTemplate({
      template,
      routerPath,
      completions,
      annotations: options.annotations ?? Context.empty(),
      handle: (uri, params) => decode(params).pipe(Effect.mapError(error => new InvalidParams({
        message: error.message
      })), Effect.flatMap(params => options.content(uri, ...params).pipe(Effect.map(content => resolveResourceContent(uri, content)), Effect.catchCause(cause => {
        const prettyError = Cause.prettyErrors(cause)[0];
        return Effect.fail(new InternalError({
          message: prettyError.message
        }));
      }))), Effect.provideContext(services))
    });
  });
};
/**
 * Creates a layer that registers an MCP resource or resource template.
 *
 * **When to use**
 *
 * Use to compose resource registration into an MCP server layer.
 *
 * @see {@link registerResource} for the Effect-level resource registration API
 *
 * @category resources
 * @since 4.0.0
 */
export const resource = function () {
  if (arguments.length === 1) {
    return Layer.effectDiscard(registerResource(arguments[0])).pipe(Layer.provide(McpServer.layer));
  }
  const register = registerResource(...arguments);
  return options => Layer.effectDiscard(register(options)).pipe(Layer.provide(McpServer.layer));
};
/**
 * Registers an MCP prompt from an Effect program.
 *
 * **When to use**
 *
 * Use to register an MCP prompt from an Effect program.
 *
 * **Details**
 *
 * Parameters are decoded with the supplied schema, completion handlers encode
 * per-parameter suggestions, and string prompt content is converted into a user
 * text message.
 *
 * @see {@link prompt} for the layer-based prompt registration wrapper
 *
 * @category prompts
 * @since 4.0.0
 */
export const registerPrompt = options => {
  const args = Arr.empty();
  const props = options.parameters ?? {};
  for (const [name, prop] of Object.entries(props)) {
    args.push({
      name,
      description: AST.resolveDescription(prop.ast),
      required: !AST.isOptional(prop.ast)
    });
  }
  const prompt = new Prompt({
    name: options.name,
    description: options.description,
    arguments: args
  });
  const decode = options.parameters ? Schema.decodeEffect(Schema.Struct(props)) : () => Effect.succeed({});
  const completion = options.completion ?? {};
  return Effect.gen(function* () {
    const registry = yield* McpServer;
    const services = yield* Effect.context();
    const completions = {};
    for (const [param, handle] of Object.entries(completion)) {
      const encodeArray = Schema.encodeEffect(Schema.Array(props[param]));
      const handler = input => handle(input).pipe(Effect.flatMap(encodeArray), Effect.map(values => ({
        completion: {
          values: values,
          total: values.length,
          hasMore: false
        }
      })), Effect.catchCause(cause => {
        const prettyError = Cause.prettyErrors(cause)[0];
        return Effect.fail(new InternalError({
          message: prettyError.message
        }));
      }), Effect.provide(services));
      completions[param] = handler;
    }
    yield* registry.addPrompt({
      prompt,
      completions,
      annotations: options.annotations ?? Context.empty(),
      handle: params => decode(params).pipe(Effect.mapError(error => new InvalidParams({
        message: error.message
      })), Effect.flatMap(params => options.content(params)), Effect.map(messages => {
        messages = typeof messages === "string" ? [{
          role: "user",
          content: TextContent.make({
            text: messages
          })
        }] : messages;
        return new GetPromptResult({
          messages,
          description: prompt.description
        });
      }), Effect.catchCause(cause => {
        const prettyError = Cause.prettyErrors(cause)[0];
        return Effect.fail(new InternalError({
          message: prettyError.message
        }));
      }), Effect.provideContext(services))
    });
  });
};
/**
 * Creates a layer that registers an MCP prompt.
 *
 * **When to use**
 *
 * Use to compose prompt registration into an MCP server layer.
 *
 * **Details**
 *
 * Parameters are decoded with the supplied schema, completion handlers encode
 * per-parameter suggestions, and string prompt content is converted into a user
 * text message.
 *
 * @see {@link registerPrompt} for the Effect-level prompt registration API
 *
 * @category prompts
 * @since 4.0.0
 */
export const prompt = options => Layer.effectDiscard(registerPrompt(options)).pipe(Layer.provide(McpServer.layer));
/**
 * Collects structured input from the current MCP client and decodes the
 * accepted response with `schema`.
 *
 * **Details**
 *
 * Accepted content is decoded with the supplied schema, declined requests fail
 * with `ElicitationDeclined`, and canceled requests interrupt the effect.
 *
 * @category elicitation
 * @since 4.0.0
 */
export const elicit = /*#__PURE__*/Effect.fnUntraced(function* (options) {
  const {
    getClient
  } = yield* McpServerClient;
  const client = yield* getClient;
  const schema = options.schema;
  const request = Elicit.payloadSchema.make({
    message: options.message,
    requestedSchema: Tool.getJsonSchemaFromSchema(schema)
  });
  const res = yield* client["elicitation/create"](request).pipe(Effect.catchCause(cause => Effect.fail(new ElicitationDeclined({
    cause: Cause.squash(cause),
    request
  }))));
  switch (res.action) {
    case "accept":
      return yield* Effect.orDie(Schema.decodeUnknownEffect(schema)(res.content));
    case "cancel":
      return yield* Effect.interrupt;
    case "decline":
      return yield* new ElicitationDeclined({
        request
      });
  }
}, Effect.scoped);
/**
 * Accesses the current client's capabilities.
 *
 * @category capabilities
 * @since 4.0.0
 */
export const clientCapabilities = /*#__PURE__*/McpServerClient.useSync(_ => _.initializePayload.capabilities);
// -----------------------------------------------------------------------------
// Internal
// -----------------------------------------------------------------------------
const makeUriMatcher = () => {
  const router = FindMyWay.make({
    ignoreTrailingSlash: true,
    ignoreDuplicateSlashes: true,
    caseSensitive: true
  });
  const add = (uri, value) => {
    router.on("GET", uri, value);
  };
  const find = uri => router.find("GET", uri);
  return {
    add,
    find
  };
};
const compileUriTemplate = (segments, ...schemas) => {
  let routerPath = segments[0].replace(":", "::");
  let uriPath = segments[0];
  const params = {};
  let pathSchema = Schema.Tuple([]);
  if (schemas.length > 0) {
    const arr = [];
    for (let i = 0; i < schemas.length; i++) {
      const toCodecStringTree = Schema.toCodecStringTree(schemas[i]);
      const segment = segments[i + 1];
      const key = String(i);
      arr.push(toCodecStringTree);
      routerPath += `:${key}${segment.replace(":", "::")}`;
      const schema = schemas[i];
      const paramName = isParam(schema) ? schema.name : `param${key}`;
      params[paramName] = toCodecStringTree;
      uriPath += `{${paramName}}${segment}`;
    }
    pathSchema = Schema.Tuple(arr);
  }
  return {
    routerPath,
    uriPath,
    schema: pathSchema,
    params
  };
};
const layerHandlers = (serverInfo, options) => ClientRpcs.toLayer(Effect.gen(function* () {
  const server = yield* McpServer;
  let currentLogLevel = yield* CurrentLogLevel;
  return ClientRpcs.of({
    // Requests
    ping: () => Effect.succeed({}),
    initialize(params, {
      client
    }) {
      const requestedVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(params.protocolVersion) ? params.protocolVersion : LATEST_PROTOCOL_VERSION;
      if (requestedVersion !== params.protocolVersion) {
        params = {
          ...params,
          protocolVersion: requestedVersion
        };
      }
      const capabilities = {
        completions: {}
      };
      if (server.tools.length > 0) {
        capabilities.tools = {
          listChanged: true
        };
      }
      if (server.resources.length > 0 || server.resourceTemplates.length > 0) {
        capabilities.resources = {
          listChanged: true,
          subscribe: false
        };
      }
      if (server.prompts.length > 0) {
        capabilities.prompts = {
          listChanged: true
        };
      }
      if (serverInfo.extensions) {
        capabilities.extensions = serverInfo.extensions;
      }
      return Effect.withFiber(fiber => {
        const httpRequest = Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest);
        if (httpRequest) {
          const sessionId = crypto.randomUUID();
          options.clientSessions.set(sessionId, params);
          appendPreResponseHandlerUnsafe(httpRequest, (_req, res) => Effect.succeed(HttpServerResponse.setHeaders(res, {
            [mcpSessionIdHeader]: sessionId,
            [mcpProtocolVersionHeader]: requestedVersion
          })));
        } else {
          options.clientSessions.set(String(client.id), params);
        }
        return Effect.succeed({
          capabilities,
          serverInfo,
          protocolVersion: requestedVersion
        });
      });
    },
    "completion/complete": r => server.completion(r).pipe(Effect.provideService(CurrentLogLevel, currentLogLevel)),
    "logging/setLevel": ({
      level
    }) => Effect.sync(() => {
      switch (level) {
        case "notice":
        case "info":
          currentLogLevel = "Info";
          break;
        case "error":
          currentLogLevel = "Error";
          break;
        case "debug":
          currentLogLevel = "Debug";
          break;
        case "warning":
          currentLogLevel = "Warn";
          break;
        case "critical":
        case "alert":
        case "emergency":
          currentLogLevel = "Fatal";
          break;
      }
    }),
    "prompts/get": r => server.getPromptResult(r).pipe(Effect.provideService(CurrentLogLevel, currentLogLevel)),
    "prompts/list": (_, {
      client,
      headers
    }) => Effect.sync(() => {
      const initialized = getInitializedClient(options.clientSessions, client.id, headers);
      return new ListPromptsResult({
        prompts: filterByClient(initialized, server.prompts, "prompt")
      });
    }),
    "resources/list": (_, {
      client,
      headers
    }) => Effect.sync(() => {
      const initialized = getInitializedClient(options.clientSessions, client.id, headers);
      return new ListResourcesResult({
        resources: filterByClient(initialized, server.resources, "resource")
      });
    }),
    "resources/read": ({
      uri
    }) => server.findResource(uri).pipe(Effect.provideService(CurrentLogLevel, currentLogLevel)),
    "resources/subscribe": () => InternalError.notImplemented,
    "resources/unsubscribe": () => InternalError.notImplemented,
    "resources/templates/list": (_, {
      client,
      headers
    }) => Effect.sync(() => {
      const initialized = getInitializedClient(options.clientSessions, client.id, headers);
      return new ListResourceTemplatesResult({
        resourceTemplates: filterByClient(initialized, server.resourceTemplates, "template")
      });
    }),
    "tools/call": r => server.callTool(r).pipe(Effect.provideService(CurrentLogLevel, currentLogLevel)),
    "tools/list": (_, {
      client,
      headers
    }) => Effect.sync(() => {
      const initialized = getInitializedClient(options.clientSessions, client.id, headers);
      return new ListToolsResult({
        tools: filterByClient(initialized, server.tools, "tool")
      });
    }),
    // Notifications
    "notifications/cancelled": _ => Effect.void,
    "notifications/initialized": _ => Effect.void,
    "notifications/progress": _ => Effect.void,
    "notifications/roots/list_changed": _ => Effect.void
  });
}));
const resolveResourceContent = (uri, content) => {
  if (typeof content === "string") {
    return {
      contents: [{
        uri,
        text: content
      }]
    };
  } else if (content instanceof Uint8Array) {
    return {
      contents: [{
        uri,
        blob: content
      }]
    };
  }
  return content;
};
const filterByClient = (client, items, prop) => {
  if (!client) {
    return items.map(item => item[prop]);
  }
  const out = Arr.empty();
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    const enabledWhen = Context.getOrUndefined(item.annotations, EnabledWhen);
    if (!enabledWhen || enabledWhen(client)) {
      out.push(item[prop]);
    }
  }
  return out;
};
const getInitializedClient = (sessions, clientId, headers) => {
  const sessionId = headers[mcpSessionIdHeader];
  if (sessionId === undefined) {
    return sessions.get(String(clientId));
  }
  return sessions.get(sessionId);
};
//# sourceMappingURL=McpServer.js.map