@casys/mcp-bridge 0.2.0

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 (131) hide show
  1. package/esm/_dnt.shims.d.ts +2 -0
  2. package/esm/_dnt.shims.d.ts.map +1 -0
  3. package/esm/_dnt.shims.js +57 -0
  4. package/esm/adapters/base-adapter.d.ts +25 -0
  5. package/esm/adapters/base-adapter.d.ts.map +1 -0
  6. package/esm/adapters/base-adapter.js +86 -0
  7. package/esm/adapters/line/adapter.d.ts +11 -0
  8. package/esm/adapters/line/adapter.d.ts.map +1 -0
  9. package/esm/adapters/line/adapter.js +10 -0
  10. package/esm/adapters/line/types.d.ts +25 -0
  11. package/esm/adapters/line/types.d.ts.map +1 -0
  12. package/esm/adapters/line/types.js +4 -0
  13. package/esm/adapters/telegram/adapter.d.ts +11 -0
  14. package/esm/adapters/telegram/adapter.d.ts.map +1 -0
  15. package/esm/adapters/telegram/adapter.js +10 -0
  16. package/esm/adapters/telegram/platform-adapter.d.ts +40 -0
  17. package/esm/adapters/telegram/platform-adapter.d.ts.map +1 -0
  18. package/esm/adapters/telegram/platform-adapter.js +214 -0
  19. package/esm/adapters/telegram/sdk-bridge.d.ts +8 -0
  20. package/esm/adapters/telegram/sdk-bridge.d.ts.map +1 -0
  21. package/esm/adapters/telegram/sdk-bridge.js +22 -0
  22. package/esm/adapters/telegram/types.d.ts +93 -0
  23. package/esm/adapters/telegram/types.d.ts.map +1 -0
  24. package/esm/adapters/telegram/types.js +6 -0
  25. package/esm/client/bridge.js +424 -0
  26. package/esm/core/adapter.d.ts +88 -0
  27. package/esm/core/adapter.d.ts.map +1 -0
  28. package/esm/core/adapter.js +10 -0
  29. package/esm/core/bridge-client.d.ts +77 -0
  30. package/esm/core/bridge-client.d.ts.map +1 -0
  31. package/esm/core/bridge-client.js +275 -0
  32. package/esm/core/message-router.d.ts +71 -0
  33. package/esm/core/message-router.d.ts.map +1 -0
  34. package/esm/core/message-router.js +187 -0
  35. package/esm/core/protocol.d.ts +116 -0
  36. package/esm/core/protocol.d.ts.map +1 -0
  37. package/esm/core/protocol.js +203 -0
  38. package/esm/core/resource-resolver.d.ts +27 -0
  39. package/esm/core/resource-resolver.d.ts.map +1 -0
  40. package/esm/core/resource-resolver.js +85 -0
  41. package/esm/core/transport.d.ts +46 -0
  42. package/esm/core/transport.d.ts.map +1 -0
  43. package/esm/core/transport.js +85 -0
  44. package/esm/core/types.d.ts +187 -0
  45. package/esm/core/types.d.ts.map +1 -0
  46. package/esm/core/types.js +35 -0
  47. package/esm/mod.d.ts +36 -0
  48. package/esm/mod.d.ts.map +1 -0
  49. package/esm/mod.js +33 -0
  50. package/esm/package.json +3 -0
  51. package/esm/resource-server/csp.d.ts +36 -0
  52. package/esm/resource-server/csp.d.ts.map +1 -0
  53. package/esm/resource-server/csp.js +36 -0
  54. package/esm/resource-server/injector.d.ts +18 -0
  55. package/esm/resource-server/injector.d.ts.map +1 -0
  56. package/esm/resource-server/injector.js +39 -0
  57. package/esm/resource-server/server.d.ts +107 -0
  58. package/esm/resource-server/server.d.ts.map +1 -0
  59. package/esm/resource-server/server.js +483 -0
  60. package/esm/resource-server/session.d.ts +60 -0
  61. package/esm/resource-server/session.d.ts.map +1 -0
  62. package/esm/resource-server/session.js +86 -0
  63. package/esm/resource-server/telegram-auth.d.ts +45 -0
  64. package/esm/resource-server/telegram-auth.d.ts.map +1 -0
  65. package/esm/resource-server/telegram-auth.js +161 -0
  66. package/package.json +31 -0
  67. package/script/_dnt.shims.d.ts +2 -0
  68. package/script/_dnt.shims.d.ts.map +1 -0
  69. package/script/_dnt.shims.js +60 -0
  70. package/script/adapters/base-adapter.d.ts +25 -0
  71. package/script/adapters/base-adapter.d.ts.map +1 -0
  72. package/script/adapters/base-adapter.js +113 -0
  73. package/script/adapters/line/adapter.d.ts +11 -0
  74. package/script/adapters/line/adapter.d.ts.map +1 -0
  75. package/script/adapters/line/adapter.js +14 -0
  76. package/script/adapters/line/types.d.ts +25 -0
  77. package/script/adapters/line/types.d.ts.map +1 -0
  78. package/script/adapters/line/types.js +5 -0
  79. package/script/adapters/telegram/adapter.d.ts +11 -0
  80. package/script/adapters/telegram/adapter.d.ts.map +1 -0
  81. package/script/adapters/telegram/adapter.js +14 -0
  82. package/script/adapters/telegram/platform-adapter.d.ts +40 -0
  83. package/script/adapters/telegram/platform-adapter.d.ts.map +1 -0
  84. package/script/adapters/telegram/platform-adapter.js +241 -0
  85. package/script/adapters/telegram/sdk-bridge.d.ts +8 -0
  86. package/script/adapters/telegram/sdk-bridge.d.ts.map +1 -0
  87. package/script/adapters/telegram/sdk-bridge.js +48 -0
  88. package/script/adapters/telegram/types.d.ts +93 -0
  89. package/script/adapters/telegram/types.d.ts.map +1 -0
  90. package/script/adapters/telegram/types.js +7 -0
  91. package/script/client/bridge.js +424 -0
  92. package/script/core/adapter.d.ts +88 -0
  93. package/script/core/adapter.d.ts.map +1 -0
  94. package/script/core/adapter.js +11 -0
  95. package/script/core/bridge-client.d.ts +77 -0
  96. package/script/core/bridge-client.d.ts.map +1 -0
  97. package/script/core/bridge-client.js +302 -0
  98. package/script/core/message-router.d.ts +71 -0
  99. package/script/core/message-router.d.ts.map +1 -0
  100. package/script/core/message-router.js +191 -0
  101. package/script/core/protocol.d.ts +116 -0
  102. package/script/core/protocol.d.ts.map +1 -0
  103. package/script/core/protocol.js +230 -0
  104. package/script/core/resource-resolver.d.ts +27 -0
  105. package/script/core/resource-resolver.d.ts.map +1 -0
  106. package/script/core/resource-resolver.js +89 -0
  107. package/script/core/transport.d.ts +46 -0
  108. package/script/core/transport.d.ts.map +1 -0
  109. package/script/core/transport.js +112 -0
  110. package/script/core/types.d.ts +187 -0
  111. package/script/core/types.d.ts.map +1 -0
  112. package/script/core/types.js +38 -0
  113. package/script/mod.d.ts +36 -0
  114. package/script/mod.d.ts.map +1 -0
  115. package/script/mod.js +76 -0
  116. package/script/package.json +3 -0
  117. package/script/resource-server/csp.d.ts +36 -0
  118. package/script/resource-server/csp.d.ts.map +1 -0
  119. package/script/resource-server/csp.js +39 -0
  120. package/script/resource-server/injector.d.ts +18 -0
  121. package/script/resource-server/injector.d.ts.map +1 -0
  122. package/script/resource-server/injector.js +42 -0
  123. package/script/resource-server/server.d.ts +107 -0
  124. package/script/resource-server/server.d.ts.map +1 -0
  125. package/script/resource-server/server.js +487 -0
  126. package/script/resource-server/session.d.ts +60 -0
  127. package/script/resource-server/session.d.ts.map +1 -0
  128. package/script/resource-server/session.js +90 -0
  129. package/script/resource-server/telegram-auth.d.ts +45 -0
  130. package/script/resource-server/telegram-auth.d.ts.map +1 -0
  131. package/script/resource-server/telegram-auth.js +164 -0
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.BridgeClient = void 0;
27
+ /**
28
+ * BridgeClient — the core of the MCP Apps Bridge.
29
+ *
30
+ * Runs client-side inside the platform webview. It:
31
+ * 1. Monkey-patches `window.parent.postMessage` to intercept MCP JSON-RPC
32
+ * messages from the App class (`@modelcontextprotocol/ext-apps`).
33
+ * 2. Routes intercepted messages to the resource server via BridgeTransport.
34
+ * 3. Dispatches incoming messages from the resource server as MessageEvents
35
+ * to the App class.
36
+ * 4. Translates platform lifecycle events into MCP notifications.
37
+ * 5. Synthesizes the `ui/initialize` response using platform adapter data.
38
+ *
39
+ * This allows unmodified MCP App HTML to work inside Telegram/LINE webviews.
40
+ */
41
+ const dntShim = __importStar(require("../_dnt.shims.js"));
42
+ const message_router_js_1 = require("./message-router.js");
43
+ const protocol_js_1 = require("./protocol.js");
44
+ const types_js_1 = require("./types.js");
45
+ /**
46
+ * The MCP Apps Bridge client.
47
+ *
48
+ * Injected into the MCP App HTML by the resource server (`bridge.js`).
49
+ * Intercepts postMessage, routes via WebSocket, translates platform events.
50
+ */
51
+ class BridgeClient {
52
+ platform;
53
+ transport;
54
+ router;
55
+ options;
56
+ hostContext = null;
57
+ started = false;
58
+ // deno-lint-ignore no-explicit-any
59
+ originalPostMessage = null;
60
+ constructor(options) {
61
+ this.options = options;
62
+ this.platform = options.platform;
63
+ this.transport = options.transport;
64
+ this.router = new message_router_js_1.MessageRouter();
65
+ }
66
+ /**
67
+ * Start the bridge:
68
+ * 1. Initialize the platform adapter to get HostContext
69
+ * 2. Connect transport to resource server
70
+ * 3. Intercept postMessage from App class
71
+ * 4. Listen for platform lifecycle events
72
+ * 5. Register ui/initialize handler to synthesize response
73
+ */
74
+ async start() {
75
+ if (this.started) {
76
+ throw new Error("[BridgeClient] Already started.");
77
+ }
78
+ // 1. Init platform and get host context
79
+ this.hostContext = await this.platform.initialize();
80
+ // 2. Connect transport
81
+ const wsUrl = `${this.options.serverUrl}/bridge?session=${this.options.sessionId}`;
82
+ await this.transport.connect(wsUrl);
83
+ // 3. Forward incoming messages from resource server to App class
84
+ this.transport.onMessage((message) => {
85
+ this.handleIncomingMessage(message);
86
+ });
87
+ // 4. Register handler for ui/initialize (App -> Bridge)
88
+ this.router.onRequest(types_js_1.McpAppsMethod.UI_INITIALIZE, (params) => {
89
+ return this.handleInitialize(params);
90
+ });
91
+ // 5. Register handler for ui/open-link (delegate to platform)
92
+ this.router.onRequest(types_js_1.McpAppsMethod.UI_OPEN_LINK, async (params) => {
93
+ const url = params?.url;
94
+ if (typeof url !== "string") {
95
+ throw new Error("ui/open-link requires a 'url' parameter.");
96
+ }
97
+ await this.platform.openLink(url);
98
+ return {};
99
+ });
100
+ // 6. Intercept postMessage
101
+ this.interceptPostMessage();
102
+ // 7. Listen for platform lifecycle events
103
+ this.platform.onLifecycleEvent((event) => {
104
+ this.handleLifecycleEvent(event);
105
+ });
106
+ this.started = true;
107
+ }
108
+ /** Stop the bridge, restore postMessage, disconnect transport. */
109
+ destroy() {
110
+ this.restorePostMessage();
111
+ this.transport.disconnect();
112
+ this.router.destroy();
113
+ this.started = false;
114
+ }
115
+ /** Whether the bridge is currently running. */
116
+ get isStarted() {
117
+ return this.started;
118
+ }
119
+ /** The current host context (null before start). */
120
+ get currentHostContext() {
121
+ return this.hostContext;
122
+ }
123
+ // -------------------------------------------------------------------------
124
+ // postMessage interception
125
+ // -------------------------------------------------------------------------
126
+ interceptPostMessage() {
127
+ // deno-lint-ignore no-explicit-any
128
+ const _global = dntShim.dntGlobalThis;
129
+ if (!_global.parent?.postMessage) {
130
+ // Not in a frame context (e.g. server-side). Skip interception.
131
+ return;
132
+ }
133
+ this.originalPostMessage = _global.parent.postMessage.bind(_global.parent);
134
+ _global.parent.postMessage = (message, targetOrigin, transfer) => {
135
+ if ((0, protocol_js_1.isJsonRpcMessage)(message)) {
136
+ // Route MCP JSON-RPC messages through the bridge
137
+ this.handleOutgoingMessage(message);
138
+ }
139
+ else if (this.originalPostMessage) {
140
+ // Non-JSON-RPC messages pass through untouched
141
+ this.originalPostMessage(message, targetOrigin, transfer);
142
+ }
143
+ };
144
+ }
145
+ restorePostMessage() {
146
+ if (this.originalPostMessage) {
147
+ // deno-lint-ignore no-explicit-any
148
+ const _global = dntShim.dntGlobalThis;
149
+ if (_global.parent) {
150
+ _global.parent.postMessage = this.originalPostMessage;
151
+ }
152
+ this.originalPostMessage = null;
153
+ }
154
+ }
155
+ // -------------------------------------------------------------------------
156
+ // Message handling
157
+ // -------------------------------------------------------------------------
158
+ /**
159
+ * Handle an outgoing message from the MCP App (intercepted from postMessage).
160
+ *
161
+ * Requests that the bridge handles locally (ui/initialize, ui/open-link)
162
+ * are dispatched to the router. All other messages are forwarded to the
163
+ * resource server via transport.
164
+ */
165
+ async handleOutgoingMessage(message) {
166
+ // Check if the router has a handler for this method
167
+ if ("method" in message && this.router.hasRequestHandler(message.method)) {
168
+ const response = await this.router.handleMessage(message);
169
+ if (response) {
170
+ this.dispatchToApp(response);
171
+ }
172
+ return;
173
+ }
174
+ // Check notification handlers
175
+ if ("method" in message &&
176
+ !("id" in message) &&
177
+ this.router.hasNotificationHandler(message.method)) {
178
+ await this.router.handleMessage(message);
179
+ return;
180
+ }
181
+ // Forward to resource server
182
+ this.transport.send(message);
183
+ // Track outgoing requests for response matching
184
+ if ("id" in message && "method" in message) {
185
+ const req = message;
186
+ this.router
187
+ .trackRequest(req.id, req.method, this.options.requestTimeoutMs ?? 30_000)
188
+ .then((result) => {
189
+ this.dispatchToApp({
190
+ jsonrpc: "2.0",
191
+ id: req.id,
192
+ result,
193
+ });
194
+ })
195
+ .catch((err) => {
196
+ this.dispatchToApp({
197
+ jsonrpc: "2.0",
198
+ id: req.id,
199
+ error: { code: -32603, message: err.message },
200
+ });
201
+ });
202
+ }
203
+ }
204
+ /**
205
+ * Handle an incoming message from the resource server.
206
+ *
207
+ * Responses are routed to the pending request tracker (which resolves the
208
+ * Promise created in handleOutgoingMessage — that Promise already calls
209
+ * dispatchToApp, so we must NOT dispatch responses again here).
210
+ *
211
+ * Notifications are dispatched directly to the App class.
212
+ */
213
+ async handleIncomingMessage(message) {
214
+ // Responses: let the router resolve the tracked request.
215
+ // The .then() in handleOutgoingMessage will call dispatchToApp.
216
+ if ("result" in message || "error" in message) {
217
+ await this.router.handleMessage(message);
218
+ return;
219
+ }
220
+ // Notifications from resource server: dispatch to App class.
221
+ // Only notifications (method + no id), not requests (method + id).
222
+ if ("method" in message && !("id" in message)) {
223
+ this.dispatchToApp(message);
224
+ }
225
+ }
226
+ /** Dispatch a message to the MCP App class as a MessageEvent. */
227
+ dispatchToApp(message) {
228
+ // deno-lint-ignore no-explicit-any
229
+ const _global = dntShim.dntGlobalThis;
230
+ if (typeof _global.dispatchEvent === "function" && typeof _global.MessageEvent === "function") {
231
+ _global.dispatchEvent(new _global.MessageEvent("message", {
232
+ data: message,
233
+ origin: this.options.serverUrl,
234
+ }));
235
+ }
236
+ }
237
+ // -------------------------------------------------------------------------
238
+ // ui/initialize handler
239
+ // -------------------------------------------------------------------------
240
+ handleInitialize(_params) {
241
+ if (!this.hostContext) {
242
+ throw new Error("[BridgeClient] Platform not initialized. Cannot handle ui/initialize.");
243
+ }
244
+ const info = this.options.bridgeInfo ?? {
245
+ name: "@casys/mcp-apps-bridge",
246
+ version: "0.1.0",
247
+ };
248
+ const capabilities = {
249
+ serverTools: { listChanged: false },
250
+ serverResources: { listChanged: false },
251
+ logging: {},
252
+ openLinks: {},
253
+ };
254
+ const response = (0, protocol_js_1.buildInitializeResponse)("__placeholder__", {
255
+ protocolVersion: "2025-11-25",
256
+ hostInfo: info,
257
+ hostCapabilities: capabilities,
258
+ hostContext: this.hostContext,
259
+ });
260
+ // Return just the result (the router wraps it in a response)
261
+ return response.result;
262
+ }
263
+ // -------------------------------------------------------------------------
264
+ // Platform lifecycle events
265
+ // -------------------------------------------------------------------------
266
+ handleLifecycleEvent(event) {
267
+ switch (event.type) {
268
+ case "theme-changed": {
269
+ const theme = this.platform.getTheme();
270
+ if (this.hostContext) {
271
+ this.hostContext = { ...this.hostContext, theme };
272
+ }
273
+ this.dispatchToApp((0, protocol_js_1.buildHostContextChangedNotification)({ theme }));
274
+ break;
275
+ }
276
+ case "viewport-changed": {
277
+ const containerDimensions = this.platform.getContainerDimensions();
278
+ if (this.hostContext) {
279
+ this.hostContext = { ...this.hostContext, containerDimensions };
280
+ }
281
+ this.dispatchToApp((0, protocol_js_1.buildHostContextChangedNotification)({ containerDimensions }));
282
+ break;
283
+ }
284
+ case "teardown": {
285
+ // Notify the app it's being torn down, then disconnect
286
+ this.dispatchToApp({
287
+ jsonrpc: "2.0",
288
+ method: types_js_1.McpAppsMethod.UI_RESOURCE_TEARDOWN,
289
+ params: { reason: event.reason ?? "platform-close" },
290
+ });
291
+ this.destroy();
292
+ break;
293
+ }
294
+ case "activated":
295
+ case "deactivated":
296
+ // Internal lifecycle events — no MCP equivalent.
297
+ // Could be used for session keepalive in the future.
298
+ break;
299
+ }
300
+ }
301
+ }
302
+ exports.BridgeClient = BridgeClient;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Routes incoming JSON-RPC messages to registered method handlers.
3
+ *
4
+ * Supports:
5
+ * - Request/response dispatch with automatic JSON-RPC response generation
6
+ * - Notification dispatch (fire-and-forget)
7
+ * - Pending request tracking for outgoing requests (match response by `id`)
8
+ * - Handler removal
9
+ */
10
+ import type { McpAppsErrorResponse, McpAppsMessage, McpAppsResponse } from "./types.js";
11
+ /** Handler for a JSON-RPC request that returns a result. */
12
+ export type RequestHandler = (params: Record<string, unknown> | undefined) => Promise<unknown> | unknown;
13
+ /** Handler for a JSON-RPC notification (fire-and-forget). */
14
+ export type NotificationHandler = (params: Record<string, unknown> | undefined) => void;
15
+ /**
16
+ * JSON-RPC message router.
17
+ *
18
+ * Register handlers for specific method names, then call `handleMessage()`
19
+ * for each incoming message. The router will dispatch to the correct handler
20
+ * and produce the appropriate JSON-RPC response.
21
+ *
22
+ * For outgoing requests, use `trackRequest()` to get a Promise that resolves
23
+ * when the matching response arrives.
24
+ */
25
+ export declare class MessageRouter {
26
+ private readonly requestHandlers;
27
+ private readonly notificationHandlers;
28
+ private readonly pendingRequests;
29
+ /** Register a handler for a JSON-RPC request method. */
30
+ onRequest(method: string, handler: RequestHandler): void;
31
+ /** Register a handler for a JSON-RPC notification method. */
32
+ onNotification(method: string, handler: NotificationHandler): void;
33
+ /** Remove a previously registered request handler. */
34
+ removeRequestHandler(method: string): boolean;
35
+ /** Remove a previously registered notification handler. */
36
+ removeNotificationHandler(method: string): boolean;
37
+ /** Check if a request handler is registered for a method. */
38
+ hasRequestHandler(method: string): boolean;
39
+ /** Check if a notification handler is registered for a method. */
40
+ hasNotificationHandler(method: string): boolean;
41
+ /**
42
+ * Track an outgoing request by its `id`.
43
+ *
44
+ * Returns a Promise that resolves with the result when the matching
45
+ * response arrives, or rejects if an error response is received.
46
+ *
47
+ * @param id - The request `id` to track.
48
+ * @param method - The method name (for error messages).
49
+ * @param timeoutMs - Timeout in ms. Defaults to 30_000.
50
+ */
51
+ trackRequest(id: string | number, method: string, timeoutMs?: number): Promise<unknown>;
52
+ /** Number of pending outgoing requests. */
53
+ get pendingCount(): number;
54
+ /**
55
+ * Route an incoming message to the appropriate handler.
56
+ *
57
+ * - **Responses** (with `result` or `error`, no `method`) are matched
58
+ * against pending outgoing requests by `id`.
59
+ * - **Requests** (with `id` and `method`) are dispatched to request
60
+ * handlers and return a JSON-RPC response.
61
+ * - **Notifications** (with `method`, no `id`) are dispatched to
62
+ * notification handlers and return `null`.
63
+ */
64
+ handleMessage(message: McpAppsMessage): Promise<McpAppsResponse | McpAppsErrorResponse | null>;
65
+ /**
66
+ * Clean up all pending requests by rejecting them.
67
+ * Call this when tearing down the router.
68
+ */
69
+ destroy(): void;
70
+ }
71
+ //# sourceMappingURL=message-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-router.d.ts","sourceRoot":"","sources":["../../src/core/message-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EAGd,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KACxC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEhC,6DAA6D;AAC7D,MAAM,MAAM,mBAAmB,GAAG,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KACxC,IAAI,CAAC;AAUV;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqC;IACrE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA0C;IAC/E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA8C;IAM9E,wDAAwD;IACxD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IASxD,6DAA6D;IAC7D,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI;IASlE,sDAAsD;IACtD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI7C,2DAA2D;IAC3D,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIlD,6DAA6D;IAC7D,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI1C,kEAAkE;IAClE,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAQ/C;;;;;;;;;OASG;IACH,YAAY,CACV,EAAE,EAAE,MAAM,GAAG,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,SAAS,SAAS,GACjB,OAAO,CAAC,OAAO,CAAC;IAkCnB,2CAA2C;IAC3C,IAAI,YAAY,IAAI,MAAM,CAEzB;IAMD;;;;;;;;;OASG;IACG,aAAa,CACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,GAAG,oBAAoB,GAAG,IAAI,CAAC;IAuDzD;;;OAGG;IACH,OAAO,IAAI,IAAI;CAYhB"}
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * Routes incoming JSON-RPC messages to registered method handlers.
4
+ *
5
+ * Supports:
6
+ * - Request/response dispatch with automatic JSON-RPC response generation
7
+ * - Notification dispatch (fire-and-forget)
8
+ * - Pending request tracking for outgoing requests (match response by `id`)
9
+ * - Handler removal
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MessageRouter = void 0;
13
+ /**
14
+ * JSON-RPC message router.
15
+ *
16
+ * Register handlers for specific method names, then call `handleMessage()`
17
+ * for each incoming message. The router will dispatch to the correct handler
18
+ * and produce the appropriate JSON-RPC response.
19
+ *
20
+ * For outgoing requests, use `trackRequest()` to get a Promise that resolves
21
+ * when the matching response arrives.
22
+ */
23
+ class MessageRouter {
24
+ requestHandlers = new Map();
25
+ notificationHandlers = new Map();
26
+ pendingRequests = new Map();
27
+ // -------------------------------------------------------------------------
28
+ // Handler registration
29
+ // -------------------------------------------------------------------------
30
+ /** Register a handler for a JSON-RPC request method. */
31
+ onRequest(method, handler) {
32
+ if (this.requestHandlers.has(method)) {
33
+ throw new Error(`[MessageRouter] Request handler already registered for method "${method}".`);
34
+ }
35
+ this.requestHandlers.set(method, handler);
36
+ }
37
+ /** Register a handler for a JSON-RPC notification method. */
38
+ onNotification(method, handler) {
39
+ if (this.notificationHandlers.has(method)) {
40
+ throw new Error(`[MessageRouter] Notification handler already registered for method "${method}".`);
41
+ }
42
+ this.notificationHandlers.set(method, handler);
43
+ }
44
+ /** Remove a previously registered request handler. */
45
+ removeRequestHandler(method) {
46
+ return this.requestHandlers.delete(method);
47
+ }
48
+ /** Remove a previously registered notification handler. */
49
+ removeNotificationHandler(method) {
50
+ return this.notificationHandlers.delete(method);
51
+ }
52
+ /** Check if a request handler is registered for a method. */
53
+ hasRequestHandler(method) {
54
+ return this.requestHandlers.has(method);
55
+ }
56
+ /** Check if a notification handler is registered for a method. */
57
+ hasNotificationHandler(method) {
58
+ return this.notificationHandlers.has(method);
59
+ }
60
+ // -------------------------------------------------------------------------
61
+ // Outgoing request tracking
62
+ // -------------------------------------------------------------------------
63
+ /**
64
+ * Track an outgoing request by its `id`.
65
+ *
66
+ * Returns a Promise that resolves with the result when the matching
67
+ * response arrives, or rejects if an error response is received.
68
+ *
69
+ * @param id - The request `id` to track.
70
+ * @param method - The method name (for error messages).
71
+ * @param timeoutMs - Timeout in ms. Defaults to 30_000.
72
+ */
73
+ trackRequest(id, method, timeoutMs = 30_000) {
74
+ if (this.pendingRequests.has(id)) {
75
+ throw new Error(`[MessageRouter] Request with id "${id}" is already being tracked.`);
76
+ }
77
+ return new Promise((resolve, reject) => {
78
+ const timer = setTimeout(() => {
79
+ this.pendingRequests.delete(id);
80
+ reject(new Error(`[MessageRouter] Request "${method}" (id=${id}) timed out after ${timeoutMs}ms.`));
81
+ }, timeoutMs);
82
+ this.pendingRequests.set(id, {
83
+ resolve: (result) => {
84
+ clearTimeout(timer);
85
+ this.pendingRequests.delete(id);
86
+ resolve(result);
87
+ },
88
+ reject: (error) => {
89
+ clearTimeout(timer);
90
+ this.pendingRequests.delete(id);
91
+ reject(error);
92
+ },
93
+ method,
94
+ createdAt: Date.now(),
95
+ });
96
+ });
97
+ }
98
+ /** Number of pending outgoing requests. */
99
+ get pendingCount() {
100
+ return this.pendingRequests.size;
101
+ }
102
+ // -------------------------------------------------------------------------
103
+ // Message dispatch
104
+ // -------------------------------------------------------------------------
105
+ /**
106
+ * Route an incoming message to the appropriate handler.
107
+ *
108
+ * - **Responses** (with `result` or `error`, no `method`) are matched
109
+ * against pending outgoing requests by `id`.
110
+ * - **Requests** (with `id` and `method`) are dispatched to request
111
+ * handlers and return a JSON-RPC response.
112
+ * - **Notifications** (with `method`, no `id`) are dispatched to
113
+ * notification handlers and return `null`.
114
+ */
115
+ async handleMessage(message) {
116
+ // Response -> resolve/reject pending request
117
+ if (isSuccessResponse(message)) {
118
+ const pending = this.pendingRequests.get(message.id);
119
+ if (pending) {
120
+ pending.resolve(message.result);
121
+ }
122
+ return null;
123
+ }
124
+ if (isErrorResponseMsg(message)) {
125
+ const pending = message.id !== null
126
+ ? this.pendingRequests.get(message.id)
127
+ : undefined;
128
+ if (pending) {
129
+ pending.reject(new Error(`[MessageRouter] RPC error ${message.error.code}: ${message.error.message}`));
130
+ }
131
+ return null;
132
+ }
133
+ if (!isRequestOrNotification(message)) {
134
+ return null;
135
+ }
136
+ const { method, params } = message;
137
+ // Notification (no id)
138
+ if (!("id" in message) || message.id === undefined) {
139
+ const handler = this.notificationHandlers.get(method);
140
+ if (handler) {
141
+ handler(params);
142
+ }
143
+ return null;
144
+ }
145
+ // Request (with id)
146
+ const reqId = message.id;
147
+ const handler = this.requestHandlers.get(method);
148
+ if (!handler) {
149
+ return errorResponse(reqId, -32601, `Method not found: ${method}`);
150
+ }
151
+ try {
152
+ const result = await handler(params);
153
+ return successResponse(reqId, result);
154
+ }
155
+ catch (err) {
156
+ const errorMessage = err instanceof Error ? err.message : String(err);
157
+ return errorResponse(reqId, -32603, errorMessage);
158
+ }
159
+ }
160
+ /**
161
+ * Clean up all pending requests by rejecting them.
162
+ * Call this when tearing down the router.
163
+ */
164
+ destroy() {
165
+ for (const [id, pending] of this.pendingRequests) {
166
+ pending.reject(new Error(`[MessageRouter] Router destroyed while request "${pending.method}" (id=${id}) was pending.`));
167
+ }
168
+ this.pendingRequests.clear();
169
+ this.requestHandlers.clear();
170
+ this.notificationHandlers.clear();
171
+ }
172
+ }
173
+ exports.MessageRouter = MessageRouter;
174
+ // ---------------------------------------------------------------------------
175
+ // Helpers
176
+ // ---------------------------------------------------------------------------
177
+ function isRequestOrNotification(msg) {
178
+ return "method" in msg;
179
+ }
180
+ function isSuccessResponse(msg) {
181
+ return "result" in msg && "id" in msg && !("method" in msg);
182
+ }
183
+ function isErrorResponseMsg(msg) {
184
+ return "error" in msg && "id" in msg && !("method" in msg);
185
+ }
186
+ function successResponse(id, result) {
187
+ return { jsonrpc: "2.0", id, result };
188
+ }
189
+ function errorResponse(id, code, message) {
190
+ return { jsonrpc: "2.0", id, error: { code, message } };
191
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * JSON-RPC 2.0 message builders and validators for the MCP Apps protocol.
3
+ *
4
+ * Provides type-safe construction of all MCP Apps messages and a
5
+ * message validator to guard incoming data at system boundaries.
6
+ */
7
+ import type { HostCapabilities, HostContext, McpAppsErrorResponse, McpAppsMessage, McpAppsNotification, McpAppsRequest, McpAppsResponse } from "./types.js";
8
+ /** Generate a monotonically increasing request ID. */
9
+ export declare function nextRequestId(): number;
10
+ /** Reset the ID counter. @internal — exported for tests only. */
11
+ export declare function resetRequestIdCounter(): void;
12
+ /** Check if a value is a valid JSON-RPC 2.0 message. */
13
+ export declare function isJsonRpcMessage(value: unknown): value is McpAppsMessage;
14
+ /** Check if a message is a JSON-RPC request (has `id` and `method`). */
15
+ export declare function isRequest(msg: McpAppsMessage): msg is McpAppsRequest;
16
+ /** Check if a message is a JSON-RPC notification (has `method`, no `id`). */
17
+ export declare function isNotification(msg: McpAppsMessage): msg is McpAppsNotification;
18
+ /** Check if a message is a JSON-RPC success response. */
19
+ export declare function isResponse(msg: McpAppsMessage): msg is McpAppsResponse;
20
+ /** Check if a message is a JSON-RPC error response. */
21
+ export declare function isErrorResponse(msg: McpAppsMessage): msg is McpAppsErrorResponse;
22
+ /** Build a `ui/initialize` request. */
23
+ export declare function buildInitializeRequest(params: {
24
+ protocolVersion: string;
25
+ clientInfo: {
26
+ name: string;
27
+ version: string;
28
+ };
29
+ capabilities?: Record<string, unknown>;
30
+ }): McpAppsRequest;
31
+ /** Build a `tools/call` request. */
32
+ export declare function buildToolCallRequest(params: {
33
+ name: string;
34
+ arguments?: Record<string, unknown>;
35
+ }): McpAppsRequest;
36
+ /** Build a `resources/read` request. */
37
+ export declare function buildResourceReadRequest(params: {
38
+ uri: string;
39
+ }): McpAppsRequest;
40
+ /** Build a `ui/open-link` request. */
41
+ export declare function buildOpenLinkRequest(params: {
42
+ url: string;
43
+ }): McpAppsRequest;
44
+ /** Build a `ui/message` request. */
45
+ export declare function buildMessageRequest(params: {
46
+ content: ReadonlyArray<{
47
+ type: string;
48
+ text: string;
49
+ }>;
50
+ }): McpAppsRequest;
51
+ /** Build a `ui/update-model-context` request. */
52
+ export declare function buildUpdateModelContextRequest(params: {
53
+ content: ReadonlyArray<{
54
+ type: string;
55
+ text: string;
56
+ }>;
57
+ }): McpAppsRequest;
58
+ /** Build a `ui/request-display-mode` request. */
59
+ export declare function buildDisplayModeRequest(params: {
60
+ mode: "inline" | "fullscreen" | "pip";
61
+ }): McpAppsRequest;
62
+ /** Build a `ui/notifications/initialized` notification (App -> Host). */
63
+ export declare function buildInitializedNotification(): McpAppsNotification;
64
+ /** Build a `ui/notifications/tool-input` notification (Host -> App). */
65
+ export declare function buildToolInputNotification(params: {
66
+ name: string;
67
+ arguments?: Record<string, unknown>;
68
+ }): McpAppsNotification;
69
+ /** Build a `ui/notifications/tool-result` notification (Host -> App). */
70
+ export declare function buildToolResultNotification(params: {
71
+ content: ReadonlyArray<{
72
+ type: string;
73
+ text?: string;
74
+ data?: string;
75
+ mimeType?: string;
76
+ }>;
77
+ isError?: boolean;
78
+ }): McpAppsNotification;
79
+ /** Build a `ui/notifications/tool-cancelled` notification (Host -> App). */
80
+ export declare function buildToolCancelledNotification(params?: {
81
+ reason?: string;
82
+ }): McpAppsNotification;
83
+ /** Build a `ui/notifications/host-context-changed` notification. */
84
+ export declare function buildHostContextChangedNotification(params: Partial<HostContext>): McpAppsNotification;
85
+ /** Build a `ui/resource-teardown` request (Host -> App). */
86
+ export declare function buildResourceTeardownRequest(params?: {
87
+ reason?: string;
88
+ }): McpAppsRequest;
89
+ /** Build a `notifications/message` notification (logging). */
90
+ export declare function buildLogNotification(params: {
91
+ level: "debug" | "info" | "warning" | "error";
92
+ data?: unknown;
93
+ logger?: string;
94
+ }): McpAppsNotification;
95
+ /** Build a `ui/initialize` success response. */
96
+ export declare function buildInitializeResponse(id: string | number, params: {
97
+ protocolVersion: string;
98
+ hostInfo: {
99
+ name: string;
100
+ version: string;
101
+ };
102
+ hostCapabilities: HostCapabilities;
103
+ hostContext: HostContext;
104
+ }): McpAppsResponse;
105
+ /** Build a generic JSON-RPC success response. */
106
+ export declare function buildSuccessResponse(id: string | number, result: unknown): McpAppsResponse;
107
+ /** Build a JSON-RPC error response. */
108
+ export declare function buildErrorResponse(id: string | number | null, code: number, message: string, data?: unknown): McpAppsErrorResponse;
109
+ export declare const JsonRpcErrorCode: {
110
+ readonly PARSE_ERROR: -32700;
111
+ readonly INVALID_REQUEST: -32600;
112
+ readonly METHOD_NOT_FOUND: -32601;
113
+ readonly INVALID_PARAMS: -32602;
114
+ readonly INTERNAL_ERROR: -32603;
115
+ };
116
+ //# sourceMappingURL=protocol.d.ts.map