UNPKG

agents

Version:

A home for your AI agents

1,642 lines 63.6 kB
import "../types.js";
import { c as RPC_DO_PREFIX, i as normalizeServerId, n as MCP_SERVER_ID_MAX_LENGTH, o as RPCClientTransport, s as RPCServerTransport } from "../client-zqKcsyFa.js";
import { Agent, getAgentByName, getCurrentAgent } from "../index.js";
import { n as getMcpAuthContext, r as runWithAuthContext, t as createStatelessMcpHandler } from "../handler-stateless-CIkKPETH.js";
import { SSEClientTransport, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
import { ElicitRequestSchema, InitializeRequestSchema, JSONRPCMessageSchema, SUPPORTED_PROTOCOL_VERSIONS, isInitializeRequest, isJSONRPCErrorResponse, isJSONRPCNotification, isJSONRPCRequest, isJSONRPCResultResponse } from "@modelcontextprotocol/sdk/types.js";
import { McpServer, Server } from "@modelcontextprotocol/server";
import { McpServer as McpServer$1 } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Server as Server$1 } from "@modelcontextprotocol/sdk/server/index.js";
import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport$1 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
//#region src/mcp/sse-keepalive.ts
/**
* SSE keepalive for the retained McpAgent WebSocket-to-SSE bridge.
*
* SDK-backed HTTP transports own their keepalive timers. This helper remains
* only for the custom bridge, where long-running POST tool calls can otherwise
* sit silent until Cloudflare's edge closes the idle stream.
*
* See cloudflare/agents#1583.
*/
/** Interval between bridge SSE keepalive comment frames, in ms. */
const KEEPALIVE_INTERVAL_MS = 25e3;
/** SSE comment frame the parser drops before any event dispatch. */
const KEEPALIVE_FRAME = ": keepalive\n\n";
/**
* Start an SSE keepalive on `writer`. Returns a `clearInterval` handle
* that the stream cleanup must invoke when the stream closes.
*/
function startKeepalive(writer, encoder) {
	const handle = setInterval(() => {
		writer.write(encoder.encode(KEEPALIVE_FRAME)).catch(() => clearInterval(handle));
	}, KEEPALIVE_INTERVAL_MS);
	return handle;
}
//#endregion
//#region src/mcp/utils.ts
/**
* Since we use WebSockets to bridge the client to the
* MCP transport in the Agent, we use this header to signal
* the method of the original request the user made, while
* leaving the WS Upgrade request as GET.
*/
const MCP_HTTP_METHOD_HEADER = "cf-mcp-method";
/**
* Since we use WebSockets to bridge the client to the
* MCP transport in the Agent, we use this header to include
* the original request body.
*/
const MCP_MESSAGE_HEADER = "cf-mcp-message";
const MAXIMUM_MESSAGE_SIZE_BYTES = 4 * 1024 * 1024;
function unsupportedProtocolVersionResponse(request) {
	const protocolVersion = request.headers.get("mcp-protocol-version");
	if (protocolVersion === null || SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) return;
	return Response.json({
		error: {
			code: -32e3,
			message: `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`
		},
		id: null,
		jsonrpc: "2.0"
	}, { status: 400 });
}
const createStreamingHttpHandler = (basePath, namespace, options = {}) => {
	let pathname = basePath;
	if (basePath === "/") pathname = "/*";
	const basePattern = new URLPattern({ pathname });
	return async (request, ctx) => {
		const url = new URL(request.url);
		if (basePattern.test(url)) {
			if (request.method === "POST") {
				const acceptHeader = request.headers.get("accept");
				if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
					const body = JSON.stringify({
						error: {
							code: -32e3,
							message: "Not Acceptable: Client must accept both application/json and text/event-stream"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 406 });
				}
				const ct = request.headers.get("content-type");
				if (!ct || !ct.includes("application/json")) {
					const body = JSON.stringify({
						error: {
							code: -32e3,
							message: "Unsupported Media Type: Content-Type must be application/json"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 415 });
				}
				if (Number.parseInt(request.headers.get("content-length") ?? "0", 10) > MAXIMUM_MESSAGE_SIZE_BYTES) {
					const body = JSON.stringify({
						error: {
							code: -32e3,
							message: `Request body too large. Maximum size is ${MAXIMUM_MESSAGE_SIZE_BYTES} bytes`
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 413 });
				}
				let sessionId = request.headers.get("mcp-session-id");
				let rawMessage;
				try {
					rawMessage = await request.json();
				} catch (_error) {
					const body = JSON.stringify({
						error: {
							code: -32700,
							message: "Parse error: Invalid JSON"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 400 });
				}
				let arrayMessage;
				if (Array.isArray(rawMessage)) arrayMessage = rawMessage;
				else arrayMessage = [rawMessage];
				let messages = [];
				for (const msg of arrayMessage) if (!JSONRPCMessageSchema.safeParse(msg).success) {
					const body = JSON.stringify({
						error: {
							code: -32700,
							message: "Parse error: Invalid JSON-RPC message"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 400 });
				}
				messages = arrayMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
				const maybeInitializeRequest = messages.find((msg) => InitializeRequestSchema.safeParse(msg).success);
				if (!!maybeInitializeRequest && sessionId) {
					const body = JSON.stringify({
						error: {
							code: -32600,
							message: "Invalid Request: Initialization requests must not include a sessionId"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 400 });
				}
				if (!!maybeInitializeRequest && messages.length > 1) {
					const body = JSON.stringify({
						error: {
							code: -32600,
							message: "Invalid Request: Only one initialization request is allowed"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 400 });
				}
				if (!maybeInitializeRequest) {
					const unsupportedVersion = unsupportedProtocolVersionResponse(request);
					if (unsupportedVersion) return unsupportedVersion;
				}
				if (!maybeInitializeRequest && !sessionId) {
					const body = JSON.stringify({
						error: {
							code: -32e3,
							message: "Bad Request: Mcp-Session-Id header is required"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 400 });
				}
				sessionId = sessionId ?? namespace.newUniqueId().toString();
				const agent = await getAgentByName(namespace, `streamable-http:${sessionId}`, {
					props: ctx.props,
					jurisdiction: options.jurisdiction
				});
				const isInitialized = await agent.getInitializeRequest();
				if (maybeInitializeRequest) await agent.setInitializeRequest(maybeInitializeRequest);
				else if (!isInitialized) {
					const body = JSON.stringify({
						error: {
							code: -32001,
							message: "Session not found"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 404 });
				}
				const { readable, writable } = new TransformStream();
				const writer = writable.getWriter();
				const encoder = new TextEncoder();
				const existingHeaders = {};
				request.headers.forEach((value, key) => {
					existingHeaders[key] = value;
				});
				const req = new Request(request.url, { headers: {
					...existingHeaders,
					[MCP_HTTP_METHOD_HEADER]: "POST",
					[MCP_MESSAGE_HEADER]: Buffer.from(JSON.stringify(messages)).toString("base64"),
					Upgrade: "websocket"
				} });
				const ws = (await agent.fetch(req)).webSocket;
				if (!ws) {
					console.error("Failed to establish WebSocket connection");
					await writer.close();
					const body = JSON.stringify({
						error: {
							code: -32001,
							message: "Failed to establish WebSocket connection"
						},
						id: null,
						jsonrpc: "2.0"
					});
					return new Response(body, { status: 500 });
				}
				ws.accept();
				if (messages.every((msg) => isJSONRPCNotification(msg) || isJSONRPCResultResponse(msg))) {
					ws.close();
					return new Response(null, {
						headers: corsHeaders(request, options.corsOptions),
						status: 202
					});
				}
				const keepAlive = startKeepalive(writer, encoder);
				ws.addEventListener("message", (event) => {
					async function onMessage(event) {
						try {
							const data = typeof event.data === "string" ? event.data : new TextDecoder().decode(event.data);
							const message = JSON.parse(data);
							if (message.type !== "cf_mcp_agent_event") return;
							await writer.write(encoder.encode(message.event));
							if (message.close) {
								clearInterval(keepAlive);
								ws?.close();
								await writer.close().catch(() => {});
							}
						} catch (error) {
							console.error("Error forwarding message to SSE:", error);
						}
					}
					onMessage(event).catch(console.error);
				});
				ws.addEventListener("error", (error) => {
					async function onError(_error) {
						clearInterval(keepAlive);
						await writer.close().catch(() => {});
					}
					onError(error).catch(console.error);
				});
				ws.addEventListener("close", () => {
					async function onClose() {
						clearInterval(keepAlive);
						await writer.close().catch(() => {});
					}
					onClose().catch(console.error);
				});
				return new Response(readable, {
					headers: {
						"Cache-Control": "no-cache",
						Connection: "keep-alive",
						"Content-Type": "text/event-stream",
						"mcp-session-id": sessionId,
						...corsHeaders(request, options.corsOptions)
					},
					status: 200
				});
			} else if (request.method === "GET") {
				if (!request.headers.get("accept")?.includes("text/event-stream")) {
					const body = JSON.stringify({
						jsonrpc: "2.0",
						error: {
							code: -32e3,
							message: "Not Acceptable: Client must accept text/event-stream"
						},
						id: null
					});
					return new Response(body, { status: 406 });
				}
				const sessionId = request.headers.get("mcp-session-id");
				if (!sessionId) return new Response(JSON.stringify({
					error: {
						code: -32e3,
						message: "Bad Request: Mcp-Session-Id header is required"
					},
					id: null,
					jsonrpc: "2.0"
				}), { status: 400 });
				const unsupportedVersion = unsupportedProtocolVersionResponse(request);
				if (unsupportedVersion) return unsupportedVersion;
				const { readable, writable } = new TransformStream();
				const writer = writable.getWriter();
				const encoder = new TextEncoder();
				const agent = await getAgentByName(namespace, `streamable-http:${sessionId}`, {
					props: ctx.props,
					jurisdiction: options.jurisdiction
				});
				if (!await agent.getInitializeRequest()) return new Response(JSON.stringify({
					jsonrpc: "2.0",
					error: {
						code: -32001,
						message: "Session not found"
					},
					id: null
				}), { status: 404 });
				const existingHeaders = {};
				request.headers.forEach((v, k) => {
					existingHeaders[k] = v;
				});
				const ws = (await agent.fetch(new Request(request.url, { headers: {
					...existingHeaders,
					[MCP_HTTP_METHOD_HEADER]: "GET",
					Upgrade: "websocket"
				} }))).webSocket;
				if (!ws) {
					await writer.close();
					return new Response("Failed to establish WS to DO", { status: 500 });
				}
				ws.accept();
				ws.addEventListener("message", (event) => {
					try {
						async function onMessage(ev) {
							const data = typeof ev.data === "string" ? ev.data : new TextDecoder().decode(ev.data);
							const message = JSON.parse(data);
							if (message.type !== "cf_mcp_agent_event") return;
							await writer.write(encoder.encode(message.event));
						}
						onMessage(event).catch(console.error);
					} catch (e) {
						console.error("Error forwarding message to SSE:", e);
					}
				});
				ws.addEventListener("error", () => {
					writer.close().catch(() => {});
				});
				ws.addEventListener("close", () => {
					writer.close().catch(() => {});
				});
				return new Response(readable, {
					headers: {
						"Cache-Control": "no-cache",
						Connection: "keep-alive",
						"Content-Type": "text/event-stream",
						"mcp-session-id": sessionId,
						...corsHeaders(request, options.corsOptions)
					},
					status: 200
				});
			} else if (request.method === "DELETE") {
				const unsupportedVersion = unsupportedProtocolVersionResponse(request);
				if (unsupportedVersion) return unsupportedVersion;
				const sessionId = request.headers.get("mcp-session-id");
				if (!sessionId) return new Response(JSON.stringify({
					jsonrpc: "2.0",
					error: {
						code: -32e3,
						message: "Bad Request: Mcp-Session-Id header is required"
					},
					id: null
				}), {
					status: 400,
					headers: corsHeaders(request, options.corsOptions)
				});
				const agent = await getAgentByName(namespace, `streamable-http:${sessionId}`, { jurisdiction: options.jurisdiction });
				if (!await agent.getInitializeRequest()) return new Response(JSON.stringify({
					jsonrpc: "2.0",
					error: {
						code: -32001,
						message: "Session not found"
					},
					id: null
				}), {
					status: 404,
					headers: corsHeaders(request, options.corsOptions)
				});
				await agent._cf_scheduleDestroy();
				return new Response(null, {
					status: 204,
					headers: corsHeaders(request, options.corsOptions)
				});
			}
		}
		const body = JSON.stringify({
			error: {
				code: -32e3,
				message: "Not found"
			},
			id: null,
			jsonrpc: "2.0"
		});
		return new Response(body, { status: 404 });
	};
};
const createLegacySseHandler = (basePath, namespace, options = {}) => {
	let pathname = basePath;
	if (basePath === "/") pathname = "/*";
	const basePattern = new URLPattern({ pathname });
	const messagePattern = new URLPattern({ pathname: `${basePath}/message` });
	return async (request, ctx) => {
		const url = new URL(request.url);
		if (request.method === "GET" && basePattern.test(url)) {
			const sessionId = url.searchParams.get("sessionId") || namespace.newUniqueId().toString();
			const { readable, writable } = new TransformStream();
			const writer = writable.getWriter();
			const encoder = new TextEncoder();
			const endpointUrl = new URL(request.url);
			endpointUrl.pathname = encodeURI(`${basePath}/message`);
			endpointUrl.searchParams.set("sessionId", sessionId);
			const endpointMessage = `event: endpoint\ndata: ${endpointUrl.pathname + endpointUrl.search + endpointUrl.hash}\n\n`;
			writer.write(encoder.encode(endpointMessage));
			const agent = await getAgentByName(namespace, `sse:${sessionId}`, {
				props: ctx.props,
				jurisdiction: options.jurisdiction
			});
			const existingHeaders = {};
			request.headers.forEach((value, key) => {
				existingHeaders[key] = value;
			});
			const ws = (await agent.fetch(new Request(request.url, { headers: {
				...existingHeaders,
				[MCP_HTTP_METHOD_HEADER]: "SSE",
				Upgrade: "websocket"
			} }))).webSocket;
			if (!ws) {
				console.error("Failed to establish WebSocket connection");
				await writer.close();
				return new Response("Failed to establish WebSocket connection", { status: 500 });
			}
			ws.accept();
			ws.addEventListener("message", (event) => {
				async function onMessage(event) {
					try {
						const message = JSON.parse(event.data);
						const result = JSONRPCMessageSchema.safeParse(message);
						if (!result.success) return;
						const messageText = `event: message\ndata: ${JSON.stringify(result.data)}\n\n`;
						await writer.write(encoder.encode(messageText));
					} catch (error) {
						console.error("Error forwarding message to SSE:", error);
					}
				}
				onMessage(event).catch(console.error);
			});
			ws.addEventListener("error", (error) => {
				async function onError(_error) {
					try {
						await writer.close();
					} catch (_e) {}
				}
				onError(error).catch(console.error);
			});
			ws.addEventListener("close", () => {
				async function onClose() {
					try {
						await writer.close();
					} catch (error) {
						console.error("Error closing SSE connection:", error);
					}
				}
				onClose().catch(console.error);
			});
			return new Response(readable, { headers: {
				"Cache-Control": "no-cache",
				Connection: "keep-alive",
				"Content-Type": "text/event-stream",
				...corsHeaders(request, options.corsOptions)
			} });
		}
		if (request.method === "POST" && messagePattern.test(url)) {
			const sessionId = url.searchParams.get("sessionId");
			if (!sessionId) return new Response(`Missing sessionId. Expected POST to ${basePath} to initiate new one`, { status: 400 });
			const contentType = request.headers.get("content-type") || "";
			if (!contentType.includes("application/json")) return new Response(`Unsupported content-type: ${contentType}`, { status: 400 });
			const contentLength = Number.parseInt(request.headers.get("content-length") || "0", 10);
			if (contentLength > MAXIMUM_MESSAGE_SIZE_BYTES) return new Response(`Request body too large: ${contentLength} bytes`, { status: 400 });
			const agent = await getAgentByName(namespace, `sse:${sessionId}`, {
				props: ctx.props,
				jurisdiction: options.jurisdiction
			});
			const messageBody = await request.json();
			const extraInfo = { requestInfo: { headers: Object.fromEntries(request.headers.entries()) } };
			const error = await agent.onSSEMcpMessage(sessionId, messageBody, extraInfo);
			if (error) return new Response(error.message, {
				headers: {
					"Cache-Control": "no-cache",
					Connection: "keep-alive",
					"Content-Type": "text/event-stream",
					...corsHeaders(request, options.corsOptions)
				},
				status: 400
			});
			return new Response("Accepted", {
				headers: {
					"Cache-Control": "no-cache",
					Connection: "keep-alive",
					"Content-Type": "text/event-stream",
					...corsHeaders(request, options.corsOptions)
				},
				status: 202
			});
		}
		return new Response("Not Found", { status: 404 });
	};
};
/**
* Auto-negotiating handler that serves both streamable HTTP and legacy SSE
* on the same path. Streamable-HTTP-capable clients are preferred; legacy SSE
* clients fall back transparently.
*
* Discrimination rules:
*  - POST to `{basePath}/message` → legacy SSE (the sub-path is SSE-only)
*  - POST to `{basePath}` → streamable HTTP
*  - GET  with `mcp-session-id` header → streamable HTTP (standalone SSE reconnect)
*  - GET  without `mcp-session-id` → legacy SSE (new SSE connection)
*  - DELETE → streamable HTTP (SSE has no session teardown)
*/
const createAutoHandler = (basePath, namespace, options = {}) => {
	const handleStreamableHttp = createStreamingHttpHandler(basePath, namespace, options);
	const handleLegacySse = createLegacySseHandler(basePath, namespace, options);
	const messagePattern = new URLPattern({ pathname: `${basePath}/message` });
	return async (request, ctx) => {
		const url = new URL(request.url);
		if (request.method === "DELETE") return handleStreamableHttp(request, ctx);
		if (request.method === "POST" && messagePattern.test(url)) return handleLegacySse(request, ctx);
		if (request.method === "POST") return handleStreamableHttp(request, ctx);
		if (request.method === "GET" && request.headers.has("mcp-session-id")) return handleStreamableHttp(request, ctx);
		if (request.method === "GET") return handleLegacySse(request, ctx);
		return new Response("Method Not Allowed", {
			status: 405,
			headers: {
				Allow: "GET, POST, DELETE",
				...corsHeaders(request, options.corsOptions)
			}
		});
	};
};
function corsHeaders(_request, corsOptions = {}) {
	const origin = corsOptions.origin || "*";
	return {
		"Access-Control-Allow-Headers": corsOptions.headers || "Content-Type, Accept, Authorization, mcp-session-id, mcp-protocol-version",
		"Access-Control-Allow-Methods": corsOptions.methods || "GET, POST, DELETE, OPTIONS",
		"Access-Control-Allow-Origin": origin,
		"Access-Control-Expose-Headers": corsOptions.exposeHeaders || "mcp-session-id",
		"Access-Control-Max-Age": (corsOptions.maxAge || 86400).toString()
	};
}
function handleCORS(request, corsOptions) {
	if (request.method === "OPTIONS") return new Response(null, { headers: corsHeaders(request, corsOptions) });
	return null;
}
function isDurableObjectNamespace(namespace) {
	return typeof namespace === "object" && namespace !== null && "newUniqueId" in namespace && typeof namespace.newUniqueId === "function" && "idFromName" in namespace && typeof namespace.idFromName === "function";
}
//#endregion
//#region src/mcp/transport.ts
var McpSSETransport = class {
	constructor() {
		this._started = false;
		const { agent } = getCurrentAgent();
		if (!agent) throw new Error("McpAgent was not found in Transport constructor");
		this.sessionId = agent.getSessionId();
		this._getWebSocket = () => agent.getWebSocket();
	}
	async start() {
		if (this._started) throw new Error("Transport already started");
		this._started = true;
	}
	async send(message) {
		if (!this._started) throw new Error("Transport not started");
		const websocket = this._getWebSocket();
		if (!websocket) throw new Error("WebSocket not connected");
		try {
			websocket.send(JSON.stringify(message));
		} catch (error) {
			this.onerror?.(error);
		}
	}
	async close() {
		this.onclose?.();
	}
};
function isClearableEventStore(store) {
	return typeof store.clearStream === "function";
}
/**
* Adapted from: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/client/streamableHttp.ts
* - Validation and initialization are removed as they're handled in `McpAgent.serve()` handler.
* - Replaces the Node-style `req`/`res` with Worker's `Request`.
* - Writes events as WS messages that the Worker forwards to the client as SSE events.
* - Replaces the in-memory maps that track requestID/stream by using `connection.setState()` and `agent.getConnections()`.
*
* Besides these points, the implementation is the same and should be updated to match the original as new features are added.
*/
/** Fixed streamId for the standalone GET listen stream. */
const STANDALONE_STREAM_ID = "_GET_stream";
var StreamableHTTPServerTransport = class {
	constructor(options) {
		this._started = false;
		this._streamResponseIds = /* @__PURE__ */ new Map();
		const { agent } = getCurrentAgent();
		if (!agent) throw new Error("McpAgent was not found in Transport constructor");
		this._agent = agent;
		this.sessionId = agent.getSessionId();
		this._eventStore = options.eventStore;
	}
	/**
	* Starts the transport. This is required by the Transport interface but is a no-op
	* for the Streamable HTTP transport as connections are managed per-request.
	*/
	async start() {
		if (this._started) throw new Error("Transport already started");
		this._started = true;
	}
	/**
	* Handles GET requests for SSE stream.
	*
	* Two roles a GET can play:
	*   1. Fresh standalone listen stream — carries server-initiated
	*      requests/notifications unrelated to any in-progress POST.
	*   2. Resumption of a previously-disconnected stream via
	*      `Last-Event-ID`. The disconnected stream may have been the
	*      standalone stream OR a POST tool-call response stream; per the
	*      MCP 2025-03-26 spec the server replays missed messages "on the
	*      stream that was disconnected" and continues delivering
	*      subsequent messages on that same stream.
	*
	* To resume a POST stream we recover the original streamId from the
	* event-store and the original `requestIds` from durable storage,
	* then write them onto the new WS connection so `send()` keeps
	* routing in-flight tool responses to it.
	*/
	async handleGetRequest(req) {
		const { connection, agent } = getCurrentAgent();
		if (!connection) throw new Error("Connection was not found in handleGetRequest");
		if (!agent) throw new Error("Agent was not found in handleGetRequest");
		const lastEventId = req.headers.get("last-event-id");
		if (this._eventStore && lastEventId) {
			const resumedStreamId = await this._eventStore.getStreamIdForEventId?.(lastEventId);
			if (resumedStreamId) {
				const resumeState = { streamId: resumedStreamId };
				if (resumedStreamId === STANDALONE_STREAM_ID) resumeState._standaloneSse = true;
				else {
					const persistedReqs = await agent.getStreamRequestIds(resumedStreamId);
					if (persistedReqs && persistedReqs.length > 0) resumeState.requestIds = persistedReqs;
				}
				this.supersedePriorStreamConnections(agent, connection.id, resumedStreamId);
				connection.setState(resumeState);
				await this.replayEvents(lastEventId);
				return;
			}
		}
		this.supersedePriorStreamConnections(agent, connection.id, STANDALONE_STREAM_ID);
		const standaloneState = {
			streamId: STANDALONE_STREAM_ID,
			_standaloneSse: true
		};
		connection.setState(standaloneState);
	}
	/**
	* Close any connection (other than `selfId`) currently bound to
	* `streamId`, so at most one live connection serves a given stream.
	* Closing rather than mutating sibling state mirrors how the SDK's
	* single `_streamMapping` entry gives last-writer-wins for free, and
	* keeps `send()` from routing to a stale bridge.
	*/
	supersedePriorStreamConnections(agent, selfId, streamId) {
		for (const other of agent.getConnections()) {
			if (other.id === selfId) continue;
			if (other.state?.streamId !== streamId) continue;
			other.close(1e3, "Superseded by resumed stream");
		}
	}
	/**
	* Replays events that would have been sent after the specified event ID
	* Only used when resumability is enabled
	*/
	async replayEvents(lastEventId) {
		if (!this._eventStore) return;
		const { connection } = getCurrentAgent();
		if (!connection) throw new Error("Connection was not available in replayEvents");
		try {
			await this._eventStore?.replayEventsAfter(lastEventId, { send: async (eventId, message) => {
				try {
					this.writeSSEEvent(connection, message, eventId);
				} catch (error) {
					this.onerror?.(error);
				}
			} });
		} catch (error) {
			this.onerror?.(error);
		}
	}
	/**
	* Writes an event to the SSE stream with proper formatting
	*/
	writeSSEEvent(connection, message, eventId, close) {
		let eventData = "event: message\n";
		if (eventId) eventData += `id: ${eventId}\n`;
		eventData += `data: ${JSON.stringify(message)}\n\n`;
		return connection.send(JSON.stringify({
			type: "cf_mcp_agent_event",
			event: eventData,
			close
		}));
	}
	/**
	* Handles POST requests containing JSON-RPC messages
	*/
	async handlePostRequest(req, parsedBody) {
		const authInfo = req.auth;
		const requestInfo = {
			headers: Object.fromEntries(req.headers.entries()),
			url: new URL(req.url)
		};
		delete requestInfo.headers[MCP_HTTP_METHOD_HEADER];
		delete requestInfo.headers[MCP_MESSAGE_HEADER];
		delete requestInfo.headers.upgrade;
		const rawMessage = parsedBody;
		let messages;
		if (Array.isArray(rawMessage)) messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
		else messages = [JSONRPCMessageSchema.parse(rawMessage)];
		const hasRequests = messages.some(isJSONRPCRequest);
		if (!hasRequests) for (const message of messages) {
			if (this.messageInterceptor) {
				if (await this.messageInterceptor(message, {
					authInfo,
					requestInfo
				})) continue;
			}
			this.onmessage?.(message, {
				authInfo,
				requestInfo
			});
		}
		else if (hasRequests) {
			const { connection, agent } = getCurrentAgent();
			if (!connection) throw new Error("Connection was not found in handlePostRequest");
			if (!agent) throw new Error("Agent was not found in handlePostRequest");
			const requestIds = messages.filter(isJSONRPCRequest).map((message) => message.id);
			const streamId = connection.id;
			const postState = {
				streamId,
				requestIds
			};
			connection.setState(postState);
			if (this._eventStore) await agent.setStreamRequestIds(streamId, requestIds);
			for (const message of messages) {
				if (this.messageInterceptor) {
					if (await this.messageInterceptor(message, {
						authInfo,
						requestInfo
					})) continue;
				}
				this.onmessage?.(message, {
					authInfo,
					requestInfo
				});
			}
		}
	}
	async close() {
		const agent = this._agent;
		for (const conn of agent.getConnections()) conn.close(1e3, "Session closed");
		this.onclose?.();
	}
	/**
	* Store the event, decide whether this is the final response, write
	* the SSE frame iff a live connection is attached, then run cleanup.
	* Caller resolves `streamId` and `relatedIds` (from connection state
	* or persisted reverse lookup) and passes `liveConnection` as null
	* when the originating WS has dropped.
	*/
	async sendOnStream(agent, streamId, relatedIds, liveConnection, message, requestId) {
		const eventId = await this._eventStore?.storeEvent(streamId, message);
		let shouldClose = false;
		if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
			let responseIds = this._streamResponseIds.get(streamId);
			if (!responseIds) {
				responseIds = /* @__PURE__ */ new Set();
				this._streamResponseIds.set(streamId, responseIds);
			}
			responseIds.add(requestId);
			shouldClose = relatedIds.every((id) => responseIds.has(id));
			if (shouldClose) this._streamResponseIds.delete(streamId);
		}
		if (liveConnection) try {
			this.writeSSEEvent(liveConnection, message, eventId, shouldClose);
		} catch (error) {
			this.onerror?.(error);
		}
		if (shouldClose) {
			await agent.deleteStreamRequestIds(streamId);
			if (this._eventStore && isClearableEventStore(this._eventStore)) await this._eventStore.clearStream(streamId);
		}
	}
	async send(message, options) {
		const isResponse = isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message);
		const requestId = isResponse ? message.id : options?.relatedRequestId;
		if (requestId === void 0) {
			if (isResponse) throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
			return this.sendStandalone(message);
		}
		return this.sendForRequest(message, requestId);
	}
	/**
	* Server-initiated message on the standalone GET stream. Stored under
	* a fixed streamId so it's replayable even when no live connection is
	* currently attached.
	*
	* Sent on exactly one stream, per MCP: "the server MUST send each of
	* its JSON-RPC messages on only one of the connected streams; it MUST
	* NOT broadcast the same message across multiple streams."
	* `handleGetRequest` supersedes prior standalone connections, so
	* there is at most one to send on.
	*/
	async sendStandalone(message) {
		const agent = this._agent;
		const eventId = await this._eventStore?.storeEvent(STANDALONE_STREAM_ID, message);
		const standalone = Array.from(agent.getConnections()).find((conn) => conn.state?._standaloneSse);
		if (standalone) this.writeSSEEvent(standalone, message, eventId);
	}
	/**
	* Message scoped to a specific in-flight client request: a tool
	* response, error, or progress notification. Resolves which stream
	* owns the request id (live POST connection, resumed GET, or
	* persisted reverse lookup for a dropped WS) and delegates to
	* {@link sendOnStream} for the actual store / write / cleanup.
	*/
	async sendForRequest(message, requestId) {
		const agent = this._agent;
		const context = getCurrentAgent();
		const originatingConnection = context.agent === agent ? context.connection : void 0;
		const matchingConnections = Array.from(agent.getConnections()).filter((conn) => conn.state?.requestIds?.includes(requestId));
		const liveConnection = matchingConnections.find((conn) => conn.id === originatingConnection?.id) ?? (matchingConnections.length === 1 ? matchingConnections[0] : null);
		if (!liveConnection && matchingConnections.length > 1) {
			const routingError = {
				jsonrpc: "2.0",
				id: requestId,
				error: {
					code: -32603,
					message: "Internal error"
				}
			};
			await Promise.all(matchingConnections.map((candidate) => this.sendOnStream(agent, candidate.state?.streamId ?? candidate.id, candidate.state?.requestIds ?? [], candidate, routingError, requestId)));
			return;
		}
		let streamId = liveConnection?.state?.streamId;
		let relatedIds = liveConnection?.state?.requestIds;
		if (!streamId) {
			const stored = await agent.getStreamForRequestId(requestId);
			if (!stored) throw new Error(`No active stream found for request ID: ${String(requestId)}`);
			streamId = stored.streamId;
			relatedIds = stored.requestIds;
		}
		await this.sendOnStream(agent, streamId, relatedIds ?? [], liveConnection, message, requestId);
	}
};
//#endregion
//#region src/mcp/event-store.ts
/**
* Durable Object–backed {@link EventStore} for SSE resumability.
*
* Default for `McpAgent`. Override `McpAgent.getEventStore()` to swap
* or disable.
*
* ## Storage layout
*
* Events are stored under `__mcp_event__:<streamId>:<seqHex>`, where
* `<seqHex>` is a 16-char zero-padded counter so events in a stream
* sort lexicographically and `getStreamIdForEventId` can recover the
* stream from `eventId` without a storage hit.
*
* ## Lifecycle
*
* Each POST tool-call stream's events live only until the final
* response is delivered. The transport calls {@link clearStream}
* immediately after writing the close frame, so storage growth is
* bounded by the in-flight POST streams plus the standalone GET
* stream. There is no background sweep — quiescent agents do no work,
* and the DO itself dies with the session.
*
* Standalone GET stream events (`_GET_stream`) are *not* cleared
* automatically; they accumulate for the lifetime of the DO. Bounded
* by session length in practice.
*
* Trade-off: if the client TCP connection dies *after* the close
* frame has been enqueued on the WS but before the bytes reach the
* client, the final message is unreplayable. Every earlier event in
* the stream is still replayable while the in-flight stream is open.
*
* ## Stream id constraints
*
* `streamId` MUST NOT contain `:`. `storeEvent` asserts this so
* embedders using custom stream ids fail loudly rather than risk
* prefix-scan collisions (e.g. clearing `a` accidentally hitting
* `a:b`). Default ids (`connection.id` UUIDs and the literal
* `_GET_stream`) already satisfy this.
*/
var DurableObjectEventStore = class DurableObjectEventStore {
	constructor(storage) {
		this.seqByStream = /* @__PURE__ */ new Map();
		this.seqInit = /* @__PURE__ */ new Map();
		this.storage = storage;
	}
	async storeEvent(streamId, message) {
		if (streamId.includes(":")) throw new Error(`DurableObjectEventStore: streamId must not contain ':' (got ${JSON.stringify(streamId)})`);
		await this.ensureSeqLoaded(streamId);
		const seq = (this.seqByStream.get(streamId) ?? 0) + 1;
		this.seqByStream.set(streamId, seq);
		const eventId = `${streamId}:${seq.toString(16).padStart(DurableObjectEventStore.SEQ_PAD, "0")}`;
		const eventKey = `${DurableObjectEventStore.EVENT_KEY_PREFIX}${eventId}`;
		await this.storage.put(eventKey, message);
		return eventId;
	}
	async getStreamIdForEventId(eventId) {
		const idx = eventId.lastIndexOf(":");
		return idx > 0 ? eventId.slice(0, idx) : void 0;
	}
	async replayEventsAfter(lastEventId, { send }) {
		const streamId = await this.getStreamIdForEventId(lastEventId);
		if (!streamId) return "";
		const prefix = `${DurableObjectEventStore.EVENT_KEY_PREFIX}${streamId}:`;
		const startKey = `${DurableObjectEventStore.EVENT_KEY_PREFIX}${lastEventId}\x00`;
		const rows = await this.storage.list({
			prefix,
			start: startKey,
			limit: DurableObjectEventStore.REPLAY_LIMIT
		});
		for (const [key, message] of rows) await send(key.slice(DurableObjectEventStore.EVENT_KEY_PREFIX.length), message);
		return streamId;
	}
	/**
	* Drop the event log for a single stream. Called by the transport
	* immediately after a POST's final response has been written to the
	* wire — no future `Last-Event-ID` for this stream is expected to
	* resolve.
	*
	* Lists and deletes in chunks of {@link DELETE_CHUNK} (128, the DO
	* storage cap) so we never load the entire event log into memory.
	* After deleting, the next `list` call won't see the deleted keys,
	* so passing `start: <prefix>` again is enough — no cursor bookkeeping.
	*/
	async clearStream(streamId) {
		const prefix = `${DurableObjectEventStore.EVENT_KEY_PREFIX}${streamId}:`;
		for (;;) {
			const rows = await this.storage.list({
				prefix,
				limit: DurableObjectEventStore.DELETE_CHUNK
			});
			if (rows.size === 0) break;
			await this.storage.delete([...rows.keys()]);
		}
		this.seqByStream.delete(streamId);
		this.seqInit.delete(streamId);
	}
	async ensureSeqLoaded(streamId) {
		if (this.seqByStream.has(streamId)) return;
		let pending = this.seqInit.get(streamId);
		if (!pending) {
			pending = (async () => {
				const prefix = `${DurableObjectEventStore.EVENT_KEY_PREFIX}${streamId}:`;
				const rows = await this.storage.list({
					prefix,
					reverse: true,
					limit: 1
				});
				let seq = 0;
				for (const key of rows.keys()) {
					const parsed = Number.parseInt(key.slice(prefix.length), 16);
					if (Number.isFinite(parsed)) seq = parsed;
				}
				if (!this.seqByStream.has(streamId)) this.seqByStream.set(streamId, seq);
			})();
			this.seqInit.set(streamId, pending);
		}
		try {
			await pending;
		} finally {
			this.seqInit.delete(streamId);
		}
	}
};
DurableObjectEventStore.EVENT_KEY_PREFIX = "__mcp_event__:";
DurableObjectEventStore.SEQ_PAD = 16;
DurableObjectEventStore.DELETE_CHUNK = 128;
DurableObjectEventStore.REPLAY_LIMIT = 1e3;
//#endregion
//#region src/mcp/client-transports.ts
/**
* Deprecated transport wrappers
*/
let didWarnAboutSSEEdgeClientTransport = false;
/**
* @deprecated Use SSEClientTransport from @modelcontextprotocol/client instead. This alias will be removed in the next major version.
*/
var SSEEdgeClientTransport = class extends SSEClientTransport {
	constructor(url, options) {
		super(url, options);
		if (!didWarnAboutSSEEdgeClientTransport) {
			didWarnAboutSSEEdgeClientTransport = true;
			console.warn("SSEEdgeClientTransport is deprecated. Use SSEClientTransport from @modelcontextprotocol/client instead. SSEEdgeClientTransport will be removed in the next major version.");
		}
	}
};
let didWarnAboutStreamableHTTPEdgeClientTransport = false;
/**
* @deprecated Use StreamableHTTPClientTransport from @modelcontextprotocol/client instead. This alias will be removed in the next major version.
*/
var StreamableHTTPEdgeClientTransport = class extends StreamableHTTPClientTransport {
	constructor(url, options) {
		super(url, options);
		if (!didWarnAboutStreamableHTTPEdgeClientTransport) {
			didWarnAboutStreamableHTTPEdgeClientTransport = true;
			console.warn("StreamableHTTPEdgeClientTransport is deprecated. Use StreamableHTTPClientTransport from @modelcontextprotocol/client instead. StreamableHTTPEdgeClientTransport will be removed in the next major version.");
		}
	}
};
//#endregion
//#region src/mcp/worker-transport.ts
/**
* WorkerTransport — retained sessionful Legacy transport
*
* Thin Cloudflare-Workers wrapper around the official MCP SDK v1
* `WebStandardStreamableHTTPServerTransport`. The wrapper layers a couple of
* Workers-specific concerns on top of the SDK transport without forking it:
*
*  1. **CORS** — preflight handling and response-header injection,
*     configurable via `corsOptions`.
*  2. **Persistent transport state** — when a `storage` adapter
*     (`MCPStorageApi`) is supplied, the wrapper persists
*     `{sessionId, initialized, initializeParams}` so that an MCP session can
*     survive DO hibernation / eviction. On the first request after a cold
*     start, the saved initialize params are replayed through the `Server`
*     so client capabilities are re-established.
*  3. **SSE keepalive** — delegated to the SDK transport, which writes SSE
*     comment frames and owns timer cleanup. Configure the cadence with the
*     inherited `keepAliveMs` option.
*
* Stateless handlers do not import this module. Everything else (session
* validation, SSE streaming, protocol-version
* negotiation, event-store resumability, etc.) is delegated to the SDK
* transport.
*/
/** Sentinel id used when replaying the persisted initialize request. */
const RESTORE_REQUEST_ID = "__worker_transport_restore__";
const DEFAULT_CORS_OPTIONS = {
	origin: "*",
	headers: "Content-Type, Accept, Authorization, mcp-session-id, MCP-Protocol-Version",
	methods: "GET, POST, DELETE, OPTIONS",
	exposeHeaders: "mcp-session-id",
	maxAge: 86400
};
var WorkerTransport = class extends WebStandardStreamableHTTPServerTransport$1 {
	constructor(options = {}) {
		const { corsOptions, storage, onsessioninitialized, ...sdkOptions } = options;
		super({
			...sdkOptions,
			onsessioninitialized: void 0
		});
		this._stateRestored = false;
		this._bridgeInstalled = false;
		this._closedRequestIds = /* @__PURE__ */ new Set();
		this._corsOptions = corsOptions;
		this._storage = storage;
		this._userOnSessionInitialized = onsessioninitialized;
	}
	/**
	* Backwards-compatible alias for the SDK's internal `_started` flag.
	* Several callers and tests check `transport.started` directly.
	*/
	get started() {
		return this._started;
	}
	/**
	* Top-level request entry point. Handles CORS preflight, restores any
	* persisted state on first invocation, then delegates to the SDK transport
	* and finally appends CORS headers to whatever response comes back.
	*/
	async handleRequest(request, options) {
		if (request.method === "OPTIONS") return new Response(null, { headers: this.getCorsHeaders({ forPreflight: true }) });
		await this.restoreState();
		this.installOnSessionInitializedBridge();
		await this.captureInitializeParams(request, options);
		const response = await super.handleRequest(request, options);
		return this.withCorsHeaders(this.normalizeAllowHeader(response));
	}
	/**
	* The SDK's 405 responses advertise `Allow: GET, POST, DELETE` because
	* OPTIONS is handled outside the SDK. Since our wrapper *does* handle
	* OPTIONS, advertise it in `Allow` so clients can probe accurately.
	*/
	normalizeAllowHeader(response) {
		if (response.status !== 405) return response;
		const allow = response.headers.get("Allow");
		if (!allow || allow.includes("OPTIONS")) return response;
		const headers = new Headers(response.headers);
		headers.set("Allow", `${allow}, OPTIONS`);
		return new Response(response.body, {
			status: response.status,
			statusText: response.statusText,
			headers
		});
	}
	closeSSEStream(requestId) {
		this._closedRequestIds.add(requestId);
		super.closeSSEStream(requestId);
	}
	async close() {
		this._closedRequestIds.clear();
		await super.close();
	}
	/**
	* Swallow two classes of message that would otherwise surface as
	* unhandled rejections from the SDK transport's `send()`:
	*
	*   1. Replayed initialize responses (the `RESTORE_REQUEST_ID` sentinel)
	*      — we synthesise these in `restoreState()` to rebuild server
	*      capabilities; there's no real client waiting for the response.
	*   2. Sends for a request id whose SSE stream has been deliberately
	*      closed via `closeSSEStream`. The protocol layer's tool-handler
	*      promise may settle after the close, and the SDK's `send()` throws
	*      "No connection established" — a race the pre-refactor transport
	*      silently swallowed.
	*
	* Everything else is delegated. We use `await super.send(...)` rather
	* than `return super.send(...)` so any rejection is observed inside this
	* async frame; without the await, the test runner's
	* unhandled-rejection tracker can fire before the caller's own `await`
	* observes it.
	*/
	async send(message, options) {
		let requestId = options?.relatedRequestId;
		if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) requestId = message.id;
		if (requestId === RESTORE_REQUEST_ID) return;
		if (requestId !== void 0 && this._closedRequestIds.has(requestId)) return;
		await super.send(message, options);
	}
	getCorsHeaders({ forPreflight } = {}) {
		const merged = {
			...DEFAULT_CORS_OPTIONS,
			...this._corsOptions
		};
		if (forPreflight) return {
			"Access-Control-Allow-Origin": merged.origin,
			"Access-Control-Allow-Headers": merged.headers,
			"Access-Control-Allow-Methods": merged.methods,
			"Access-Control-Max-Age": String(merged.maxAge)
		};
		return {
			"Access-Control-Allow-Origin": merged.origin,
			"Access-Control-Expose-Headers": merged.exposeHeaders
		};
	}
	withCorsHeaders(response) {
		const headers = new Headers(response.headers);
		for (const [k, v] of Object.entries(this.getCorsHeaders())) headers.set(k, v);
		return new Response(response.body, {
			status: response.status,
			statusText: response.statusText,
			headers
		});
	}
	installOnSessionInitializedBridge() {
		if (this._bridgeInstalled) return;
		const sdk = this;
		sdk._onsessioninitialized = async (sessionId) => {
			if (this._userOnSessionInitialized) await Promise.resolve(this._userOnSessionInitialized(sessionId));
			await this.saveState();
		};
		this._bridgeInstalled = true;
	}
	async captureInitializeParams(request, handleOptions) {
		if (request.method !== "POST") return;
		try {
			const parsed = handleOptions?.parsedBody ?? await request.clone().json();
			const init = (Array.isArray(parsed) ? parsed : [parsed]).find((m) => typeof m === "object" && m !== null && isInitializeRequest(m));
			if (init && isInitializeRequest(init)) this._capturedInitializeParams = {
				capabilities: init.params.capabilities,
				clientInfo: init.params.clientInfo,
				protocolVersion: init.params.protocolVersion
			};
		} catch {}
	}
	async restoreState() {
		if (!this._storage || this._stateRestored) return;
		this._stateRestored = true;
		let state;
		try {
			state = await Promise.resolve(this._storage.get());
		} catch (error) {
			this._stateRestored = false;
			throw error;
		}
		if (!state) return;
		const sdk = this;
		sdk.sessionId = state.sessionId;
		sdk._initialized = state.initialized;
		this._capturedInitializeParams = state.initializeParams;
		if (state.initializeParams && this.onmessage) this.onmessage({
			jsonrpc: "2.0",
			id: RESTORE_REQUEST_ID,
			method: "initialize",
			params: state.initializeParams
		});
	}
	async saveState() {
		if (!this._storage) return;
		const sdk = this;
		const state = {
			sessionId: sdk.sessionId,
			initialized: sdk._initialized,
			initializeParams: this._capturedInitializeParams
		};
		await Promise.resolve(this._storage.set(state));
	}
};
//#endregion
//#region src/mcp/handler-legacy.ts
/**
* Create a sessionful Legacy MCP handler backed by SDK v1.
*
* New Stateless servers should use `createMcpHandler` from
* `agents/mcp/server` instead.
*/
function createLegacyMcpHandler(server, options = {}) {
	const route = options.route ?? "/mcp";
	const { route: _route, authContext, transport: providedTransport, ...transportOptions } = options;
	return async (request, _env, ctx) => {
		const url = new URL(request.url);
		if (route && url.pathname !== route) return new Response("Not Found", { status: 404 });
		const transport = providedTransport ?? new WorkerTransport(transportOptions);
		const resolvedAuthContext = authContext ?? (ctx.props && Object.keys(ctx.props).length > 0 ? { props: ctx.props } : void 0);
		if (!transport.started) {
			if (server instanceof McpServer$1 ? server.isConnected() : server.transport !== void 0) throw new Error("Server is already connected to a transport. Create a new McpServer instance per request for stateless handlers.");
			await server.connect(transport);
		}
		const handleRequest = () => transport.handleRequest(request);
		try {
			return resolvedAuthContext ? await runWithAuthContext(resolvedAuthContext, handleRequest) : await handleRequest();
		} catch (error) {
			console.error("MCP handler error:", error);
			return Response.json({
				jsonrpc: "2.0",
				error: {
					code: -32603,
					message: error instanceof Error ? error.message : "Internal server error"
				},
				id: null
			}, { status: 500 });
		}
	};
}
//#endregion
//#region src/mcp/handler-warning.ts
let didWarnAboutLegacyCreateMcpHandlerOverload = false;
function warnLegacyCreateMcpHandlerOverload() {
	if (didWarnAboutLegacyCreateMcpHandlerOverload) return;
	didWarnAboutLegacyCreateMcpHandlerOverload = true;
	console.warn("[agents/mcp] Passing an MCP SDK v1 server to createMcpHandler is deprecated and will be removed in the next major version. Pass an @modelcontextprotocol/server factory to createMcpHandler. To temporarily retain sessionful SDK v1 behavior while migrating, use createLegacyMcpHandler.");
}
//#endregion
//#region src/mcp/handler-compat.ts
function createMcpHandler(serverOrFactory, options = {}) {
	if (typeof serverOrFactory === "function") return createStatelessMcpHandler(serverOrFactory, options);
	if (serverOrFactory instanceof McpServer$1 || serverOrFactory instanceof Server$1) {
		warnLegacyCreateMcpHandlerOverload();
		return createLegacyMcpHandler(serverOrFactory, options);
	}
	throw new TypeError("createMcpHandler received an unsupported server. Pass a factory returning McpServer or Server from \"@modelcontextprotocol/server\", or use createLegacyMcpHandler with an MCP SDK v1 server.");
}
let didWarnAboutExperimentalCreateMcpHandler = false;
/**
* @deprecated Pass an SDK v2 factory to createMcpHandler.
* experimental_createMcpHandler will be removed in the next major version.
* Use createLegacyMcpHandler only to temporarily retain sessionful SDK v1
* behavior while migrating.
*/
function experimental_createMcpHandler(server, options = {}) {
	if (!didWarnAboutExperimentalCreateMcpHandler) {
		didWarnAboutExperimentalCreateMcpHandler = true;
		console.warn("experimental_createMcpHandler is deprecated and will be removed in the next major version. Pass an @modelcontextprotocol/server factory to createMcpHandler. To temporarily retain sessionful SDK v1 behavior while migrating, use createLegacyMcpHandler.");
	}
	return createLegacyMcpHandler(server, options);
}
//#endregion
//#region src/mcp/legacy-agent.ts
/**
* @deprecated McpAgent is feature-frozen. Migrate to an SDK v2 factory with
* createMcpHandler from agents/mcp/server. When sessionful features prevent an
* immediate migration, run the stateless route beside the existing McpAgent
* route until clients transition and sessions drain.
*/
var McpAgent = class McpAgent extends Agent {
	constructor(..._args) {
		super(..._args);
		this._pendingElicitations = /* @__PURE__ */ new Map();
	}
	shouldSendProtocolMessages(_connection, ctx) {
		return !ctx.request.headers.get(MCP_HTTP_METHOD_HEADER);
	}
	async setInitializeRequest(initializeRequest) {
		await this.ctx.storage.put("initializeRequest", initializeRequest);
	}
	async getInitializeRequest() {
		return this.ctx.storage.get("initializeRequest");
	}
	/** Persist the `requestIds` for a POST stream. @internal */
	async setStreamRequestIds(streamId, requestIds) {
		await this.ctx.storage.put(`${McpAgent.STREAM_REQS_KEY_PREFIX}${streamId}`, requestIds);
	}
	/** Read the persisted `requestIds` for a POST stream. @internal */
	async getStreamRequestIds(streamId) {
		return this.ctx.storage.get(`${McpAgent.STREAM_REQS_KEY_PREFIX}${streamId}`);
	}
	/** Drop the persisted `requestIds` for a POST stream. @internal */
	async deleteStreamRequestIds(streamId) {
		await this.ctx.storage.delete(`${McpAgent.STREAM_REQS_KEY_PREFIX}${streamId}`);
	}
	/**
	* Reverse lookup: find which POST stream a given `requestId` belongs
	* to, and return the stream's full `requestIds` list in the same
	* pass. Used by the transport when the originating WS has dropped,
	* so `send()` can still record events for replay and decide whether
	* the stream is fully responded — mirrors the SDK's
	* `_requestToStreamMapping` which outlives connection loss.
	*
	* Returning `requestIds` alongside `streamId` lets `send()` skip a
	* second `getStreamRequestIds` read on the same key.
	*
	* O(n) in the number of in-flight POST streams — single-digit in
	* practice since each stream is cleaned up on its final response.
	* The `limit` is a defensive ceiling so an abandoned-POST leak can't
	* unbounded-load this scan; if you hit it, something else has gone
	* wrong and `send()` will throw `No active stream found`.
	*
	* @internal
	*/
	async getStreamForRequestId(requestId) {
		const STREAM_REQS_SCAN_LIMIT = 1e3;
		const rows = await this.ctx.storage.list({
			prefix: McpAgent.STREAM_REQS_KEY_PREFIX,
			limit: STREAM_REQS_SCAN_LIMIT
		});
		if (rows.size === STREAM_REQS_SCAN_LIMIT) console.warn(`McpAgent: getStreamForRequestId hit the ${STREAM_REQS_SCAN_LIMIT}-key scan cap; stale __mcp_stream_reqs__ entries may be accumulating from abandoned POSTs`);
		for (const [key, requestIds] of rows) if (requestIds?.includes(requestId)) return {
			streamId: key.slice(McpAgent.STREAM_REQS_KEY_PREFIX.length),
			requestIds
		};
	}
	/** Read the transport type for this agent.
	* This relies on the naming scheme being `sse:${sessionId}`,
	* `streamable-http:${sessionId}`, or `rpc:${sessionId}`.
	*/
	getTransportType() {
		const [t, ..._] = this.name.split(":");
		switch (t) {
			case "sse": return "sse";
			case "streamable-http": return "streamable-http";
			case "rpc": return "rpc";
			default: throw new Error("Invalid transport type. McpAgent must be addressed with a valid protocol.");
		}
	}
	/** Read the sessionId for this agent.
	* This relies on the naming scheme being `sse:${sessionId}`
	* or `streamable-http:${sessionId}`.
	*/
	getSessionId() {
		const [_, sessionId] = this.name.split(":");
		if (!sessionId) throw new Error("Invalid session id. McpAgent must be addressed with a valid session id.");
		return sessionId;
	}
	/** Get the unique WebSocket. SSE transport only. */
	getWebSocket() {
		const websockets = Array.from(this.getConnections());
		if (websockets.length === 0) return null;
		return websockets[0];
	}
	/**
	* Returns options for configuring the RPC server transport.
	* Override this method to customize RPC transport behavior (e.g., timeout).
	*
	* @example
	* ```typescript
	* class MyMCP extends McpAgent {
	*   protected getRpcTransportOptions() {
	*     return { timeout: 120000 }; // 2 minutes
	*   }
	* }
	* ```
	*/
	getRpcTransportOptions() {
		return {};
	}
	/**
	* Returns the {@link EventStore} for SSE resumability. Defaults to a
	* {@link DurableObjectEventStore} backed by this agent's storage,
	* letting clients reconnect with `Last-Event-ID` after the Cloudflare
	* edge closes an idle SSE stream (~5 minute watchdog) instead of
	* relying on a server-side keepalive that would block hibernation.
	*
	* Per-stream events are cleared by the transport immediately after
	* the final response is written to the wire, so there's no
	* background cleanup — storage cost is bounded by the in-flight
	* streams alone.
	*
	* Override to disable (`return undefined`) or swap implementations.
	*/
	getEventStore() {
		return new DurableObjectEventStore(this.ctx.storage);
	}
	/** Returns a new transport matching the type of the Agent. */
	initTransport() {
		switch (this.getTransportType()) {
			case "sse": return new McpSSETransport();
			case "streamable-http": {
				const transport = new StreamableHTTPServerTransport({ eventStore: this.getEventStore() });
				transport.messageInterceptor = (message) => {
					return Promise.resolve(this._handleElicitationResponse(message));
				};
				return transport;
			}
			case "rpc": return new RPCServerTransport(this.getRpcTransportOptions());
		}
	}
	/** Update and store the props */
	async updateProps(props) {
		await this.ctx.storage.put("props", props ?? {});
		this.props = props;
	}
	async reinitializeServer() {
		const initializeRequest = await this.getInitializeRequest();
		if (initializeRequest) this._transport?.onmessage?.(initializeRequest);
	}
	/** Sets up the MCP transport and server every time the Agent is started.*/
	async onStart(props) {
		if (props) await this.updateProps(props);
		else this.props = await this.ctx.storage.get("props");
		await this.init();
		const server = await this.server;
		if (server instanceof McpServer || server instanceof Server) throw new TypeError("McpAgent uses MCP SDK v1 and cannot serve McpServer from \"@modelcontextprotocol/server\". For MCP SDK v2, use createMcpHandler. Existing McpAgent applications should continue importing McpServer from \"@modelcontextprotocol/sdk/server/mcp.js\".");
		this._transport = this.initTransport();
		if (!this._transport) throw new Error("Failed to initialize transport");
		await server.connect(this._transport);
		await this.reinitializeServer();
	}
	/** Validates new WebSocket connections. */
	async onConnect(conn, { request: req }) {
		switch (this.getTransportType()) {
			case "sse":
				if (Array.from(this.getConnections()).length > 1) {
					conn.close(1008, "Websocket already connected");
					return;
				}
				break;
			case "streamable-http": if (this._transport instanceof StreamableHTTPServerTransport) switch (req.headers.get(MCP_HTTP_METHOD_HEADER)) {
				case "POST": {
					const payloadHeader = req.headers.get(MCP_MESSAGE_HEADER);
					let rawPayload;
					if (!payloadHeader) rawPayload = "{}";
					else try {
						rawPayload = Buffer.from(payloadHeader, "base64").toString("utf-8");
					} catch (_error) {
						throw new Error("Internal Server Error: Failed to decode MCP message header");
					}
					const parsedBody = JSON.parse(rawPayload);
					this._transport?.handlePostRequest(req, parsedBody);
					break;
				}
				case "GET":
					this._transport?.handleGetRequest(req);
					break;
			}
		}
	}
	/** Handles MCP Messages for the legacy SSE transport. */
	async onSSEMcpMessage(_sessionId, messageBody, extraInfo) {
		if (this.getTransportType() !== "sse") return /* @__PURE__ */ new Error("Internal Server Error: Expected SSE transport");
		try {
			let parsedMessage;
			try {
				parsedMessage = JSONRPCMessageSchema.parse(messageBody);
			} catch (error) {
				this._transport?.onerror?.(error);
				throw error;
			}
			if (this._handleElicitationResponse(parsedMessage)) return null;
			this._transport?.onmessage?.(parsedMessage, extraInfo);
			return null;
		} catch (error) {
			console.error("Error forwarding message to SSE:", error);
			this._transport?.onerror?.(error);
			return error;
		}
	}
	/** Elicit user input with a message and schema */
	async elicitInput(params, options) {
		const requestId = `elicit_${Math.random().toString(36).substring(2, 11)}`;
		const elicitRequest = {
			jsonrpc: "2.0",
			id: requestId,
			method: "elicitation/create",
			params: {
				message: params.message,
				requestedSchema: params.requestedSchema
			}
		};
		let timeoutId;
		const responsePromise = new Promise((resolve, reject) => {
			timeoutId = setTimeout(() => {
				this._pendingElicitations.delete(requestId);
				reject(/* @__PURE__ */ new Error("Elicitation request timed out"));
			}, 6e4);
			this._pendingElicitations.set(requestId, {
				resolve: (result) => {
					clearTimeout(timeoutId);
					this._pendingElicitations.delete(requestId);
					resolve(result);
				},
				reject: (err) => {
					clearTimeout(timeoutId);
					this._pendingElicitations.delete(requestId);
					reject(err);
				}
			});
		});
		const cleanup = () => {
			clearTimeout(timeoutId);
			this._pendingElicitations.delete(requestId);
		};
		return this.keepAliveWhile(async () => {
			if (this._transport) try {
				await this._transport.send(elicitRequest, options);
			} catch (error) {
				cleanup();
				throw error;
			}
			else {
				const connections = this.getConnections();
				if (!connections || Array.from(connections).length === 0) {
					cleanup();
					throw new Error("No active connections available for elicitation");
				}
				const connectionList = Array.from(connections);
				for (const connection of connectionList) try {
					connection.send(JSON.stringify(elicitRequest));
				} catch (error) {
					console.error("Failed to send elicitation request:", error);
				}
			}
			return responsePromise;
		});
	}
	/** Handle elicitation responses via in-memory resolver */
	_handleElicitationResponse(message) {
		if (isJSONRPCResultResponse(message) && message.result) {
			const requestId = message.id?.toString();
			if (!requestId || !requestId.startsWith("elicit_")) return false;
			const pending = this._pendingElicitations.get(requestId);
			if (!pending) return false;
			pending.resolve(message.result);
			return true;
		}
		if (isJSONRPCErrorResponse(message)) {
			const requestId = message.id?.toString();
			if (!requestId || !requestId.startsWith("elicit_")) return false;
			const pending = this._pendingElicitations.get(requestId);
			if (!pending) return false;
			pending.resolve({
				action: "cancel",
				content: { error: message.error.message || "Elicitation request failed" }
			});
			return true;
		}
		return false;
	}
	/**
	* Handle an RPC message for MCP
	* This method is called by the RPC stub to process MCP messages
	* @param message The JSON-RPC message(s) to handle
	* @returns The response message(s) or undefined
	*/
	async handleMcpMessage(message) {
		await this.__unsafe_ensureInitialized();
		if (!(this._transport instanceof RPCServerTransport)) throw new Error("Expected RPC transport");
		const transport = this._transport;
		return await this.keepAliveWhile(async () => {
			if (!Array.isArray(message)) {
				const parseResult = JSONRPCMessageSchema.safeParse(message);
				if (parseResult.success && this._handleElicitationResponse(parseResult.data)) return await transport._awaitPendingResponse();
			}
			return await transport.handle(message);
		});
	}
	/** Return a handler for the given path for this MCP.
	* Defaults to Streamable HTTP transport.
	*/
	static serve(path, { binding = "MCP_OBJECT", corsOptions, transport = "streamable-http", jurisdiction } = {}) {
		return { async fetch(request, env, ctx) {
			const corsResponse = handleCORS(request, corsOptions);
			if (corsResponse) return corsResponse;
			const bindingValue = env[binding];
			if (bindingValue == null || typeof bindingValue !== "object") throw new Error(`Could not find McpAgent binding for ${binding}. Did you update your wrangler configuration?`);
			if (!isDurableObjectNamespace(bindingValue)) throw new Error(`Invalid McpAgent binding for ${binding}. Make sure it's a Durable Object binding.`);
			const namespace = bindingValue;
			switch (transport) {
				case "streamable-http": return createStreamingHttpHandler(path, namespace, {
					corsOptions,
					jurisdiction
				})(request, ctx);
				case "sse": return createLegacySseHandler(path, namespace, {
					corsOptions,
					jurisdiction
				})(request, ctx);
				case "auto": return createAutoHandler(path, namespace, {
					corsOptions,
					jurisdiction
				})(request, ctx);
				default: return new Response("Invalid MCP transport mode. Only `streamable-http`, `sse`, or `auto` are allowed.", { status: 500 });
			}
		} };
	}
	/**
	* Legacy api
	**/
	static mount(path, opts = {}) {
		return McpAgent.serveSSE(path, opts);
	}
	static serveSSE(path, opts = {}) {
		return McpAgent.serve(path, {
			...opts,
			transport: "sse"
		});
	}
};
McpAgent.STREAM_REQS_KEY_PREFIX = "__mcp_stream_reqs__:";
//#endregion
export { DurableObjectEventStore, ElicitRequestSchema, MCP_SERVER_ID_MAX_LENGTH, McpAgent, RPCClientTransport, RPCServerTransport, RPC_DO_PREFIX, SSEEdgeClientTransport, StreamableHTTPEdgeClientTransport, WorkerTransport, createLegacyMcpHandler, createMcpHandler, experimental_createMcpHandler, getMcpAuthContext, normalizeServerId };

//# sourceMappingURL=index.js.map