@particle-academy/agent-integrations 0.2.4

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.
Files changed (49) hide show
  1. package/README.md +131 -0
  2. package/dist/bridges/flow.d.cts +72 -0
  3. package/dist/bridges/flow.d.ts +72 -0
  4. package/dist/bridges/whiteboard.d.cts +40 -0
  5. package/dist/bridges/whiteboard.d.ts +40 -0
  6. package/dist/bridges-flow.cjs +330 -0
  7. package/dist/bridges-flow.cjs.map +1 -0
  8. package/dist/bridges-flow.js +4 -0
  9. package/dist/bridges-flow.js.map +1 -0
  10. package/dist/bridges-whiteboard.cjs +409 -0
  11. package/dist/bridges-whiteboard.cjs.map +1 -0
  12. package/dist/bridges-whiteboard.js +4 -0
  13. package/dist/bridges-whiteboard.js.map +1 -0
  14. package/dist/chunk-2VOQJKSU.js +320 -0
  15. package/dist/chunk-2VOQJKSU.js.map +1 -0
  16. package/dist/chunk-5ZUHNNLR.js +398 -0
  17. package/dist/chunk-5ZUHNNLR.js.map +1 -0
  18. package/dist/chunk-6LTKCNLF.js +68 -0
  19. package/dist/chunk-6LTKCNLF.js.map +1 -0
  20. package/dist/chunk-FLEOQUKF.js +157 -0
  21. package/dist/chunk-FLEOQUKF.js.map +1 -0
  22. package/dist/chunk-QGCF7YKW.js +130 -0
  23. package/dist/chunk-QGCF7YKW.js.map +1 -0
  24. package/dist/index.cjs +1632 -0
  25. package/dist/index.cjs.map +1 -0
  26. package/dist/index.d.cts +155 -0
  27. package/dist/index.d.ts +155 -0
  28. package/dist/index.js +567 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/mcp/index.d.cts +73 -0
  31. package/dist/mcp/index.d.ts +73 -0
  32. package/dist/mcp.cjs +210 -0
  33. package/dist/mcp.cjs.map +1 -0
  34. package/dist/mcp.js +4 -0
  35. package/dist/mcp.js.map +1 -0
  36. package/dist/server-Bv985us3.d.cts +173 -0
  37. package/dist/server-Bv985us3.d.ts +173 -0
  38. package/dist/sharing/index.d.cts +89 -0
  39. package/dist/sharing/index.d.ts +89 -0
  40. package/dist/sharing.cjs +166 -0
  41. package/dist/sharing.cjs.map +1 -0
  42. package/dist/sharing.js +3 -0
  43. package/dist/sharing.js.map +1 -0
  44. package/dist/styles.css +331 -0
  45. package/dist/styles.css.map +1 -0
  46. package/dist/types-CRPA_D0z.d.ts +18 -0
  47. package/dist/types-DR5AS6Rd.d.cts +18 -0
  48. package/docs/relay-protocol.md +57 -0
  49. package/package.json +61 -0
@@ -0,0 +1,157 @@
1
+ // src/sharing/token.ts
2
+ var TOKEN_BYTES = 24;
3
+ function createSessionDescriptor() {
4
+ const id = randomId(8);
5
+ const token = randomToken();
6
+ return { id, token, display: token.slice(0, 8) };
7
+ }
8
+ function describeSession(id, token) {
9
+ return { id, token, display: token.slice(0, 8) };
10
+ }
11
+ function buildShareUrl(descriptor, baseUrl = typeof window !== "undefined" ? window.location.href.split("?")[0] : "") {
12
+ const u = new URL(baseUrl);
13
+ u.searchParams.set("session", descriptor.id);
14
+ u.searchParams.set("token", descriptor.token);
15
+ return u.toString();
16
+ }
17
+ function buildShareConfig(descriptor, transport = "broadcast-channel") {
18
+ return {
19
+ name: `whiteboard-${descriptor.id}`,
20
+ transport,
21
+ session: descriptor.id,
22
+ token: descriptor.token,
23
+ channel: `fai:share:${descriptor.id}`,
24
+ protocol_version: "2025-06-18"
25
+ };
26
+ }
27
+ function readSessionFromUrl() {
28
+ if (typeof window === "undefined") return null;
29
+ const params = new URL(window.location.href).searchParams;
30
+ const id = params.get("session");
31
+ const token = params.get("token");
32
+ if (!id || !token) return null;
33
+ return describeSession(id, token);
34
+ }
35
+ function randomToken() {
36
+ const bytes = new Uint8Array(TOKEN_BYTES);
37
+ crypto.getRandomValues(bytes);
38
+ return base64Url(bytes);
39
+ }
40
+ function randomId(len) {
41
+ const bytes = new Uint8Array(Math.ceil(len * 3 / 4));
42
+ crypto.getRandomValues(bytes);
43
+ return base64Url(bytes).slice(0, len);
44
+ }
45
+ function base64Url(bytes) {
46
+ let s = "";
47
+ for (const b of bytes) s += String.fromCharCode(b);
48
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
49
+ }
50
+ function constantTimeEqual(a, b) {
51
+ if (a.length !== b.length) return false;
52
+ let diff = 0;
53
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
54
+ return diff === 0;
55
+ }
56
+
57
+ // src/sharing/sse-relay.ts
58
+ var SseRelayTransport = class {
59
+ constructor(options) {
60
+ this.sendQueue = [];
61
+ this.connected = false;
62
+ this.listeners = /* @__PURE__ */ new Set();
63
+ this.state = "idle";
64
+ this.opts = options;
65
+ this.expectedToken = options.token;
66
+ }
67
+ bindServer(server) {
68
+ this.server = server;
69
+ }
70
+ /** Open the SSE channel. Idempotent. */
71
+ start() {
72
+ if (this.connected || typeof window === "undefined") return;
73
+ const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;
74
+ this.setState("connecting");
75
+ const es = new EventSource(url, { withCredentials: false });
76
+ this.es = es;
77
+ es.addEventListener("open", () => {
78
+ this.connected = true;
79
+ this.setState("open");
80
+ const queued = this.sendQueue.splice(0);
81
+ for (const msg of queued) this.postOut(msg);
82
+ });
83
+ es.addEventListener("mcp", (ev) => {
84
+ const raw = ev.data;
85
+ this.handleInbound(raw);
86
+ });
87
+ es.addEventListener("error", () => {
88
+ this.setState("error");
89
+ });
90
+ }
91
+ send(message) {
92
+ if (!this.connected) {
93
+ this.sendQueue.push(message);
94
+ return;
95
+ }
96
+ this.postOut(message);
97
+ }
98
+ close() {
99
+ this.es?.close();
100
+ this.es = void 0;
101
+ this.connected = false;
102
+ this.setState("closed");
103
+ }
104
+ onStateChange(listener) {
105
+ this.listeners.add(listener);
106
+ listener(this.state);
107
+ return () => this.listeners.delete(listener);
108
+ }
109
+ /**
110
+ * For relays that wrap each frame with auth metadata: hosts can call this
111
+ * directly when a frame arrives via a non-SSE path. The transport will
112
+ * dispatch it to the bound server.
113
+ */
114
+ async deliverFromRemote(payload, token) {
115
+ if (token !== void 0 && !constantTimeEqual(token, this.expectedToken)) return;
116
+ if (!this.server) throw new Error("SseRelayTransport has no bound server");
117
+ const message = typeof payload === "string" ? JSON.parse(payload) : payload;
118
+ await this.server.receive(this, message);
119
+ }
120
+ async postOut(message) {
121
+ const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;
122
+ const f = this.opts.fetch ?? fetch;
123
+ try {
124
+ await f(url, {
125
+ method: "POST",
126
+ headers: { "content-type": "application/json", "accept": "application/json" },
127
+ body: JSON.stringify(message)
128
+ });
129
+ } catch {
130
+ }
131
+ }
132
+ async handleInbound(raw) {
133
+ if (!this.server) return;
134
+ let message;
135
+ try {
136
+ message = JSON.parse(raw);
137
+ } catch {
138
+ return;
139
+ }
140
+ await this.server.receive(this, message);
141
+ }
142
+ setState(state) {
143
+ this.state = state;
144
+ for (const l of this.listeners) l(state);
145
+ }
146
+ };
147
+ function attachSseRelay(server, options) {
148
+ const transport = new SseRelayTransport(options);
149
+ transport.bindServer(server);
150
+ server.attach(transport);
151
+ transport.start();
152
+ return transport;
153
+ }
154
+
155
+ export { SseRelayTransport, attachSseRelay, buildShareConfig, buildShareUrl, constantTimeEqual, createSessionDescriptor, describeSession, readSessionFromUrl };
156
+ //# sourceMappingURL=chunk-FLEOQUKF.js.map
157
+ //# sourceMappingURL=chunk-FLEOQUKF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sharing/token.ts","../src/sharing/sse-relay.ts"],"names":[],"mappings":"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","file":"chunk-FLEOQUKF.js","sourcesContent":["/**\n * Session-token utilities. The token is a high-entropy secret; possession\n * grants read/write on the session. We don't HMAC frames — frames carry\n * the token directly (which is fine for in-process / same-origin / TLS\n * transports). For lower-trust transports, host apps can layer signing\n * on top of the BroadcastChannelTransport.\n */\n\nconst TOKEN_BYTES = 24; // 192 bits, base64url-encoded → 32 chars\n\nexport type SessionDescriptor = {\n /** Stable session identifier. Channel name = `fai:share:${id}`. */\n id: string;\n /** Secret token. Treat as a password — anyone with it can read/write. */\n token: string;\n /** Pretty hash for display (first 8 chars of token). */\n display: string;\n};\n\nexport function createSessionDescriptor(): SessionDescriptor {\n const id = randomId(8);\n const token = randomToken();\n return { id, token, display: token.slice(0, 8) };\n}\n\nexport function describeSession(id: string, token: string): SessionDescriptor {\n return { id, token, display: token.slice(0, 8) };\n}\n\n/** Build the shareable URL for the current page (preserves path, adds session+token). */\nexport function buildShareUrl(\n descriptor: SessionDescriptor,\n baseUrl: string = typeof window !== \"undefined\" ? window.location.href.split(\"?\")[0] : \"\",\n): string {\n const u = new URL(baseUrl);\n u.searchParams.set(\"session\", descriptor.id);\n u.searchParams.set(\"token\", descriptor.token);\n return u.toString();\n}\n\n/** Build the JSON config form (suitable for Claude Desktop / Cline / etc.). */\nexport function buildShareConfig(descriptor: SessionDescriptor, transport = \"broadcast-channel\") {\n return {\n name: `whiteboard-${descriptor.id}`,\n transport,\n session: descriptor.id,\n token: descriptor.token,\n channel: `fai:share:${descriptor.id}`,\n protocol_version: \"2025-06-18\",\n };\n}\n\n/** Read session descriptor from current URL, or null if not a shared link. */\nexport function readSessionFromUrl(): SessionDescriptor | null {\n if (typeof window === \"undefined\") return null;\n const params = new URL(window.location.href).searchParams;\n const id = params.get(\"session\");\n const token = params.get(\"token\");\n if (!id || !token) return null;\n return describeSession(id, token);\n}\n\nfunction randomToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n crypto.getRandomValues(bytes);\n return base64Url(bytes);\n}\n\nfunction randomId(len: number): string {\n const bytes = new Uint8Array(Math.ceil((len * 3) / 4));\n crypto.getRandomValues(bytes);\n return base64Url(bytes).slice(0, len);\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Constant-time string compare so a mismatched token leaks no timing info. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n","import type { JsonRpcMessage } from \"../mcp/types\";\nimport type { Transport } from \"../mcp/server\";\nimport type { MicroMcpServer } from \"../mcp/server\";\nimport { constantTimeEqual } from \"./token\";\n\n/**\n * SseRelayTransport — bridges the in-page MicroMcpServer to a host-app\n * relay broker over Server-Sent Events (inbound) + POST (outbound).\n *\n * Wire model:\n * - Browser opens an EventSource at `${baseUrl}/${sessionId}/events?token=…`.\n * Each `event: mcp` carries one JSON-RPC frame from a remote client.\n * - Browser POSTs JSON-RPC frames to `${baseUrl}/${sessionId}/outbox?token=…`\n * when the local server has a response/notification to send.\n *\n * The host provides the relay endpoint (any HTTP server). See the demo\n * `WhiteboardShareController` for the reference implementation.\n *\n * Token authentication is the host's job — this transport just carries the\n * token in the query string. For lower-trust deployments, layer signing on\n * top by wrapping `send` / `deliverFromRemote`.\n */\nexport type SseRelayOptions = {\n baseUrl: string;\n sessionId: string;\n token: string;\n /** Override fetch (testing / non-browser). Defaults to global fetch. */\n fetch?: typeof fetch;\n};\n\nexport class SseRelayTransport implements Transport {\n private server?: MicroMcpServer;\n private es?: EventSource;\n private opts: SseRelayOptions;\n private sendQueue: JsonRpcMessage[] = [];\n private connected = false;\n private listeners = new Set<(state: RelayState) => void>();\n private state: RelayState = \"idle\";\n private expectedToken: string;\n\n constructor(options: SseRelayOptions) {\n this.opts = options;\n this.expectedToken = options.token;\n }\n\n bindServer(server: MicroMcpServer): void {\n this.server = server;\n }\n\n /** Open the SSE channel. Idempotent. */\n start(): void {\n if (this.connected || typeof window === \"undefined\") return;\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;\n this.setState(\"connecting\");\n const es = new EventSource(url, { withCredentials: false });\n this.es = es;\n\n es.addEventListener(\"open\", () => {\n this.connected = true;\n this.setState(\"open\");\n // Flush queued outbound frames (tool list_changed notifications, etc.)\n const queued = this.sendQueue.splice(0);\n for (const msg of queued) this.postOut(msg);\n });\n\n es.addEventListener(\"mcp\", (ev: MessageEvent) => {\n const raw = ev.data;\n this.handleInbound(raw);\n });\n\n es.addEventListener(\"error\", () => {\n this.setState(\"error\");\n // EventSource auto-reconnects; no need to dispose.\n });\n }\n\n send(message: JsonRpcMessage): void {\n if (!this.connected) {\n this.sendQueue.push(message);\n return;\n }\n this.postOut(message);\n }\n\n close(): void {\n this.es?.close();\n this.es = undefined;\n this.connected = false;\n this.setState(\"closed\");\n }\n\n onStateChange(listener: (state: RelayState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * For relays that wrap each frame with auth metadata: hosts can call this\n * directly when a frame arrives via a non-SSE path. The transport will\n * dispatch it to the bound server.\n */\n async deliverFromRemote(payload: JsonRpcMessage | string, token?: string): Promise<void> {\n if (token !== undefined && !constantTimeEqual(token, this.expectedToken)) return;\n if (!this.server) throw new Error(\"SseRelayTransport has no bound server\");\n const message: JsonRpcMessage = typeof payload === \"string\" ? JSON.parse(payload) : payload;\n await this.server.receive(this, message);\n }\n\n private async postOut(message: JsonRpcMessage): Promise<void> {\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;\n const f = this.opts.fetch ?? fetch;\n try {\n await f(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"accept\": \"application/json\" },\n body: JSON.stringify(message),\n });\n } catch {\n // Drop — relay errors are surfaced via state change separately.\n }\n }\n\n private async handleInbound(raw: string): Promise<void> {\n if (!this.server) return;\n let message: JsonRpcMessage;\n try {\n message = JSON.parse(raw);\n } catch {\n return;\n }\n await this.server.receive(this, message);\n }\n\n private setState(state: RelayState): void {\n this.state = state;\n for (const l of this.listeners) l(state);\n }\n}\n\nexport type RelayState = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport function attachSseRelay(server: MicroMcpServer, options: SseRelayOptions): SseRelayTransport {\n const transport = new SseRelayTransport(options);\n transport.bindServer(server);\n server.attach(transport);\n transport.start();\n return transport;\n}\n"]}
@@ -0,0 +1,130 @@
1
+ // src/mcp/types.ts
2
+ var JSONRPC_PARSE_ERROR = -32700;
3
+ var JSONRPC_INVALID_REQUEST = -32600;
4
+ var JSONRPC_METHOD_NOT_FOUND = -32601;
5
+ var JSONRPC_INVALID_PARAMS = -32602;
6
+ var JSONRPC_INTERNAL_ERROR = -32603;
7
+ var MCP_PROTOCOL_VERSION = "2025-06-18";
8
+
9
+ // src/mcp/server.ts
10
+ var MicroMcpServer = class {
11
+ constructor(options) {
12
+ this.tools = /* @__PURE__ */ new Map();
13
+ this.transports = /* @__PURE__ */ new Set();
14
+ this.notifyListChangedScheduled = false;
15
+ this.info = options.info;
16
+ this.capabilities = options.capabilities ?? { tools: { listChanged: true } };
17
+ this.instructions = options.instructions;
18
+ }
19
+ attach(transport) {
20
+ this.transports.add(transport);
21
+ return () => this.detach(transport);
22
+ }
23
+ detach(transport) {
24
+ if (this.transports.delete(transport)) {
25
+ transport.close?.();
26
+ }
27
+ }
28
+ registerTool(definition, handler) {
29
+ this.tools.set(definition.name, { definition, handler });
30
+ this.scheduleListChangedNotification();
31
+ return () => this.unregisterTool(definition.name);
32
+ }
33
+ unregisterTool(name) {
34
+ if (this.tools.delete(name)) {
35
+ this.scheduleListChangedNotification();
36
+ }
37
+ }
38
+ listTools() {
39
+ return Array.from(this.tools.values()).map((t) => t.definition);
40
+ }
41
+ /**
42
+ * Receive a JSON-RPC frame from a client (called by the transport).
43
+ * The transport is responsible for sending the response back.
44
+ */
45
+ async receive(transport, message) {
46
+ if (!("method" in message)) return;
47
+ const isNotification = !("id" in message);
48
+ if (isNotification) {
49
+ return;
50
+ }
51
+ const request = message;
52
+ try {
53
+ const result = await this.handle(request);
54
+ transport.send({ jsonrpc: "2.0", id: request.id, result });
55
+ } catch (err) {
56
+ transport.send({
57
+ jsonrpc: "2.0",
58
+ id: request.id,
59
+ error: this.toRpcError(err)
60
+ });
61
+ }
62
+ }
63
+ async handle(request) {
64
+ const { method, params } = request;
65
+ switch (method) {
66
+ case "initialize":
67
+ return {
68
+ protocolVersion: MCP_PROTOCOL_VERSION,
69
+ capabilities: this.capabilities,
70
+ serverInfo: this.info,
71
+ ...this.instructions ? { instructions: this.instructions } : {}
72
+ };
73
+ case "tools/list":
74
+ return { tools: this.listTools() };
75
+ case "tools/call": {
76
+ const name = params?.name;
77
+ const args = params?.arguments ?? {};
78
+ if (typeof name !== "string") {
79
+ throw rpcError(JSONRPC_INVALID_PARAMS, "tools/call requires `name`");
80
+ }
81
+ const tool = this.tools.get(name);
82
+ if (!tool) {
83
+ throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unknown tool: ${name}`);
84
+ }
85
+ const result = await tool.handler(args);
86
+ return result;
87
+ }
88
+ case "ping":
89
+ return {};
90
+ default:
91
+ throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unsupported method: ${method}`);
92
+ }
93
+ }
94
+ scheduleListChangedNotification() {
95
+ if (this.notifyListChangedScheduled) return;
96
+ this.notifyListChangedScheduled = true;
97
+ queueMicrotask(() => {
98
+ this.notifyListChangedScheduled = false;
99
+ this.broadcast({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
100
+ });
101
+ }
102
+ broadcast(message) {
103
+ for (const t of this.transports) t.send(message);
104
+ }
105
+ toRpcError(err) {
106
+ if (err && typeof err === "object" && "code" in err && "message" in err) {
107
+ return err;
108
+ }
109
+ return {
110
+ code: JSONRPC_INTERNAL_ERROR,
111
+ message: err instanceof Error ? err.message : String(err)
112
+ };
113
+ }
114
+ };
115
+ function rpcError(code, message, data) {
116
+ return { code, message, ...data !== void 0 ? { data } : {} };
117
+ }
118
+ function textResult(text, structured) {
119
+ return {
120
+ content: [{ type: "text", text }],
121
+ ...structured !== void 0 ? { structuredContent: structured } : {}
122
+ };
123
+ }
124
+ function errorResult(text) {
125
+ return { content: [{ type: "text", text }], isError: true };
126
+ }
127
+
128
+ export { JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, MCP_PROTOCOL_VERSION, MicroMcpServer, errorResult, rpcError, textResult };
129
+ //# sourceMappingURL=chunk-QGCF7YKW.js.map
130
+ //# sourceMappingURL=chunk-QGCF7YKW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/types.ts","../src/mcp/server.ts"],"names":[],"mappings":"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","file":"chunk-QGCF7YKW.js","sourcesContent":["/**\n * Minimal MCP (Model Context Protocol) types — covers the subset this\n * package implements: initialize, tools/list, tools/call, plus JSON-RPC.\n *\n * Aligned with the public MCP spec but trimmed to v0.1 needs. See\n * https://spec.modelcontextprotocol.io/ for the full surface.\n */\n\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | { [key: string]: JsonValue }\n | JsonValue[];\n\nexport type JsonObject = { [key: string]: JsonValue };\n\nexport type JsonRpcId = string | number | null;\n\nexport type JsonRpcRequest = {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n method: string;\n params?: JsonObject;\n};\n\nexport type JsonRpcNotification = {\n jsonrpc: \"2.0\";\n method: string;\n params?: JsonObject;\n};\n\nexport type JsonRpcSuccess = {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n result: JsonValue;\n};\n\nexport type JsonRpcError = {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n error: { code: number; message: string; data?: JsonValue };\n};\n\nexport type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcSuccess | JsonRpcError;\n\nexport const JSONRPC_PARSE_ERROR = -32700;\nexport const JSONRPC_INVALID_REQUEST = -32600;\nexport const JSONRPC_METHOD_NOT_FOUND = -32601;\nexport const JSONRPC_INVALID_PARAMS = -32602;\nexport const JSONRPC_INTERNAL_ERROR = -32603;\n\nexport type ServerCapabilities = {\n tools?: { listChanged?: boolean };\n resources?: { listChanged?: boolean; subscribe?: boolean };\n prompts?: { listChanged?: boolean };\n logging?: Record<string, never>;\n};\n\nexport type ServerInfo = {\n name: string;\n version: string;\n title?: string;\n};\n\nexport type InitializeResult = {\n protocolVersion: string;\n capabilities: ServerCapabilities;\n serverInfo: ServerInfo;\n instructions?: string;\n};\n\nexport type ToolInputSchema = {\n type: \"object\";\n properties?: Record<string, JsonValue>;\n required?: string[];\n additionalProperties?: boolean;\n};\n\nexport type ToolDefinition = {\n name: string;\n title?: string;\n description?: string;\n inputSchema: ToolInputSchema;\n};\n\nexport type ContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; text?: string; mimeType?: string } };\n\nexport type CallToolResult = {\n content: ContentBlock[];\n isError?: boolean;\n /** Structured tool output — non-spec but useful for typed bridges. */\n structuredContent?: JsonValue;\n};\n\n/** Handler signature for a tool registered on the MicroMcpServer. */\nexport type ToolHandler = (args: JsonObject) => Promise<CallToolResult> | CallToolResult;\n\n/** Internal record kept by the server. */\nexport type RegisteredTool = {\n definition: ToolDefinition;\n handler: ToolHandler;\n};\n\nexport const MCP_PROTOCOL_VERSION = \"2025-06-18\";\n","import {\n type CallToolResult,\n type JsonObject,\n type JsonRpcMessage,\n type JsonRpcRequest,\n type JsonRpcId,\n type RegisteredTool,\n type ServerCapabilities,\n type ServerInfo,\n type ToolDefinition,\n type ToolHandler,\n JSONRPC_INTERNAL_ERROR,\n JSONRPC_INVALID_PARAMS,\n JSONRPC_METHOD_NOT_FOUND,\n MCP_PROTOCOL_VERSION,\n} from \"./types\";\n\nexport type McpServerOptions = {\n info: ServerInfo;\n /** Defaults to { tools: { listChanged: true } } */\n capabilities?: ServerCapabilities;\n /** Free-text instructions surfaced to clients during initialize. */\n instructions?: string;\n};\n\nexport type Transport = {\n /** Called by the server when it has a message to deliver to the client. */\n send: (message: JsonRpcMessage) => void;\n /** Called by the server when it's torn down so the transport can clean up. */\n close?: () => void;\n};\n\n/**\n * MicroMcpServer — protocol-level MCP server, transport-agnostic.\n *\n * Use it like:\n *\n * const server = new MicroMcpServer({ info: { name: \"session\", version: \"0.1\" } });\n * server.registerTool({ name: \"...\", inputSchema: { type: \"object\" } }, async (args) => ({...}));\n * const transport = new InProcessTransport();\n * server.attach(transport);\n * transport.deliver({ ... }); // client → server frames\n *\n * The same server can serve multiple transports (e.g. an in-process agent\n * AND a relayed external client) by attaching each one.\n */\nexport class MicroMcpServer {\n private tools = new Map<string, RegisteredTool>();\n private transports = new Set<Transport>();\n private notifyListChangedScheduled = false;\n\n readonly info: ServerInfo;\n readonly capabilities: ServerCapabilities;\n readonly instructions?: string;\n\n constructor(options: McpServerOptions) {\n this.info = options.info;\n this.capabilities = options.capabilities ?? { tools: { listChanged: true } };\n this.instructions = options.instructions;\n }\n\n attach(transport: Transport): () => void {\n this.transports.add(transport);\n return () => this.detach(transport);\n }\n\n detach(transport: Transport): void {\n if (this.transports.delete(transport)) {\n transport.close?.();\n }\n }\n\n registerTool(definition: ToolDefinition, handler: ToolHandler): () => void {\n this.tools.set(definition.name, { definition, handler });\n this.scheduleListChangedNotification();\n return () => this.unregisterTool(definition.name);\n }\n\n unregisterTool(name: string): void {\n if (this.tools.delete(name)) {\n this.scheduleListChangedNotification();\n }\n }\n\n listTools(): ToolDefinition[] {\n return Array.from(this.tools.values()).map((t) => t.definition);\n }\n\n /**\n * Receive a JSON-RPC frame from a client (called by the transport).\n * The transport is responsible for sending the response back.\n */\n async receive(transport: Transport, message: JsonRpcMessage): Promise<void> {\n if (!(\"method\" in message)) return; // It's a response, not a request — ignore.\n\n const isNotification = !(\"id\" in message);\n if (isNotification) {\n // Notifications are fire-and-forget. We ignore unknown methods.\n return;\n }\n\n const request = message as JsonRpcRequest;\n try {\n const result = await this.handle(request);\n transport.send({ jsonrpc: \"2.0\", id: request.id, result });\n } catch (err) {\n transport.send({\n jsonrpc: \"2.0\",\n id: request.id,\n error: this.toRpcError(err),\n });\n }\n }\n\n private async handle(request: JsonRpcRequest): Promise<any> {\n const { method, params } = request;\n switch (method) {\n case \"initialize\":\n return {\n protocolVersion: MCP_PROTOCOL_VERSION,\n capabilities: this.capabilities,\n serverInfo: this.info,\n ...(this.instructions ? { instructions: this.instructions } : {}),\n };\n\n case \"tools/list\":\n return { tools: this.listTools() };\n\n case \"tools/call\": {\n const name = params?.name;\n const args = (params?.arguments ?? {}) as JsonObject;\n if (typeof name !== \"string\") {\n throw rpcError(JSONRPC_INVALID_PARAMS, \"tools/call requires `name`\");\n }\n const tool = this.tools.get(name);\n if (!tool) {\n throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unknown tool: ${name}`);\n }\n const result = await tool.handler(args);\n return result satisfies CallToolResult;\n }\n\n case \"ping\":\n return {};\n\n default:\n throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unsupported method: ${method}`);\n }\n }\n\n private scheduleListChangedNotification(): void {\n if (this.notifyListChangedScheduled) return;\n this.notifyListChangedScheduled = true;\n queueMicrotask(() => {\n this.notifyListChangedScheduled = false;\n this.broadcast({ jsonrpc: \"2.0\", method: \"notifications/tools/list_changed\" });\n });\n }\n\n private broadcast(message: JsonRpcMessage): void {\n for (const t of this.transports) t.send(message);\n }\n\n private toRpcError(err: unknown): { code: number; message: string; data?: any } {\n if (err && typeof err === \"object\" && \"code\" in err && \"message\" in err) {\n return err as any;\n }\n return {\n code: JSONRPC_INTERNAL_ERROR,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nexport function rpcError(code: number, message: string, data?: any) {\n return { code, message, ...(data !== undefined ? { data } : {}) };\n}\n\n/**\n * Helper to build a CallToolResult from a string or structured value.\n */\nexport function textResult(text: string, structured?: any): CallToolResult {\n return {\n content: [{ type: \"text\", text }],\n ...(structured !== undefined ? { structuredContent: structured } : {}),\n };\n}\n\nexport function errorResult(text: string): CallToolResult {\n return { content: [{ type: \"text\", text }], isError: true };\n}\n\n// Internal helper so the JsonRpcId import isn't dropped by tsup\ntype _KeepIdImport = JsonRpcId;\n"]}