gesso-framework 0.6.12 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # gesso-framework
2
2
 
3
+ ## 0.6.13
4
+
5
+ ### Patch Changes
6
+
7
+ - 0020afa: `mcpHandler` keeps sessions: `initialize` is answered with an `Mcp-Session-Id`, which remembers the client's `clientInfo`, and a new `around` option runs around every request with that caller (`McpCaller`), so an application can tell which agent asked. `DELETE` with the id ends a session. The bearer token is now compared in constant time.
8
+ - gesso-core@0.6.13
9
+
3
10
  ## 0.6.12
4
11
 
5
12
  ### Patch Changes
@@ -16,6 +16,14 @@ import { E as WorkerHandle, F as JsonSchema, a as resolveTarget, c as AgentResou
16
16
  * transport allows, and the GET stream for server-initiated messages
17
17
  * is declined with 405, which it also allows: nothing here sends one,
18
18
  * so the capabilities say neither `listChanged` nor `subscribe`.
19
+ *
20
+ * `mcpHandler` keeps sessions, as the transport describes: `initialize`
21
+ * is answered with an `Mcp-Session-Id`, the client sends it back on
22
+ * every request after, and `DELETE` with it ends the session. A session
23
+ * remembers the client's `clientInfo`, so an application can tell one
24
+ * agent from another while a request runs (`McpHandlerOptions.around`).
25
+ * A request without a session, or with one the server has forgotten, is
26
+ * still answered, as one from an unknown caller.
19
27
  */
20
28
  /** The protocol revisions this server speaks, newest first. */
21
29
  declare const MCP_PROTOCOL_VERSIONS: readonly ['2025-11-25', '2025-06-18', '2025-03-26'];
@@ -45,6 +53,21 @@ type JsonRpcResponse = {
45
53
  * with the JSON-RPC error that says what was wrong with it.
46
54
  */
47
55
  declare function handleMcpMessage(surface: AgentSurfaceLike, message: unknown, info?: McpServerInfo): Promise<JsonRpcResponse | null>;
56
+ /**
57
+ * Who sent a request: the session `initialize` began, and the client it
58
+ * said it was. Handed to `McpHandlerOptions.around` for each request.
59
+ */
60
+ interface McpCaller {
61
+ /** The session's id, as sent in `Mcp-Session-Id`. */
62
+ readonly session: string;
63
+ /** The order sessions began in since the handler was made, from 1: a short way to tell two of one client apart. */
64
+ readonly number: number;
65
+ /** What the client said it was in `initialize`'s `clientInfo`; an empty name when it said nothing. */
66
+ readonly client: {
67
+ readonly name: string;
68
+ readonly version: string;
69
+ };
70
+ }
48
71
  interface McpHandlerOptions extends McpServerInfo {
49
72
  /**
50
73
  * Origins a browser may call from. A request carrying any other
@@ -60,6 +83,17 @@ interface McpHandlerOptions extends McpServerInfo {
60
83
  * other than the person's own agents.
61
84
  */
62
85
  token?: string;
86
+ /**
87
+ * Runs around every request, told who sent it: the session and the
88
+ * client it belongs to, or null for a request outside any session. A
89
+ * place to keep the caller in an `AsyncLocalStorage`, say, so the
90
+ * commands a tool call runs can tell which agent asked:
91
+ *
92
+ * around: (caller, handle) => agents.run(caller, handle)
93
+ *
94
+ * It must call `handle` and return what it returns.
95
+ */
96
+ around?: (caller: McpCaller | null, handle: () => Promise<JsonRpcResponse | null>) => Promise<JsonRpcResponse | null>;
63
97
  }
64
98
  /**
65
99
  * The Streamable HTTP transport, as a `fetch` handler.
@@ -313,5 +347,5 @@ interface ApplicationAgentParts {
313
347
  */
314
348
  declare function serveApplicationAgent(port: MessagePort, parts: ApplicationAgentParts): () => void;
315
349
  //#endregion
316
- export { AGENT_PORT, type AgentConfirmation, type AgentPortRequest, type AgentPortResponse, type AgentResource, type AgentSurface, type AgentSurfaceLike, type AgentSurfaceOptions, type AgentTool, type AgentToolResult, type ApplicationAgentParts, DEV_AGENT_EVENTS, type DevAgentApp, type DevAgentHot, type DevAgentRequest, type DevAgentResponse, type JsonRpcResponse, MCP_PROTOCOL_VERSIONS, type McpHandlerOptions, type McpServerInfo, type ModelContextLike, type UiHost, UiRefs, type UiSurfaceOptions, type WebMcpOptions, agentSurface, combineSurfaces, confirmInWindow, connectDevAgent, connectWebMcp, handleMcpMessage, mcpHandler, outline, pageModelContext, registerWebMcpTools, remoteSurface, resolveTarget, resourceUri, serveAgentPort, serveApplicationAgent, uiSurface, validate };
350
+ export { AGENT_PORT, type AgentConfirmation, type AgentPortRequest, type AgentPortResponse, type AgentResource, type AgentSurface, type AgentSurfaceLike, type AgentSurfaceOptions, type AgentTool, type AgentToolResult, type ApplicationAgentParts, DEV_AGENT_EVENTS, type DevAgentApp, type DevAgentHot, type DevAgentRequest, type DevAgentResponse, type JsonRpcResponse, MCP_PROTOCOL_VERSIONS, type McpCaller, type McpHandlerOptions, type McpServerInfo, type ModelContextLike, type UiHost, UiRefs, type UiSurfaceOptions, type WebMcpOptions, agentSurface, combineSurfaces, confirmInWindow, connectDevAgent, connectWebMcp, handleMcpMessage, mcpHandler, outline, pageModelContext, registerWebMcpTools, remoteSurface, resolveTarget, resourceUri, serveAgentPort, serveApplicationAgent, uiSurface, validate };
317
351
  //# sourceMappingURL=index.d.ts.map
@@ -18,6 +18,14 @@ import { i as registerWebMcpTools, n as connectWebMcp, r as pageModelContext, t
18
18
  * transport allows, and the GET stream for server-initiated messages
19
19
  * is declined with 405, which it also allows: nothing here sends one,
20
20
  * so the capabilities say neither `listChanged` nor `subscribe`.
21
+ *
22
+ * `mcpHandler` keeps sessions, as the transport describes: `initialize`
23
+ * is answered with an `Mcp-Session-Id`, the client sends it back on
24
+ * every request after, and `DELETE` with it ends the session. A session
25
+ * remembers the client's `clientInfo`, so an application can tell one
26
+ * agent from another while a request runs (`McpHandlerOptions.around`).
27
+ * A request without a session, or with one the server has forgotten, is
28
+ * still answered, as one from an unknown caller.
21
29
  */
22
30
  /** The protocol revisions this server speaks, newest first. */
23
31
  const MCP_PROTOCOL_VERSIONS = [
@@ -80,6 +88,8 @@ async function handleMcpMessage(surface, message, info = {}) {
80
88
  default: return error(id, METHOD_NOT_FOUND, `This server does not implement ${message.method}.`);
81
89
  }
82
90
  }
91
+ /** The most sessions remembered at once; the oldest is forgotten first, and its requests are then from an unknown caller. */
92
+ const MAX_SESSIONS = 256;
83
93
  /**
84
94
  * The Streamable HTTP transport, as a `fetch` handler.
85
95
  *
@@ -88,15 +98,22 @@ async function handleMcpMessage(surface, message, info = {}) {
88
98
  * It answers every path; mount it where the server routes `/mcp`.
89
99
  */
90
100
  function mcpHandler(surface, options = {}) {
101
+ const sessions = /* @__PURE__ */ new Map();
102
+ let begun = 0;
91
103
  return async (request) => {
92
104
  const origin = request.headers.get("origin");
93
105
  if (origin !== null && !(options.allowedOrigins ?? []).includes(origin)) return text(403, `Requests from ${origin} are not allowed.`);
94
- if (options.token !== void 0 && request.headers.get("authorization") !== `Bearer ${options.token}`) return text(401, "This server needs a bearer token.");
106
+ if (options.token !== void 0 && !sameText(request.headers.get("authorization") ?? "", `Bearer ${options.token}`)) return text(401, "This server needs a bearer token.");
107
+ const sessionId = request.headers.get("mcp-session-id");
108
+ if (request.method === "DELETE" && sessionId !== null) {
109
+ sessions.delete(sessionId);
110
+ return new Response(null, { status: 204 });
111
+ }
95
112
  const version = request.headers.get("mcp-protocol-version");
96
113
  if (version !== null && !MCP_PROTOCOL_VERSIONS.includes(version)) return text(400, `Unsupported MCP protocol version ${version}.`);
97
114
  if (request.method !== "POST") return new Response(null, {
98
115
  status: 405,
99
- headers: { allow: "POST" }
116
+ headers: { allow: "POST, DELETE" }
100
117
  });
101
118
  let message;
102
119
  try {
@@ -104,10 +121,44 @@ function mcpHandler(surface, options = {}) {
104
121
  } catch {
105
122
  return json(400, error(null, PARSE_ERROR, "The body is not JSON."));
106
123
  }
107
- const response = await handleMcpMessage(surface, message, options);
108
- return response === null ? new Response(null, { status: 202 }) : json(200, response);
124
+ let started = null;
125
+ if (isRequest(message) && message.method === "initialize" && message.id !== void 0) {
126
+ const info = message.params?.clientInfo ?? {};
127
+ started = {
128
+ session: newSessionId(),
129
+ number: ++begun,
130
+ client: {
131
+ name: typeof info.name === "string" ? info.name : "",
132
+ version: typeof info.version === "string" ? info.version : ""
133
+ }
134
+ };
135
+ sessions.set(started.session, started);
136
+ if (sessions.size > MAX_SESSIONS) sessions.delete(sessions.keys().next().value);
137
+ }
138
+ const caller = started ?? (sessionId === null ? null : sessions.get(sessionId) ?? null);
139
+ const handle = () => handleMcpMessage(surface, message, options);
140
+ const response = await (options.around === void 0 ? handle() : options.around(caller, handle));
141
+ if (response === null) return new Response(null, { status: 202 });
142
+ const answered = json(200, response);
143
+ if (started !== null) answered.headers.set("mcp-session-id", started.session);
144
+ return answered;
109
145
  };
110
146
  }
147
+ /** A session id no one can guess: 128 random bits, as hex. */
148
+ function newSessionId() {
149
+ const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
150
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
151
+ }
152
+ /**
153
+ * Whether two strings are the same, taking as long however early they
154
+ * differ, so a token cannot be guessed a character at a time from how
155
+ * quickly it is refused.
156
+ */
157
+ function sameText(a, b) {
158
+ let differs = a.length ^ b.length;
159
+ for (let i = 0; i < b.length; i++) differs |= (a.charCodeAt(i % Math.max(a.length, 1)) || 0) ^ b.charCodeAt(i);
160
+ return differs === 0;
161
+ }
111
162
  function isRequest(message) {
112
163
  const candidate = message;
113
164
  return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) && candidate.jsonrpc === "2.0" && typeof candidate.method === "string";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/agent/mcp.ts","../../src/agent/dev.ts"],"sourcesContent":["import type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * An agent surface, served over the Model Context Protocol.\n *\n * Two layers. `handleMcpMessage` answers one JSON-RPC message and knows\n * nothing about how it arrived, which is all a stdio transport or a\n * relay needs. `mcpHandler` wraps it in MCP's Streamable HTTP\n * transport as a `fetch`-style function, `(Request) => Response`,\n * which is what `Bun.serve`, Deno, a service worker and every modern\n * Node server framework take, so the framework imports no server.\n *\n * What it implements is the part a channel needs: `initialize`, `ping`,\n * `tools/list`, `tools/call`, `resources/list` and `resources/read`.\n * Responses are plain JSON rather than an event stream, which the\n * transport allows, and the GET stream for server-initiated messages\n * is declined with 405, which it also allows: nothing here sends one,\n * so the capabilities say neither `listChanged` nor `subscribe`.\n */\n\n/** The protocol revisions this server speaks, newest first. */\nexport const MCP_PROTOCOL_VERSIONS = ['2025-11-25', '2025-06-18', '2025-03-26'] as const;\n\nexport interface McpServerInfo {\n /** The name a client shows for this server. Defaults to `gesso`. */\n name?: string;\n version?: string;\n /** Told to the agent on connecting: what this application is, in a sentence or two. */\n instructions?: string;\n}\n\ntype JsonRpcId = string | number;\n\ninterface JsonRpcRequest {\n readonly jsonrpc: '2.0';\n readonly id?: JsonRpcId;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\nexport type JsonRpcResponse =\n | { jsonrpc: '2.0'; id: JsonRpcId | null; result: unknown }\n | { jsonrpc: '2.0'; id: JsonRpcId | null; error: { code: number; message: string } };\n\nconst PARSE_ERROR = -32700;\nconst INVALID_REQUEST = -32600;\nconst METHOD_NOT_FOUND = -32601;\nconst INVALID_PARAMS = -32602;\n/** MCP's code for a resource that does not exist. */\nconst RESOURCE_NOT_FOUND = -32002;\n\n/**\n * Answers one JSON-RPC message. Returns null for a notification, which\n * takes no answer, and never throws: a malformed message is answered\n * with the JSON-RPC error that says what was wrong with it.\n */\nexport async function handleMcpMessage(\n surface: AgentSurfaceLike,\n message: unknown,\n info: McpServerInfo = {}\n): Promise<JsonRpcResponse | null> {\n if (!isRequest(message)) {\n return error(null, INVALID_REQUEST, 'Expected a JSON-RPC 2.0 request with a method.');\n }\n if (message.id === undefined) {\n // `notifications/initialized` and `notifications/cancelled` are the\n // ones a client sends. Neither needs anything from a server whose\n // every call answers before the next is read.\n return null;\n }\n const { id } = message;\n const params = message.params ?? {};\n switch (message.method) {\n case 'initialize': {\n const asked = params.protocolVersion;\n const version = MCP_PROTOCOL_VERSIONS.find(supported => supported === asked) ?? MCP_PROTOCOL_VERSIONS[0];\n return ok(id, {\n protocolVersion: version,\n capabilities: { tools: {}, resources: {} },\n serverInfo: { name: info.name ?? 'gesso', version: info.version ?? '0.0.0' },\n ...(info.instructions === undefined ? {} : { instructions: info.instructions })\n });\n }\n case 'ping':\n return ok(id, {});\n case 'tools/list':\n return ok(id, { tools: await surface.tools() });\n case 'tools/call': {\n if (typeof params.name !== 'string') {\n return error(id, INVALID_PARAMS, 'tools/call needs the name of a tool.');\n }\n const args = params.arguments;\n if (args !== undefined && (args === null || typeof args !== 'object' || Array.isArray(args))) {\n return error(id, INVALID_PARAMS, 'tools/call arguments must be an object.');\n }\n return ok(id, await surface.call(params.name, args as Record<string, unknown> | undefined));\n }\n case 'resources/list':\n return ok(id, { resources: await surface.resources() });\n case 'resources/templates/list':\n return ok(id, { resourceTemplates: [] });\n case 'resources/read': {\n const uri = params.uri;\n const view = typeof uri === 'string' ? await surface.read(uri) : undefined;\n if (typeof uri !== 'string' || view === undefined) {\n return error(id, RESOURCE_NOT_FOUND, `No resource at ${String(uri)}.`);\n }\n return ok(id, { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(view) }] });\n }\n default:\n return error(id, METHOD_NOT_FOUND, `This server does not implement ${message.method}.`);\n }\n}\n\nexport interface McpHandlerOptions extends McpServerInfo {\n /**\n * Origins a browser may call from. A request carrying any other\n * `Origin` is refused, as the transport requires: without it, a web\n * page the person happens to have open could reach a server bound to\n * their own machine and send commands to their application. Agents\n * that are not browsers send no `Origin` and are unaffected.\n */\n allowedOrigins?: readonly string[];\n /**\n * A secret every request must carry as `Authorization: Bearer\n * <token>`. Worth setting whenever the port is reachable by anything\n * other than the person's own agents.\n */\n token?: string;\n}\n\n/**\n * The Streamable HTTP transport, as a `fetch` handler.\n *\n * Bun.serve({ hostname: '127.0.0.1', port: 7310, fetch: mcpHandler(surface, { name: 'notes' }) });\n *\n * It answers every path; mount it where the server routes `/mcp`.\n */\nexport function mcpHandler(\n surface: AgentSurfaceLike,\n options: McpHandlerOptions = {}\n): (request: Request) => Promise<Response> {\n return async request => {\n const origin = request.headers.get('origin');\n if (origin !== null && !(options.allowedOrigins ?? []).includes(origin)) {\n return text(403, `Requests from ${origin} are not allowed.`);\n }\n if (options.token !== undefined && request.headers.get('authorization') !== `Bearer ${options.token}`) {\n return text(401, 'This server needs a bearer token.');\n }\n const version = request.headers.get('mcp-protocol-version');\n if (version !== null && !(MCP_PROTOCOL_VERSIONS as readonly string[]).includes(version)) {\n return text(400, `Unsupported MCP protocol version ${version}.`);\n }\n if (request.method !== 'POST') {\n // GET is the optional stream for messages the server starts, and\n // DELETE ends a session. There are no such messages and no\n // sessions, and 405 is how the transport says so.\n return new Response(null, { status: 405, headers: { allow: 'POST' } });\n }\n let message: unknown;\n try {\n message = await request.json();\n } catch {\n return json(400, error(null, PARSE_ERROR, 'The body is not JSON.'));\n }\n const response = await handleMcpMessage(surface, message, options);\n return response === null ? new Response(null, { status: 202 }) : json(200, response);\n };\n}\n\nfunction isRequest(message: unknown): message is JsonRpcRequest {\n const candidate = message as Partial<JsonRpcRequest> | null;\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n !Array.isArray(candidate) &&\n candidate.jsonrpc === '2.0' &&\n typeof candidate.method === 'string'\n );\n}\n\nfunction ok(id: JsonRpcId, result: unknown): JsonRpcResponse {\n return { jsonrpc: '2.0', id, result };\n}\n\nfunction error(id: JsonRpcId | null, code: number, message: string): JsonRpcResponse {\n return { jsonrpc: '2.0', id, error: { code, message } };\n}\n\nfunction json(status: number, body: unknown): Response {\n return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });\n}\n\nfunction text(status: number, body: string): Response {\n return new Response(body, { status, headers: { 'content-type': 'text/plain; charset=utf-8' } });\n}\n","import { handleMcpMessage, type JsonRpcResponse, type McpServerInfo } from './mcp';\nimport { AGENT_PORT, combineSurfaces, remoteSurface } from './remote';\nimport { confirmInWindow } from './webmcp';\nimport type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * The page's half of the development agent bridge.\n *\n * `gesso-vite-plugin` serves MCP at `/__gesso/mcp` on the dev server,\n * but the channels are in the page's workers, and a server cannot\n * reach a worker. The page can, and it already holds a socket to the\n * server: Vite's HMR connection. So the server relays each JSON-RPC\n * message down that socket, this answers it against the render\n * worker's agent port, and the answer goes back up the same way.\n *\n * MCP is spoken here, in the page, rather than on the server, so the\n * server needs nothing from the framework and the workers need nothing\n * from MCP. The plugin injects the call in a dev server only; a build\n * carries no reference to this module.\n */\n\n/** The part of `import.meta.hot` this uses. */\nexport interface DevAgentHot {\n send(event: string, data?: unknown): void;\n on(event: string, listener: (data: never) => void): void;\n}\n\n/** What the bridge needs of an application: a way to reach its render worker. */\nexport interface DevAgentApp {\n openRenderPort(key: string): MessagePort | undefined;\n}\n\n/** Server to page. */\nexport interface DevAgentRequest {\n readonly id: number;\n readonly message: unknown;\n}\n\n/** Page to server. */\nexport interface DevAgentResponse {\n readonly id: number;\n readonly response: JsonRpcResponse | null;\n}\n\n/** The HMR events the bridge speaks. The plugin's half uses the same names. */\nexport const DEV_AGENT_EVENTS = {\n ready: 'gesso:agent:ready',\n request: 'gesso:agent:request',\n response: 'gesso:agent:response'\n} as const;\n\nexport function connectDevAgent(app: DevAgentApp, hot: DevAgentHot, info: McpServerInfo = {}): void {\n let surface: AgentSurfaceLike | undefined;\n /**\n * Opened on the first request, because the render worker does not\n * exist until the app mounts, and an agent may well ask first.\n */\n const reach = (): AgentSurfaceLike => {\n if (surface !== undefined) {\n return surface;\n }\n const port = app.openRenderPort(AGENT_PORT);\n if (port === undefined) {\n return combineSurfaces([]);\n }\n // A command marked `@confirm` is put to the person here, in the\n // page they are looking at. A browser's own dialog: in development\n // that is enough, and it cannot be mistaken for the application's.\n surface = remoteSurface(port, { confirm: confirmInWindow });\n return surface;\n };\n hot.on(DEV_AGENT_EVENTS.request, (request: DevAgentRequest) => {\n void handleMcpMessage(reach(), request.message, {\n name: info.name ?? (document.title || 'gesso'),\n ...info\n }).then(response => hot.send(DEV_AGENT_EVENTS.response, { id: request.id, response } satisfies DevAgentResponse));\n });\n const announce = () => hot.send(DEV_AGENT_EVENTS.ready, { title: document.title, url: location.href });\n announce();\n // A restarted dev server is a new one that has never heard of this\n // page, and the page reconnects to it without reloading. Saying so\n // again on every connection is what keeps an agent from being told\n // no page is open while one plainly is.\n hot.on('vite:ws:connect', announce);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/agent/mcp.ts","../../src/agent/dev.ts"],"sourcesContent":["import type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * An agent surface, served over the Model Context Protocol.\n *\n * Two layers. `handleMcpMessage` answers one JSON-RPC message and knows\n * nothing about how it arrived, which is all a stdio transport or a\n * relay needs. `mcpHandler` wraps it in MCP's Streamable HTTP\n * transport as a `fetch`-style function, `(Request) => Response`,\n * which is what `Bun.serve`, Deno, a service worker and every modern\n * Node server framework take, so the framework imports no server.\n *\n * What it implements is the part a channel needs: `initialize`, `ping`,\n * `tools/list`, `tools/call`, `resources/list` and `resources/read`.\n * Responses are plain JSON rather than an event stream, which the\n * transport allows, and the GET stream for server-initiated messages\n * is declined with 405, which it also allows: nothing here sends one,\n * so the capabilities say neither `listChanged` nor `subscribe`.\n *\n * `mcpHandler` keeps sessions, as the transport describes: `initialize`\n * is answered with an `Mcp-Session-Id`, the client sends it back on\n * every request after, and `DELETE` with it ends the session. A session\n * remembers the client's `clientInfo`, so an application can tell one\n * agent from another while a request runs (`McpHandlerOptions.around`).\n * A request without a session, or with one the server has forgotten, is\n * still answered, as one from an unknown caller.\n */\n\n/** The protocol revisions this server speaks, newest first. */\nexport const MCP_PROTOCOL_VERSIONS = ['2025-11-25', '2025-06-18', '2025-03-26'] as const;\n\nexport interface McpServerInfo {\n /** The name a client shows for this server. Defaults to `gesso`. */\n name?: string;\n version?: string;\n /** Told to the agent on connecting: what this application is, in a sentence or two. */\n instructions?: string;\n}\n\ntype JsonRpcId = string | number;\n\ninterface JsonRpcRequest {\n readonly jsonrpc: '2.0';\n readonly id?: JsonRpcId;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\nexport type JsonRpcResponse =\n | { jsonrpc: '2.0'; id: JsonRpcId | null; result: unknown }\n | { jsonrpc: '2.0'; id: JsonRpcId | null; error: { code: number; message: string } };\n\nconst PARSE_ERROR = -32700;\nconst INVALID_REQUEST = -32600;\nconst METHOD_NOT_FOUND = -32601;\nconst INVALID_PARAMS = -32602;\n/** MCP's code for a resource that does not exist. */\nconst RESOURCE_NOT_FOUND = -32002;\n\n/**\n * Answers one JSON-RPC message. Returns null for a notification, which\n * takes no answer, and never throws: a malformed message is answered\n * with the JSON-RPC error that says what was wrong with it.\n */\nexport async function handleMcpMessage(\n surface: AgentSurfaceLike,\n message: unknown,\n info: McpServerInfo = {}\n): Promise<JsonRpcResponse | null> {\n if (!isRequest(message)) {\n return error(null, INVALID_REQUEST, 'Expected a JSON-RPC 2.0 request with a method.');\n }\n if (message.id === undefined) {\n // `notifications/initialized` and `notifications/cancelled` are the\n // ones a client sends. Neither needs anything from a server whose\n // every call answers before the next is read.\n return null;\n }\n const { id } = message;\n const params = message.params ?? {};\n switch (message.method) {\n case 'initialize': {\n const asked = params.protocolVersion;\n const version = MCP_PROTOCOL_VERSIONS.find(supported => supported === asked) ?? MCP_PROTOCOL_VERSIONS[0];\n return ok(id, {\n protocolVersion: version,\n capabilities: { tools: {}, resources: {} },\n serverInfo: { name: info.name ?? 'gesso', version: info.version ?? '0.0.0' },\n ...(info.instructions === undefined ? {} : { instructions: info.instructions })\n });\n }\n case 'ping':\n return ok(id, {});\n case 'tools/list':\n return ok(id, { tools: await surface.tools() });\n case 'tools/call': {\n if (typeof params.name !== 'string') {\n return error(id, INVALID_PARAMS, 'tools/call needs the name of a tool.');\n }\n const args = params.arguments;\n if (args !== undefined && (args === null || typeof args !== 'object' || Array.isArray(args))) {\n return error(id, INVALID_PARAMS, 'tools/call arguments must be an object.');\n }\n return ok(id, await surface.call(params.name, args as Record<string, unknown> | undefined));\n }\n case 'resources/list':\n return ok(id, { resources: await surface.resources() });\n case 'resources/templates/list':\n return ok(id, { resourceTemplates: [] });\n case 'resources/read': {\n const uri = params.uri;\n const view = typeof uri === 'string' ? await surface.read(uri) : undefined;\n if (typeof uri !== 'string' || view === undefined) {\n return error(id, RESOURCE_NOT_FOUND, `No resource at ${String(uri)}.`);\n }\n return ok(id, { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(view) }] });\n }\n default:\n return error(id, METHOD_NOT_FOUND, `This server does not implement ${message.method}.`);\n }\n}\n\n/**\n * Who sent a request: the session `initialize` began, and the client it\n * said it was. Handed to `McpHandlerOptions.around` for each request.\n */\nexport interface McpCaller {\n /** The session's id, as sent in `Mcp-Session-Id`. */\n readonly session: string;\n /** The order sessions began in since the handler was made, from 1: a short way to tell two of one client apart. */\n readonly number: number;\n /** What the client said it was in `initialize`'s `clientInfo`; an empty name when it said nothing. */\n readonly client: { readonly name: string; readonly version: string };\n}\n\n/** The most sessions remembered at once; the oldest is forgotten first, and its requests are then from an unknown caller. */\nconst MAX_SESSIONS = 256;\n\nexport interface McpHandlerOptions extends McpServerInfo {\n /**\n * Origins a browser may call from. A request carrying any other\n * `Origin` is refused, as the transport requires: without it, a web\n * page the person happens to have open could reach a server bound to\n * their own machine and send commands to their application. Agents\n * that are not browsers send no `Origin` and are unaffected.\n */\n allowedOrigins?: readonly string[];\n /**\n * A secret every request must carry as `Authorization: Bearer\n * <token>`. Worth setting whenever the port is reachable by anything\n * other than the person's own agents.\n */\n token?: string;\n /**\n * Runs around every request, told who sent it: the session and the\n * client it belongs to, or null for a request outside any session. A\n * place to keep the caller in an `AsyncLocalStorage`, say, so the\n * commands a tool call runs can tell which agent asked:\n *\n * around: (caller, handle) => agents.run(caller, handle)\n *\n * It must call `handle` and return what it returns.\n */\n around?: (caller: McpCaller | null, handle: () => Promise<JsonRpcResponse | null>) => Promise<JsonRpcResponse | null>;\n}\n\n/**\n * The Streamable HTTP transport, as a `fetch` handler.\n *\n * Bun.serve({ hostname: '127.0.0.1', port: 7310, fetch: mcpHandler(surface, { name: 'notes' }) });\n *\n * It answers every path; mount it where the server routes `/mcp`.\n */\nexport function mcpHandler(\n surface: AgentSurfaceLike,\n options: McpHandlerOptions = {}\n): (request: Request) => Promise<Response> {\n const sessions = new Map<string, McpCaller>();\n let begun = 0;\n return async request => {\n const origin = request.headers.get('origin');\n if (origin !== null && !(options.allowedOrigins ?? []).includes(origin)) {\n return text(403, `Requests from ${origin} are not allowed.`);\n }\n if (\n options.token !== undefined &&\n !sameText(request.headers.get('authorization') ?? '', `Bearer ${options.token}`)\n ) {\n return text(401, 'This server needs a bearer token.');\n }\n const sessionId = request.headers.get('mcp-session-id');\n if (request.method === 'DELETE' && sessionId !== null) {\n sessions.delete(sessionId);\n return new Response(null, { status: 204 });\n }\n const version = request.headers.get('mcp-protocol-version');\n if (version !== null && !(MCP_PROTOCOL_VERSIONS as readonly string[]).includes(version)) {\n return text(400, `Unsupported MCP protocol version ${version}.`);\n }\n if (request.method !== 'POST') {\n // GET is the optional stream for messages the server starts. There\n // are no such messages, and 405 is how the transport says so.\n return new Response(null, { status: 405, headers: { allow: 'POST, DELETE' } });\n }\n let message: unknown;\n try {\n message = await request.json();\n } catch {\n return json(400, error(null, PARSE_ERROR, 'The body is not JSON.'));\n }\n // `initialize` begins a session; anything else belongs to the one it names, if it is known.\n let started: McpCaller | null = null;\n if (isRequest(message) && message.method === 'initialize' && message.id !== undefined) {\n const info = (message.params?.clientInfo ?? {}) as { name?: unknown; version?: unknown };\n started = {\n session: newSessionId(),\n number: ++begun,\n client: {\n name: typeof info.name === 'string' ? info.name : '',\n version: typeof info.version === 'string' ? info.version : ''\n }\n };\n sessions.set(started.session, started);\n if (sessions.size > MAX_SESSIONS) sessions.delete(sessions.keys().next().value!);\n }\n const caller = started ?? (sessionId === null ? null : (sessions.get(sessionId) ?? null));\n const handle = () => handleMcpMessage(surface, message, options);\n const response = await (options.around === undefined ? handle() : options.around(caller, handle));\n if (response === null) return new Response(null, { status: 202 });\n const answered = json(200, response);\n if (started !== null) answered.headers.set('mcp-session-id', started.session);\n return answered;\n };\n}\n\n/** A session id no one can guess: 128 random bits, as hex. */\nfunction newSessionId(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Whether two strings are the same, taking as long however early they\n * differ, so a token cannot be guessed a character at a time from how\n * quickly it is refused.\n */\nfunction sameText(a: string, b: string): boolean {\n let differs = a.length ^ b.length;\n for (let i = 0; i < b.length; i++) differs |= (a.charCodeAt(i % Math.max(a.length, 1)) || 0) ^ b.charCodeAt(i);\n return differs === 0;\n}\n\nfunction isRequest(message: unknown): message is JsonRpcRequest {\n const candidate = message as Partial<JsonRpcRequest> | null;\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n !Array.isArray(candidate) &&\n candidate.jsonrpc === '2.0' &&\n typeof candidate.method === 'string'\n );\n}\n\nfunction ok(id: JsonRpcId, result: unknown): JsonRpcResponse {\n return { jsonrpc: '2.0', id, result };\n}\n\nfunction error(id: JsonRpcId | null, code: number, message: string): JsonRpcResponse {\n return { jsonrpc: '2.0', id, error: { code, message } };\n}\n\nfunction json(status: number, body: unknown): Response {\n return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });\n}\n\nfunction text(status: number, body: string): Response {\n return new Response(body, { status, headers: { 'content-type': 'text/plain; charset=utf-8' } });\n}\n","import { handleMcpMessage, type JsonRpcResponse, type McpServerInfo } from './mcp';\nimport { AGENT_PORT, combineSurfaces, remoteSurface } from './remote';\nimport { confirmInWindow } from './webmcp';\nimport type { AgentSurfaceLike } from './AgentSurface';\n\n/**\n * The page's half of the development agent bridge.\n *\n * `gesso-vite-plugin` serves MCP at `/__gesso/mcp` on the dev server,\n * but the channels are in the page's workers, and a server cannot\n * reach a worker. The page can, and it already holds a socket to the\n * server: Vite's HMR connection. So the server relays each JSON-RPC\n * message down that socket, this answers it against the render\n * worker's agent port, and the answer goes back up the same way.\n *\n * MCP is spoken here, in the page, rather than on the server, so the\n * server needs nothing from the framework and the workers need nothing\n * from MCP. The plugin injects the call in a dev server only; a build\n * carries no reference to this module.\n */\n\n/** The part of `import.meta.hot` this uses. */\nexport interface DevAgentHot {\n send(event: string, data?: unknown): void;\n on(event: string, listener: (data: never) => void): void;\n}\n\n/** What the bridge needs of an application: a way to reach its render worker. */\nexport interface DevAgentApp {\n openRenderPort(key: string): MessagePort | undefined;\n}\n\n/** Server to page. */\nexport interface DevAgentRequest {\n readonly id: number;\n readonly message: unknown;\n}\n\n/** Page to server. */\nexport interface DevAgentResponse {\n readonly id: number;\n readonly response: JsonRpcResponse | null;\n}\n\n/** The HMR events the bridge speaks. The plugin's half uses the same names. */\nexport const DEV_AGENT_EVENTS = {\n ready: 'gesso:agent:ready',\n request: 'gesso:agent:request',\n response: 'gesso:agent:response'\n} as const;\n\nexport function connectDevAgent(app: DevAgentApp, hot: DevAgentHot, info: McpServerInfo = {}): void {\n let surface: AgentSurfaceLike | undefined;\n /**\n * Opened on the first request, because the render worker does not\n * exist until the app mounts, and an agent may well ask first.\n */\n const reach = (): AgentSurfaceLike => {\n if (surface !== undefined) {\n return surface;\n }\n const port = app.openRenderPort(AGENT_PORT);\n if (port === undefined) {\n return combineSurfaces([]);\n }\n // A command marked `@confirm` is put to the person here, in the\n // page they are looking at. A browser's own dialog: in development\n // that is enough, and it cannot be mistaken for the application's.\n surface = remoteSurface(port, { confirm: confirmInWindow });\n return surface;\n };\n hot.on(DEV_AGENT_EVENTS.request, (request: DevAgentRequest) => {\n void handleMcpMessage(reach(), request.message, {\n name: info.name ?? (document.title || 'gesso'),\n ...info\n }).then(response => hot.send(DEV_AGENT_EVENTS.response, { id: request.id, response } satisfies DevAgentResponse));\n });\n const announce = () => hot.send(DEV_AGENT_EVENTS.ready, { title: document.title, url: location.href });\n announce();\n // A restarted dev server is a new one that has never heard of this\n // page, and the page reconnects to it without reloading. Saying so\n // again on every connection is what keeps an agent from being told\n // no page is open while one plainly is.\n hot.on('vite:ws:connect', announce);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gesso-framework",
3
- "version": "0.6.12",
3
+ "version": "0.6.13",
4
4
  "description": "SwiftUI and Compose, for the web: declarative components, stores and a frame runtime that keeps the whole interface off the main thread.",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Baker",
@@ -69,7 +69,7 @@
69
69
  "LICENSE"
70
70
  ],
71
71
  "dependencies": {
72
- "gesso-core": "^0.6.12"
72
+ "gesso-core": "^0.6.13"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "rxjs": "^7.8.2"