EdgeAIG's picture
download
raw
34.6 kB
/**
* Builds Model Context Protocol (MCP) servers with Effect.
*
* The `McpServer` service stores the tools, resources, resource templates,
* prompts, completions, initialized clients, and outgoing notifications exposed
* by a server. This module also includes the server runner, custom protocol,
* stdio, and HTTP layers, registration helpers, and APIs that let handlers ask
* the connected client for structured input or read its advertised
* capabilities.
*
* @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 SchemaAST 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 when you are already inside an Effect program with an `McpServer`
* service and need to add a concrete resource or URI-template resource
* directly.
*
* @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 when you are already inside an Effect program with an `McpServer`
* service and need to add a prompt handler directly.
*
* **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: SchemaAST.resolveDescription(prop.ast),
required: !SchemaAST.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

Xet Storage Details

Size:
34.6 kB
·
Xet hash:
af39d8d1849fc51701c5f7c5d06f2bce92e70462460c0a83c8c3757271f63d12

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.