// vendored from atlibs-asyncai 94afa33 modules/asyncai/mcp-stub.mjs // Test MCP servers: mcpServer(tools) answers one JSON-RPC message (the in-process hook's contract); // streamableHttp(server) serves it as a Streamable HTTP endpoint for ctx.http.fetch stubs. /** tools: { name: (arguments) => CallToolResult }. Returns (message) => response, or null for a notification. */ export function mcpServer(tools, { version, pageSize = Infinity } = {}) { const names = Object.keys(tools); const ok = (id, result) => ({ jsonrpc: "2.0", id, result }); const fail = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } }); return (message) => { const { id, method, params = {} } = message; if (id === undefined) return null; if (method === "initialize") return ok(id, { protocolVersion: version ?? params.protocolVersion, capabilities: { tools: {} }, serverInfo: { name: "stub", version: "0" } }); if (method === "ping") return ok(id, {}); if (method === "tools/list") { const from = Number(params.cursor ?? 0); const page = names.slice(from, from + pageSize).map((name) => ({ name, description: name, inputSchema: { type: "object" } })); return ok(id, { tools: page, ...(from + pageSize < names.length && { nextCursor: String(from + pageSize) }) }); } if (method === "tools/call") return tools[params.name] ? ok(id, tools[params.name](params.arguments ?? {})) : fail(id, -32602, `unknown tool ${params.name}`); return fail(id, -32601, `no method ${method}`); }; } /** server as a Streamable HTTP endpoint: { handle(init) -> Response, seen: the requests }; sse answers as an event stream. */ export function streamableHttp(server, { sse = false, session } = {}) { const seen = []; const handle = async (init) => { const message = JSON.parse(init.body); const h = init.headers; seen.push({ method: message.method, params: message.params, session: h["mcp-session-id"], version: h["mcp-protocol-version"], accept: h.accept }); const answer = server(message); if (message.id === undefined) return new Response(null, { status: 202 }); const headers = { ...(message.method === "initialize" && session && { "mcp-session-id": session }) }; if (!sse) return Response.json(answer, { headers }); return new Response(`event: message\ndata: ${JSON.stringify(answer)}\n\n`, { headers: { ...headers, "content-type": "text/event-stream" } }); }; return { handle, seen }; } /** handle as a host scheme fetcher (runModule's `fetchers`): { method, url, headers, body } -> { status, headers, body }, * the headers cased as a Go server sends them (the guest agent's); `urls` gets each request's URL. */ export function fetcher(handle) { const urls = []; const fetch = async (request) => { urls.push(request.url); const response = await handle(request); const headers = Object.fromEntries([...response.headers].map(([k, v]) => [k.replace(/(^|-)[a-z]/g, (c) => c.toUpperCase()), v])); return { status: response.status, headers, body: await response.text() }; }; return { fetch, urls }; } /** A reason to skip while this atlibs core routes no webrtc:// URL to a host fetcher (contract v2.2 section 3), else false. */ export async function noFetchers(runModule, ctx, mod) { const probe = { ...ctx, http: { fetch: async () => Response.json({ content: [], stop_reason: "end_turn" }) } }; const input = { prompt: "-", endpoint: "https://inference.test", mcp: [{ name: "vm", url: "webrtc:///probe/shell/mcp" }] }; const options = { http: { "https://inference.test": {}, "webrtc://*": {} }, fetchers: { "webrtc:": fetcher(streamableHttp(mcpServer({})).handle).fetch } }; const ran = await runModule(probe, mod, "run", input, async () => {}, options).catch((e) => ({ error: e.message })); const server = ran.result?.servers?.[0]; return server?.phase !== "ready" && `atlibs core routes no webrtc:// URL to a host fetcher yet (${server?.error ?? ran.error})`; }