@mcp-b/webmcp-local-relay 2.2.0 → 2.3.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.
package/README.md CHANGED
@@ -84,6 +84,16 @@ Custom relay port:
84
84
  ></script>
85
85
  ```
86
86
 
87
+ Increase the per-request timeout (default `60000` ms) for tools that chain
88
+ several slow API calls and might exceed one minute:
89
+
90
+ ```html
91
+ <script
92
+ src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@latest/dist/browser/embed.js"
93
+ data-request-timeout="120000"
94
+ ></script>
95
+ ```
96
+
87
97
  Tool registration references:
88
98
 
89
99
  - [`@mcp-b/global` quick start and `registerTool`](../global/README.md)
@@ -109,12 +119,12 @@ npx @mcp-b/webmcp-local-relay
109
119
 
110
120
  The relay exposes four static management tools that are always available:
111
121
 
112
- | Tool | Description |
113
- |------|-------------|
114
- | `webmcp_list_sources` | Lists connected browser tabs with metadata (tab ID, origin, URL, title, icon, tool count) |
115
- | `webmcp_list_tools` | Lists all relayed tools with source info |
116
- | `webmcp_call_tool` | Invokes a relayed tool by name with JSON arguments — useful for clients that don't support dynamic tool registration |
117
- | `webmcp_open_page` | Opens a URL in the user's default browser, or refreshes a connected source page by matching origin |
122
+ | Tool | Description |
123
+ | --------------------- | -------------------------------------------------------------------------------------------------------------------- |
124
+ | `webmcp_list_sources` | Lists connected browser tabs with metadata (tab ID, origin, URL, title, icon, tool count) |
125
+ | `webmcp_list_tools` | Lists all relayed tools with source info |
126
+ | `webmcp_call_tool` | Invokes a relayed tool by name with JSON arguments — useful for clients that don't support dynamic tool registration |
127
+ | `webmcp_open_page` | Opens a URL in the user's default browser, or refreshes a connected source page by matching origin |
118
128
 
119
129
  **Dynamic tools** are registered directly on the MCP server using the original tool name, sanitized to `[a-zA-Z0-9_]`. When tools from different tabs share a name, a short tab-ID suffix is appended for disambiguation:
120
130
 
@@ -153,6 +163,7 @@ npx @mcp-b/webmcp-local-relay --widget-origin https://myapp.com
153
163
  - The default `allowedOrigins` is `*`, which permits any browser page to connect and register tools. This is convenient for development but means any website open in your browser can expose tools to the relay.
154
164
  - `--widget-origin` validates the **host page origin** reported in the browser `hello` message. This is the origin of the page that loaded `embed.js` (e.g., `https://myapp.com`), regardless of whether the widget iframe is served from CDN or self-hosted.
155
165
  - **Recommended:** Use `--widget-origin` to restrict which websites can register tools:
166
+
156
167
  ```bash
157
168
  # Only allow tools from myapp.com
158
169
  webmcp-local-relay --widget-origin https://myapp.com
@@ -160,6 +171,7 @@ npx @mcp-b/webmcp-local-relay --widget-origin https://myapp.com
160
171
  # Allow multiple origins
161
172
  webmcp-local-relay --widget-origin https://app1.com,https://app2.com
162
173
  ```
174
+
163
175
  - Only the host page origin is checked — any local process can connect regardless of origin restrictions.
164
176
 
165
177
  ### Architecture
@@ -227,12 +239,13 @@ For Chromium/Chrome Canary native preview testing:
227
239
 
228
240
  ### Troubleshooting
229
241
 
230
- | Problem | Fix |
231
- |---------|-----|
232
- | `No sources connected` | Ensure the page loaded `embed.js` and the relay process is running |
233
- | `No tools listed` | Ensure tools are registered on the page's WebMCP runtime. If tools register after load, confirm your runtime emits tool-change notifications (`toolschanged` or `registerToolsChangedCallback`) |
234
- | `Tool not found` | Tab reloaded or disconnected — call `webmcp_list_tools` again to refresh |
235
- | Connection blocked | Verify `--widget-origin` matches your host page's origin (e.g., `https://myapp.com`), and relay port matches `data-relay-port` |
242
+ | Problem | Fix |
243
+ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
244
+ | `No sources connected` | Ensure the page loaded `embed.js` and the relay process is running |
245
+ | `No tools listed` | Ensure tools are registered on the page's WebMCP runtime. If tools register after load, confirm your runtime emits tool-change notifications (`toolschanged` or `registerToolsChangedCallback`) |
246
+ | `Tool not found` | Tab reloaded or disconnected — call `webmcp_list_tools` again to refresh |
247
+ | Connection blocked | Verify `--widget-origin` matches your host page's origin (e.g., `https://myapp.com`), and relay port matches `data-relay-port` |
248
+ | `Host response timeout:` | The host page took longer than the per-request timeout (default 60s) to respond. Increase via `data-request-timeout="<ms>"` on the embed script tag |
236
249
 
237
250
  ---
238
251
 
@@ -242,18 +255,24 @@ For Chromium/Chrome Canary native preview testing:
242
255
 
243
256
  ```text
244
257
  src/
245
- ├── cli.ts CLI entry point
246
- ├── cli-utils.ts CLI argument parsing
247
- ├── mcpRelayServer.ts MCP server (stdio + dynamic tool sync)
248
- ├── bridgeServer.ts WebSocket relay server
249
- ├── registry.ts Multi-source aggregation and deduplication
250
- ├── naming.ts Tool name sanitization and namespacing
251
- ├── schemas.ts Browser <-> relay protocol schemas
252
- ├── browser/embed.js Script-tag loader for website owners
253
- ├── browser/widget.html Hidden iframe bridge runtime
254
- └── index.ts Public API exports
258
+ ├── cli.ts CLI entry point
259
+ ├── cli-utils.ts CLI argument parsing
260
+ ├── mcpRelayServer.ts MCP server (stdio + dynamic tool sync)
261
+ ├── bridgeServer.ts WebSocket relay server
262
+ ├── registry.ts Multi-source aggregation and deduplication
263
+ ├── naming.ts Tool name sanitization and namespacing
264
+ ├── schemas.ts Browser <-> relay protocol schemas
265
+ ├── browser/embed.ts Script-tag loader for website owners
266
+ ├── browser/widget.ts Widget IIFE entry point (calls startWidgetRuntime)
267
+ ├── browser/widgetRuntime.ts Hidden iframe bridge runtime
268
+ ├── browser/shared.ts Shared browser-side utilities
269
+ └── index.ts Public API exports
255
270
  ```
256
271
 
272
+ `dist/browser/widget.html` is generated at build time by
273
+ `scripts/build-widget-html.js`, which wraps the bundled `widget.js` (compiled
274
+ from `widget.ts` + `widgetRuntime.ts`) into a minimal HTML shell.
275
+
257
276
  ### Build and Test
258
277
 
259
278
  From repository root:
@@ -275,13 +294,13 @@ Produces `webmcp-local-relay-<version>.mcpb` for distribution via Claude Desktop
275
294
 
276
295
  ### Plugin and Skill Files
277
296
 
278
- | File | Purpose |
279
- |------|---------|
280
- | `.claude-plugin/plugin.json` | Claude Code plugin definition |
281
- | `.claude-plugin/marketplace.json` | Plugin marketplace metadata |
282
- | `.mcp.json` | MCP server configuration |
297
+ | File | Purpose |
298
+ | ------------------------------------ | ------------------------------- |
299
+ | `.claude-plugin/plugin.json` | Claude Code plugin definition |
300
+ | `.claude-plugin/marketplace.json` | Plugin marketplace metadata |
301
+ | `.mcp.json` | MCP server configuration |
283
302
  | `skills/webmcp-local-relay/SKILL.md` | Claude Code skill documentation |
284
- | `manifest.json` | MCPB bundle manifest |
303
+ | `manifest.json` | MCPB bundle manifest |
285
304
 
286
305
  ### References
287
306
 
@@ -1 +1 @@
1
- (function(){function e(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}let t=`[data-webmcp-relay]`,n=`__webmcp_relay_tab_id`,r=null,i;function a(){return document.currentScript instanceof HTMLScriptElement?document.currentScript:null}let o=a(),s=o?o.hasAttribute(`data-debug`):!1;function c(...e){s&&console.warn(`[webmcp-relay-embed]`,...e)}function l(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function u(){try{let e=sessionStorage.getItem(n);if(e)return e}catch(e){c(`sessionStorage read failed, tab ID will not persist:`,e)}let e=l();try{sessionStorage.setItem(n,e)}catch(e){c(`sessionStorage write failed:`,e)}return e}function d(e){if(e?.src)try{return new URL(`widget.html`,e.src).href}catch(e){c(`Failed to resolve widget URL from script src, falling back to CDN:`,e)}else c(`Script element has no src attribute, falling back to CDN widget URL.`);return`https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay/dist/browser/widget.html`}function f(e){let t=d(e),n=e?.getAttribute(`data-relay-id`)||void 0,r=e?.getAttribute(`data-relay-workspace`)||void 0;return{autoConnect:e?.getAttribute(`data-auto-connect`)!==`false`,relayHost:e?.getAttribute(`data-relay-host`)||`127.0.0.1`,relayPort:e?.getAttribute(`data-relay-port`)||`9333`,...n?{relayId:n}:{},...r?{relayWorkspace:r}:{},tabId:u(),widgetUrl:t,widgetOrigin:new URL(t).origin}}function p(t){if(typeof t!=`string`||t.length===0)return{type:`object`,properties:{}};try{let n=JSON.parse(t);return e(n)?n:{type:`object`,properties:{}}}catch(e){return c(`Tool inputSchema is not valid JSON:`,typeof t==`string`?t.slice(0,200):t,e),{type:`object`,properties:{}}}}function m(t){return e(t)?t:(t!=null&&c(`Tool invocation args must be an object, got`,typeof t),{})}function h(e){return{name:e.name,description:e.description,inputSchema:e.inputSchema}}function g(e){return{name:e.name,description:e.description,inputSchema:p(e.inputSchema)}}function _(){let e=navigator.modelContext;if(e&&typeof e.listTools==`function`&&typeof e.callTool==`function`)return e}function v(){let t=_();if(t)return{listTools(){return t.listTools().map(h)},invoke(e,n){return t.callTool({name:e,arguments:n})}};let n=navigator.modelContextTesting;return n&&typeof n.listTools==`function`&&typeof n.executeTool==`function`?{listTools(){return n.listTools().map(g)},async invoke(t,r){let i=await n.executeTool(t,JSON.stringify(r));if(i===null)return{isError:!0,content:[{type:`text`,text:`Tool execution interrupted by navigation`}]};let a;try{a=JSON.parse(i)}catch{throw Error(`Testing tool returned invalid JSON: ${String(i).slice(0,200)}`)}if(!e(a))throw Error(`Testing tool response was not an object`);return a}}:(c(`No WebMCP runtime found (navigator.modelContext or navigator.modelContextTesting).`),null)}let y=!1;function b(){y||!r||(y=!0,setTimeout(()=>{if(y=!1,!r)return;let e=v();(e?Promise.resolve(e.listTools()):Promise.resolve([])).then(e=>{r&&r.postMessage({type:`webmcp.tools.changed`,tools:Array.isArray(e)?e:[]},i.widgetOrigin)}).catch(e=>{c(`Failed to push tool changes:`,e)})},0))}function x(){let e=_();if(e)try{return e.addEventListener(`toolschanged`,b),!0}catch(e){c(`addEventListener threw:`,e)}let t=navigator.modelContextTesting;if(t&&typeof t.registerToolsChangedCallback==`function`)try{return t.registerToolsChangedCallback(b),!0}catch(e){c(`Failed to subscribe via registerToolsChangedCallback:`,e)}return!1}function S(){if(x())return;let e=0,t=setInterval(()=>{if(e++,x()){clearInterval(t);return}e>=50&&(clearInterval(t),c(`Could not subscribe to tool changes after 50 retries. Dynamic tool updates will not be relayed.`))},100)}function C(e,t,n){!e||typeof e!=`object`||!(`postMessage`in e)||e.postMessage(n,t)}function w(t){return!e(t)||typeof t.requestId!=`string`||typeof t.type!=`string`?null:{requestId:t.requestId,type:t.type,toolName:t.toolName,args:t.args}}function T(e,t){let n=v();(n?Promise.resolve(n.listTools()):Promise.resolve([])).then(n=>{C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:Array.isArray(n)?n:[]})}).catch(n=>{c(`Failed to list tools:`,n),C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:[],error:`Failed to list tools: ${n instanceof Error?n.message:String(n)}`})})}function E(t,n){let r=v();if(!r){C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:`No WebMCP runtime found on this page`});return}Promise.resolve(r.invoke(String(t.toolName??``),m(t.args))).then(r=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.response`,requestId:t.requestId,result:e(r)?r:{}})}).catch(e=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:String(e instanceof Error?e.message:e)})})}function D(e){if(document.querySelector(t))return;let n=new URLSearchParams;n.set(`tabId`,e.tabId),n.set(`hostOrigin`,window.location.origin);let i=new URL(window.location.href);i.search=``,i.hash=``,n.set(`hostUrl`,i.href),n.set(`hostTitle`,document.title||``),n.set(`relayHost`,e.relayHost),n.set(`relayPort`,e.relayPort),n.set(`autoConnect`,e.autoConnect?`true`:`false`),e.relayId&&n.set(`relayId`,e.relayId),e.relayWorkspace&&n.set(`relayWorkspace`,e.relayWorkspace);let a=document.createElement(`iframe`);a.src=`${e.widgetUrl}?${n.toString()}`,a.style.display=`none`,a.setAttribute(`aria-hidden`,`true`),a.setAttribute(`data-webmcp-relay`,`1`),a.setAttribute(`allow`,`loopback-network; local-network; local-network-access`),document.body.appendChild(a),r=a.contentWindow,a.addEventListener(`load`,()=>{r=a.contentWindow}),a.addEventListener(`error`,()=>{console.error(`[webmcp-relay-embed] Failed to load relay widget iframe from:`,a.src,`-- WebMCP tools will NOT be relayed. Check network connectivity and widget URL.`)})}if(!document.querySelector(t)){try{i=f(o)}catch(e){throw console.error(`[webmcp-relay-embed] Failed to initialize relay configuration:`,e),e}window.addEventListener(`message`,t=>{if(t.origin!==i.widgetOrigin||!r||t.source!==r)return;let n=t.data;if(e(n)&&n.type===`webmcp.reload`){window.location.reload();return}let a=w(t.data);if(a){if(a.type===`webmcp.tools.list.request`){T(a,t);return}a.type===`webmcp.tools.invoke.request`&&E(a,t)}}),document.body?D(i):document.addEventListener(`DOMContentLoaded`,()=>D(i),{once:!0}),S()}})();
1
+ (function(){function e(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}let t=`[data-webmcp-relay]`,n=`__webmcp_relay_tab_id`,r=null,i;function a(){return document.currentScript instanceof HTMLScriptElement?document.currentScript:null}let o=a(),s=o?o.hasAttribute(`data-debug`):!1;function c(...e){s&&console.warn(`[webmcp-relay-embed]`,...e)}function l(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function u(){try{let e=sessionStorage.getItem(n);if(e)return e}catch(e){c(`sessionStorage read failed, tab ID will not persist:`,e)}let e=l();try{sessionStorage.setItem(n,e)}catch(e){c(`sessionStorage write failed:`,e)}return e}function d(e){if(e?.src)try{return new URL(`widget.html`,e.src).href}catch(e){c(`Failed to resolve widget URL from script src, falling back to CDN:`,e)}else c(`Script element has no src attribute, falling back to CDN widget URL.`);return`https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay/dist/browser/widget.html`}function f(e){let t=d(e),n=e?.getAttribute(`data-relay-id`)||void 0,r=e?.getAttribute(`data-relay-workspace`)||void 0,i=e?.getAttribute(`data-request-timeout`)||void 0;return{autoConnect:e?.getAttribute(`data-auto-connect`)!==`false`,relayHost:e?.getAttribute(`data-relay-host`)||`127.0.0.1`,relayPort:e?.getAttribute(`data-relay-port`)||`9333`,...n?{relayId:n}:{},...r?{relayWorkspace:r}:{},...i?{requestTimeout:i}:{},tabId:u(),widgetUrl:t,widgetOrigin:new URL(t).origin}}function p(t){if(typeof t!=`string`||t.length===0)return{type:`object`,properties:{}};try{let n=JSON.parse(t);return e(n)?n:{type:`object`,properties:{}}}catch(e){return c(`Tool inputSchema is not valid JSON:`,typeof t==`string`?t.slice(0,200):t,e),{type:`object`,properties:{}}}}function m(t){return e(t)?t:(t!=null&&c(`Tool invocation args must be an object, got`,typeof t),{})}function h(e){return{name:e.name,description:e.description,inputSchema:e.inputSchema}}function g(e){return{name:e.name,description:e.description,inputSchema:p(e.inputSchema)}}function _(){let e=navigator.modelContext;if(e&&typeof e.listTools==`function`&&typeof e.callTool==`function`)return e}function v(){let t=_();if(t)return{listTools(){return t.listTools().map(h)},invoke(e,n){return t.callTool({name:e,arguments:n})}};let n=navigator.modelContextTesting;return n&&typeof n.listTools==`function`&&typeof n.executeTool==`function`?{listTools(){return n.listTools().map(g)},async invoke(t,r){let i=await n.executeTool(t,JSON.stringify(r));if(i===null)return{isError:!0,content:[{type:`text`,text:`Tool execution interrupted by navigation`}]};let a;try{a=JSON.parse(i)}catch{throw Error(`Testing tool returned invalid JSON: ${String(i).slice(0,200)}`)}if(!e(a))throw Error(`Testing tool response was not an object`);return a}}:(c(`No WebMCP runtime found (navigator.modelContext or navigator.modelContextTesting).`),null)}let y=!1;function b(){y||!r||(y=!0,setTimeout(()=>{if(y=!1,!r)return;let e=v();(e?Promise.resolve(e.listTools()):Promise.resolve([])).then(e=>{r&&r.postMessage({type:`webmcp.tools.changed`,tools:Array.isArray(e)?e:[]},i.widgetOrigin)}).catch(e=>{c(`Failed to push tool changes:`,e)})},0))}function x(){let e=_();if(e)try{return e.addEventListener(`toolchange`,b),!0}catch(e){c(`addEventListener threw:`,e)}let t=navigator.modelContextTesting;if(t&&typeof t.registerToolsChangedCallback==`function`)try{return t.registerToolsChangedCallback(b),!0}catch(e){c(`Failed to subscribe via registerToolsChangedCallback:`,e)}return!1}function S(){if(x())return;let e=0,t=100,n=()=>{setTimeout(()=>{if(e++,!x()){if(e>=40){c(`Could not subscribe to tool changes after 40 retries. Dynamic tool updates will not be relayed.`);return}t=Math.min(Math.round(t*1.5),1e3),n()}},t)};n()}function C(e,t,n){!e||typeof e!=`object`||!(`postMessage`in e)||e.postMessage(n,t)}function w(t){return!e(t)||typeof t.requestId!=`string`||typeof t.type!=`string`?null:{requestId:t.requestId,type:t.type,toolName:t.toolName,args:t.args}}function T(e,t){let n=v();(n?Promise.resolve(n.listTools()):Promise.resolve([])).then(n=>{C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:Array.isArray(n)?n:[]})}).catch(n=>{c(`Failed to list tools:`,n),C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:[],error:`Failed to list tools: ${n instanceof Error?n.message:String(n)}`})})}let E=!1;function D(t,n){if(E)return;let r=_();if(!r||typeof r.elicitInput!=`function`){c(`Elicitation bridge not installed: elicitInput not available on modelContext`);return}r.elicitInput=(r,i)=>{let a=k();return new Promise(i=>{let o=()=>{window.removeEventListener(`message`,l),clearTimeout(s)},s=setTimeout(()=>{o(),c(`Elicitation request timed out after 60s`),i({action:`decline`,content:null})},6e4),l=t=>{if(t.origin!==n)return;let r=t.data;!e(r)||r.type!==`webmcp.elicitation.response`||r.callId!==a||(o(),i(e(r.result)?r.result:{action:`decline`,content:null}))};window.addEventListener(`message`,l),t.postMessage({type:`webmcp.elicitation.request`,callId:a,params:r},n)})},E=!0,c(`Elicitation bridge installed`)}let O=0;function k(){return O+=1,`elicit_${String(Date.now())}_${String(O)}`}function A(t,n){let r=v();if(!r){C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:`No WebMCP runtime found on this page`});return}n.source&&D(n.source,n.origin),Promise.resolve(r.invoke(String(t.toolName??``),m(t.args))).then(r=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.response`,requestId:t.requestId,result:e(r)?r:{}})}).catch(e=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:String(e instanceof Error?e.message:e)})})}async function j(e){if(document.querySelector(t))return;let n=new URLSearchParams;n.set(`tabId`,e.tabId),n.set(`hostOrigin`,window.location.origin);let a=new URL(window.location.href);a.search=``,a.hash=``,n.set(`hostUrl`,a.href),n.set(`hostTitle`,document.title||``),n.set(`relayHost`,e.relayHost),n.set(`relayPort`,e.relayPort),n.set(`autoConnect`,e.autoConnect?`true`:`false`),e.relayId&&n.set(`relayId`,e.relayId),e.relayWorkspace&&n.set(`relayWorkspace`,e.relayWorkspace),e.requestTimeout&&n.set(`requestTimeout`,e.requestTimeout);let o=null;try{let t=await fetch(e.widgetUrl);if(!t.ok)console.warn(`[webmcp-relay-embed] Widget HTML fetch returned ${String(t.status)}; falling back to direct iframe src.`);else if(t.ok){let e=await t.text(),r=`<script>window.__WEBMCP_RELAY_CONFIG=${JSON.stringify(Object.fromEntries(n))};<\/script>`,a=new Blob([e.replace(`</head>`,`${r}</head>`)],{type:`text/html`});o=URL.createObjectURL(a),i.widgetOrigin=window.location.origin}}catch(e){c(`Failed to fetch widget HTML for blob URL:`,e)}let s=document.createElement(`iframe`);s.src=o??`${e.widgetUrl}?${n.toString()}`,s.style.display=`none`,s.setAttribute(`aria-hidden`,`true`),s.setAttribute(`data-webmcp-relay`,`1`),s.setAttribute(`allow`,`loopback-network; local-network; local-network-access`),document.body.appendChild(s),r=s.contentWindow,s.addEventListener(`load`,()=>{r=s.contentWindow,o&&URL.revokeObjectURL(o)}),s.addEventListener(`error`,()=>{console.error(`[webmcp-relay-embed] Failed to load relay widget iframe from:`,s.src,`-- WebMCP tools will NOT be relayed. Check network connectivity and widget URL.`),o&&URL.revokeObjectURL(o)})}if(!document.querySelector(t)){try{i=f(o)}catch(e){throw console.error(`[webmcp-relay-embed] Failed to initialize relay configuration:`,e),e}window.addEventListener(`message`,t=>{if(t.origin!==i.widgetOrigin||!r||t.source!==r)return;let n=t.data;if(e(n)&&n.type===`webmcp.reload`){window.location.reload();return}let a=w(t.data);if(a){if(a.type===`webmcp.tools.list.request`){T(a,t);return}a.type===`webmcp.tools.invoke.request`&&A(a,t)}});let t=()=>{j(i).catch(e=>{console.error(`[webmcp-relay-embed] Failed to inject relay widget:`,e)})};document.body?t():document.addEventListener(`DOMContentLoaded`,t,{once:!0}),S()}})();
@@ -5,6 +5,6 @@
5
5
  <title>WebMCP Relay Widget</title>
6
6
  </head>
7
7
  <body>
8
- <script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=t.get(`hostOrigin`);if(!i)return null;let a=t.get(`hostUrl`)||i,o=t.get(`hostTitle`)||``,s=t.get(`tabId`)||r(),c=t.get(`relayHost`)||`127.0.0.1`,l=t.get(`relayPort`),u=l&&l.length>0?Number.parseInt(l,10):9333,d=t.get(`autoConnect`)!==`false`,f=t.get(`relayId`)||void 0,p=t.get(`relayWorkspace`)||void 0;return n(c)?!Number.isInteger(u)||u<1||u>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,l),null):{autoConnect:d,hostOrigin:i,hostTitle:o,hostUrl:a,relayHostHint:c,relayPortHint:u,...f?{relayId:f}:{},...p?{relayWorkspace:p}:{},tabId:s}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,c),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}_(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function p(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(f(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function m(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(f(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function h(e){let t=p(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function g(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function _(e){let n=new Map,o=null,c=null,u=0,f=!1,p=500,_=null,v=`idle`,y=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},b=e=>{v=e};function x(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},1e4);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let S=(n,r)=>{_&&(clearTimeout(_),_=null),o=r,c=n,p=500,f=!1,b(`connected`),m(e,r),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let l=d(c);if(l){o={hello:l,host:r.host,port:r.port};return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}x(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(f=!1,c=null,y(`WebSocket connection lost`),u+=1,u>=5){u=0,E();return}T()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),x(`webmcp.tools.list`,{}).then(t=>{let r=Array.isArray(t.tools)?t.tools:[];a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),a(n,JSON.stringify({type:`tools/list`,tools:r})),f=!0}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},C=async t=>{let n=await g({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(S(n.socket,r),!0)},w=async()=>{b(`discovering`);for(let t of h(e))if(await C(t))return u=0,!0;return!1},T=()=>{if(!o||_)return;b(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,C(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,E();return}T()})},t)},E=()=>{if(_)return;b(`rediscover`);let e=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,w().then(e=>{e||E()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&f&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&v!==`discovering`&&w();return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?w().then(e=>{e||E()}):b(`idle`)}u()})();</script>
8
+ <script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return t.get(e)??i?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`hostUrl`)||o,c=a(`hostTitle`)||``,l=a(`tabId`)||r(),u=a(`relayHost`)||`127.0.0.1`,d=a(`relayPort`),f=d&&d.length>0?Number.parseInt(d,10):9333,p=a(`autoConnect`)!==`false`,m=a(`relayId`)||void 0,h=a(`relayWorkspace`)||void 0,g=a(`requestTimeout`),_=g&&g.length>0?Number.parseInt(g,10):6e4;return n(u)?!Number.isInteger(f)||f<1||f>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,d),null):!Number.isInteger(_)||_<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,g),null):{autoConnect:p,hostOrigin:o,hostTitle:c,hostUrl:s,relayHostHint:u,relayPortHint:f,...m?{relayId:m}:{},...h?{relayWorkspace:h}:{},requestTimeoutMs:_,tabId:l}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,u),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}b(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function p(e){return!t(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function m(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function h(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(m(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function g(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(m(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function _(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(m(e))}catch{}}function v(e){let t=h(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function y(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function b(e){let n=new Map,o=null,c=null,u=0,m=!1,h=null,b=500,x=null,S=`idle`,C=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},w=e=>{S=e};function T(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},e.requestTimeoutMs);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let E=(n,r)=>{let s=[],l=()=>{h&&(clearTimeout(h),h=null)},v=()=>{a(n,JSON.stringify({type:`tools/list`,tools:s}))};x&&(clearTimeout(x),x=null),o=r,c=n,b=500,m=!1,w(`connected`),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let u=d(c);if(u){o={hello:u,host:r.host,port:r.port};return}if(f(c)){l(),m=!0,g(e,r),v();return}let h=p(c);if(h){l(),m=!1,_(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:h.message,reason:h.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,h.reason,h.message);try{n.close(1008,h.message)}catch{}return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type===`elicitation-response`){window.parent.postMessage({type:`webmcp.elicitation.response`,callId:c.callId,result:t(c.result)?c.result:{action:`decline`,content:null}},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}T(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(m=!1,c=null,l(),C(`WebSocket connection lost`),u+=1,u>=5){u=0,A();return}k()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),T(`webmcp.tools.list`,{}).then(t=>{s=Array.isArray(t.tools)?t.tools:[],a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),h=setTimeout(()=>{h=null,!(c!==n||n.readyState!==WebSocket.OPEN||m)&&(m=!0,g(e,r),v())},250)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},D=async t=>{let n=await y({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(E(n.socket,r),!0)},O=async()=>{w(`discovering`);for(let t of v(e))if(await D(t))return u=0,!0;return!1},k=()=>{if(!o||x)return;w(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,D(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,A();return}k()})},t)},A=()=>{if(x)return;w(`rediscover`);let e=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,O().then(e=>{e||A()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&m&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&S!==`discovering`&&O();return}if(t(i)&&i.type===`webmcp.elicitation.request`){c&&c.readyState===WebSocket.OPEN&&a(c,JSON.stringify({type:`elicitation-request`,callId:i.callId,params:t(i.params)?i.params:{}}));return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?O().then(e=>{e||A()}):w(`idle`)}u()})();</script>
9
9
  </body>
10
10
  </html>
@@ -1 +1 @@
1
- (function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=t.get(`hostOrigin`);if(!i)return null;let a=t.get(`hostUrl`)||i,o=t.get(`hostTitle`)||``,s=t.get(`tabId`)||r(),c=t.get(`relayHost`)||`127.0.0.1`,l=t.get(`relayPort`),u=l&&l.length>0?Number.parseInt(l,10):9333,d=t.get(`autoConnect`)!==`false`,f=t.get(`relayId`)||void 0,p=t.get(`relayWorkspace`)||void 0;return n(c)?!Number.isInteger(u)||u<1||u>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,l),null):{autoConnect:d,hostOrigin:i,hostTitle:o,hostUrl:a,relayHostHint:c,relayPortHint:u,...f?{relayId:f}:{},...p?{relayWorkspace:p}:{},tabId:s}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,c),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}_(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function p(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(f(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function m(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(f(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function h(e){let t=p(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function g(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function _(e){let n=new Map,o=null,c=null,u=0,f=!1,p=500,_=null,v=`idle`,y=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},b=e=>{v=e};function x(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},1e4);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let S=(n,r)=>{_&&(clearTimeout(_),_=null),o=r,c=n,p=500,f=!1,b(`connected`),m(e,r),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let l=d(c);if(l){o={hello:l,host:r.host,port:r.port};return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}x(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(f=!1,c=null,y(`WebSocket connection lost`),u+=1,u>=5){u=0,E();return}T()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),x(`webmcp.tools.list`,{}).then(t=>{let r=Array.isArray(t.tools)?t.tools:[];a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),a(n,JSON.stringify({type:`tools/list`,tools:r})),f=!0}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},C=async t=>{let n=await g({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(S(n.socket,r),!0)},w=async()=>{b(`discovering`);for(let t of h(e))if(await C(t))return u=0,!0;return!1},T=()=>{if(!o||_)return;b(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,C(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,E();return}T()})},t)},E=()=>{if(_)return;b(`rediscover`);let e=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,w().then(e=>{e||E()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&f&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&v!==`discovering`&&w();return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?w().then(e=>{e||E()}):b(`idle`)}u()})();
1
+ (function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return t.get(e)??i?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`hostUrl`)||o,c=a(`hostTitle`)||``,l=a(`tabId`)||r(),u=a(`relayHost`)||`127.0.0.1`,d=a(`relayPort`),f=d&&d.length>0?Number.parseInt(d,10):9333,p=a(`autoConnect`)!==`false`,m=a(`relayId`)||void 0,h=a(`relayWorkspace`)||void 0,g=a(`requestTimeout`),_=g&&g.length>0?Number.parseInt(g,10):6e4;return n(u)?!Number.isInteger(f)||f<1||f>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,d),null):!Number.isInteger(_)||_<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,g),null):{autoConnect:p,hostOrigin:o,hostTitle:c,hostUrl:s,relayHostHint:u,relayPortHint:f,...m?{relayId:m}:{},...h?{relayWorkspace:h}:{},requestTimeoutMs:_,tabId:l}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,u),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}b(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function p(e){return!t(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function m(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function h(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(m(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function g(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(m(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function _(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(m(e))}catch{}}function v(e){let t=h(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function y(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function b(e){let n=new Map,o=null,c=null,u=0,m=!1,h=null,b=500,x=null,S=`idle`,C=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},w=e=>{S=e};function T(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},e.requestTimeoutMs);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let E=(n,r)=>{let s=[],l=()=>{h&&(clearTimeout(h),h=null)},v=()=>{a(n,JSON.stringify({type:`tools/list`,tools:s}))};x&&(clearTimeout(x),x=null),o=r,c=n,b=500,m=!1,w(`connected`),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let u=d(c);if(u){o={hello:u,host:r.host,port:r.port};return}if(f(c)){l(),m=!0,g(e,r),v();return}let h=p(c);if(h){l(),m=!1,_(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:h.message,reason:h.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,h.reason,h.message);try{n.close(1008,h.message)}catch{}return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type===`elicitation-response`){window.parent.postMessage({type:`webmcp.elicitation.response`,callId:c.callId,result:t(c.result)?c.result:{action:`decline`,content:null}},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}T(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(m=!1,c=null,l(),C(`WebSocket connection lost`),u+=1,u>=5){u=0,A();return}k()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),T(`webmcp.tools.list`,{}).then(t=>{s=Array.isArray(t.tools)?t.tools:[],a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),h=setTimeout(()=>{h=null,!(c!==n||n.readyState!==WebSocket.OPEN||m)&&(m=!0,g(e,r),v())},250)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},D=async t=>{let n=await y({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(E(n.socket,r),!0)},O=async()=>{w(`discovering`);for(let t of v(e))if(await D(t))return u=0,!0;return!1},k=()=>{if(!o||x)return;w(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,D(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,A();return}k()})},t)},A=()=>{if(x)return;w(`rediscover`);let e=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,O().then(e=>{e||A()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&m&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&S!==`discovering`&&O();return}if(t(i)&&i.type===`webmcp.elicitation.request`){c&&c.readyState===WebSocket.OPEN&&a(c,JSON.stringify({type:`elicitation-request`,callId:i.callId,params:t(i.params)?i.params:{}}));return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?O().then(e=>{e||A()}):w(`idle`)}u()})();
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as LocalRelayMcpServer, w as parseCliOptions } from "./mcpRelayServer-rbay_DSr.js";
2
+ import { n as parseCliOptions, t as LocalRelayMcpServer } from "./mcpRelayServer-BL8kNwIK.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  let options;
@@ -52,6 +52,15 @@ const shutdown = async (signal) => {
52
52
  process.exit(0);
53
53
  };
54
54
  let shuttingDown = false;
55
+ process.on("uncaughtException", (err) => {
56
+ process.stderr.write(`[webmcp-local-relay] error: uncaught exception: ${err.stack ?? err.message}\n`);
57
+ shutdown("uncaughtException");
58
+ });
59
+ process.on("unhandledRejection", (reason) => {
60
+ const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
61
+ process.stderr.write(`[webmcp-local-relay] error: unhandled rejection: ${message}\n`);
62
+ shutdown("unhandledRejection");
63
+ });
55
64
  process.on("SIGINT", () => {
56
65
  shutdown("SIGINT");
57
66
  });
@@ -61,4 +70,4 @@ process.on("SIGTERM", () => {
61
70
 
62
71
  //#endregion
63
72
  export { };
64
- //# sourceMappingURL=cli.js.map
73
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { parseCliOptions } from './cli-utils.js';\nimport { LocalRelayMcpServer } from './mcpRelayServer.js';\n\nlet options: ReturnType<typeof parseCliOptions>;\ntry {\n options = parseCliOptions(process.argv.slice(2));\n} catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n process.exit(1);\n}\n\nif (options.allowedOrigins.includes('*')) {\n process.stderr.write(\n '[webmcp-local-relay] WARNING: accepting connections from ALL host page origins. Use --widget-origin to restrict.\\n'\n );\n}\n\nconst bridgeOptions = {\n host: options.host,\n port: options.port,\n portExplicitlySet: options.portExplicitlySet,\n allowedOrigins: options.allowedOrigins,\n ...(options.label ? { label: options.label } : {}),\n ...(options.relayId ? { relayId: options.relayId } : {}),\n ...(options.workspace ? { workspace: options.workspace } : {}),\n};\n\nconst relay = new LocalRelayMcpServer({\n bridgeOptions,\n});\n\ntry {\n await relay.start();\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('EACCES')) {\n process.stderr.write(\n `[webmcp-local-relay] error: insufficient permissions to bind to ${options.host}:${options.port}.\\n`\n );\n } else {\n process.stderr.write(`[webmcp-local-relay] error: failed to start bridge server: ${message}\\n`);\n }\n process.exit(1);\n}\n\ntry {\n await relay.startStdio();\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[webmcp-local-relay] error: failed to start stdio transport: ${message}\\n`);\n process.exit(1);\n}\n\nif (relay.bridge.mode === 'server') {\n process.stderr.write(\n `[webmcp-local-relay] server mode: listening on ws://${options.host}:${relay.bridge.port} (allowed origins: ${options.allowedOrigins.join(', ')})\\n`\n );\n} else {\n process.stderr.write(\n `[webmcp-local-relay] client mode: proxying through existing relay at ws://${options.host}:${relay.bridge.port}\\n`\n );\n}\n\n/**\n * Gracefully shuts down bridge and MCP server for process termination signals.\n */\nconst shutdown = async (signal: string) => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n process.stderr.write(`[webmcp-local-relay] received ${signal}, shutting down\\n`);\n try {\n await relay.stop();\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error during shutdown: ${err instanceof Error ? err.message : err}\\n`\n );\n process.exit(1);\n }\n process.exit(0);\n};\n\nlet shuttingDown = false;\n\nprocess.on('uncaughtException', (err) => {\n process.stderr.write(\n `[webmcp-local-relay] error: uncaught exception: ${err.stack ?? err.message}\\n`\n );\n void shutdown('uncaughtException');\n});\nprocess.on('unhandledRejection', (reason) => {\n const message = reason instanceof Error ? (reason.stack ?? reason.message) : String(reason);\n process.stderr.write(`[webmcp-local-relay] error: unhandled rejection: ${message}\\n`);\n void shutdown('unhandledRejection');\n});\n\nprocess.on('SIGINT', () => {\n void shutdown('SIGINT');\n});\nprocess.on('SIGTERM', () => {\n void shutdown('SIGTERM');\n});\n"],"mappings":";;;;AAKA,IAAI;AACJ,IAAI;AACF,WAAU,gBAAgB,QAAQ,KAAK,MAAM,EAAE,CAAC;SACzC,KAAK;AACZ,SAAQ,OAAO,MACb,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC,IACjF;AACD,SAAQ,KAAK,EAAE;;AAGjB,IAAI,QAAQ,eAAe,SAAS,IAAI,CACtC,SAAQ,OAAO,MACb,qHACD;AAaH,MAAM,QAAQ,IAAI,oBAAoB,EACpC,eAXoB;CACpB,MAAM,QAAQ;CACd,MAAM,QAAQ;CACd,mBAAmB,QAAQ;CAC3B,gBAAgB,QAAQ;CACxB,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,OAAO,GAAG,EAAE;CACjD,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;CACvD,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,WAAW,GAAG,EAAE;CAC9D,EAIA,CAAC;AAEF,IAAI;AACF,OAAM,MAAM,OAAO;SACZ,KAAK;CACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,KAAI,QAAQ,SAAS,SAAS,CAC5B,SAAQ,OAAO,MACb,mEAAmE,QAAQ,KAAK,GAAG,QAAQ,KAAK,KACjG;KAED,SAAQ,OAAO,MAAM,8DAA8D,QAAQ,IAAI;AAEjG,SAAQ,KAAK,EAAE;;AAGjB,IAAI;AACF,OAAM,MAAM,YAAY;SACjB,KAAK;CACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAQ,OAAO,MAAM,gEAAgE,QAAQ,IAAI;AACjG,SAAQ,KAAK,EAAE;;AAGjB,IAAI,MAAM,OAAO,SAAS,SACxB,SAAQ,OAAO,MACb,uDAAuD,QAAQ,KAAK,GAAG,MAAM,OAAO,KAAK,qBAAqB,QAAQ,eAAe,KAAK,KAAK,CAAC,KACjJ;IAED,SAAQ,OAAO,MACb,6EAA6E,QAAQ,KAAK,GAAG,MAAM,OAAO,KAAK,IAChH;;;;AAMH,MAAM,WAAW,OAAO,WAAmB;AACzC,KAAI,aACF;AAEF,gBAAe;AACf,SAAQ,OAAO,MAAM,iCAAiC,OAAO,mBAAmB;AAChF,KAAI;AACF,QAAM,MAAM,MAAM;UACX,KAAK;AACZ,UAAQ,OAAO,MACb,+CAA+C,eAAe,QAAQ,IAAI,UAAU,IAAI,IACzF;AACD,UAAQ,KAAK,EAAE;;AAEjB,SAAQ,KAAK,EAAE;;AAGjB,IAAI,eAAe;AAEnB,QAAQ,GAAG,sBAAsB,QAAQ;AACvC,SAAQ,OAAO,MACb,mDAAmD,IAAI,SAAS,IAAI,QAAQ,IAC7E;AACD,CAAK,SAAS,oBAAoB;EAClC;AACF,QAAQ,GAAG,uBAAuB,WAAW;CAC3C,MAAM,UAAU,kBAAkB,QAAS,OAAO,SAAS,OAAO,UAAW,OAAO,OAAO;AAC3F,SAAQ,OAAO,MAAM,oDAAoD,QAAQ,IAAI;AACrF,CAAK,SAAS,qBAAqB;EACnC;AAEF,QAAQ,GAAG,gBAAgB;AACzB,CAAK,SAAS,SAAS;EACvB;AACF,QAAQ,GAAG,iBAAiB;AAC1B,CAAK,SAAS,UAAU;EACxB"}
@@ -1,6 +1,6 @@
1
+ import { EventEmitter } from "node:events";
1
2
  import { CallToolRequestParamsSchema, CallToolResult, CallToolResult as CallToolResult$1, CallToolResultSchema, Tool, Tool as Tool$1, ToolAnnotations, ToolAnnotations as ToolAnnotations$1, ToolAnnotationsSchema, ToolSchema } from "@modelcontextprotocol/sdk/types.js";
2
3
  import { z } from "zod/v4";
3
- import { EventEmitter } from "node:events";
4
4
  import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5
5
 
6
6
  //#region src/protocol.d.ts
@@ -198,6 +198,10 @@ declare const BrowserToRelayMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
198
198
  result: z.ZodUnknown;
199
199
  }, z.core.$strip>, z.ZodObject<{
200
200
  type: z.ZodLiteral<"pong">;
201
+ }, z.core.$strip>, z.ZodObject<{
202
+ type: z.ZodLiteral<"elicitation-request">;
203
+ callId: z.ZodString;
204
+ params: z.ZodRecord<z.ZodString, z.ZodUnknown>;
201
205
  }, z.core.$strip>]>;
202
206
  /**
203
207
  * Browser-to-relay message payload.
@@ -240,6 +244,28 @@ declare const ServerHelloMessageSchema: z.ZodObject<{
240
244
  * Server hello payload.
241
245
  */
242
246
  type ServerHelloMessage = z.infer<typeof ServerHelloMessageSchema>;
247
+ /**
248
+ * Schema for acceptance of a browser source hello message.
249
+ */
250
+ declare const RelayHelloAcceptedMessageSchema: z.ZodObject<{
251
+ type: z.ZodLiteral<"hello/accepted">;
252
+ }, z.core.$strip>;
253
+ /**
254
+ * Acceptance payload for a browser source hello message.
255
+ */
256
+ type RelayHelloAcceptedMessage = z.infer<typeof RelayHelloAcceptedMessageSchema>;
257
+ /**
258
+ * Schema for rejection of a browser source hello message.
259
+ */
260
+ declare const RelayHelloRejectedMessageSchema: z.ZodObject<{
261
+ type: z.ZodLiteral<"hello/rejected">;
262
+ reason: z.ZodString;
263
+ message: z.ZodString;
264
+ }, z.core.$strip>;
265
+ /**
266
+ * Rejection payload for a browser source hello message.
267
+ */
268
+ type RelayHelloRejectedMessage = z.infer<typeof RelayHelloRejectedMessageSchema>;
243
269
  /**
244
270
  * Union schema for all relay-to-browser protocol messages.
245
271
  */
@@ -253,6 +279,12 @@ declare const RelayToBrowserMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
253
279
  type: z.ZodLiteral<"server-hello">;
254
280
  service: z.ZodLiteral<"webmcp-local-relay">;
255
281
  version: z.ZodLiteral<1>;
282
+ }, z.core.$strip>, z.ZodObject<{
283
+ type: z.ZodLiteral<"hello/accepted">;
284
+ }, z.core.$strip>, z.ZodObject<{
285
+ type: z.ZodLiteral<"hello/rejected">;
286
+ reason: z.ZodString;
287
+ message: z.ZodString;
256
288
  }, z.core.$strip>, z.ZodObject<{
257
289
  type: z.ZodLiteral<"invoke">;
258
290
  callId: z.ZodString;
@@ -262,6 +294,10 @@ declare const RelayToBrowserMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
262
294
  type: z.ZodLiteral<"ping">;
263
295
  }, z.core.$strip>, z.ZodObject<{
264
296
  type: z.ZodLiteral<"reload">;
297
+ }, z.core.$strip>, z.ZodObject<{
298
+ type: z.ZodLiteral<"elicitation-response">;
299
+ callId: z.ZodString;
300
+ result: z.ZodRecord<z.ZodString, z.ZodUnknown>;
265
301
  }, z.core.$strip>]>;
266
302
  /**
267
303
  * Relay-to-browser message payload.
@@ -832,6 +868,10 @@ declare class RelayBridgeServer extends EventEmitter {
832
868
  * and browser-protocol messages to the existing browser handler.
833
869
  */
834
870
  private onSocketMessage;
871
+ /**
872
+ * Sends an elicitation response back to the browser source that requested it.
873
+ */
874
+ sendElicitationResponse(connectionId: string, callId: string, result: Record<string, unknown>): void;
835
875
  /**
836
876
  * Handles relay-protocol messages from relay client connections.
837
877
  */
@@ -868,6 +908,8 @@ declare class RelayBridgeServer extends EventEmitter {
868
908
  */
869
909
  private toWireTools;
870
910
  private isHostOriginAllowed;
911
+ private sendHelloAccepted;
912
+ private sendHelloRejected;
871
913
  /**
872
914
  * Validates browser tool results against CallToolResultSchema.
873
915
  * Non-conforming payloads are wrapped as error results with diagnostic text.
@@ -1055,5 +1097,5 @@ declare function buildPublicToolName(options: {
1055
1097
  disambiguate?: boolean;
1056
1098
  }): string;
1057
1099
  //#endregion
1058
- export { type AggregatedTool, type BrowserToRelayMessage, BrowserToRelayMessageSchema, CallToolRequestParamsSchema, type CallToolResult, CallToolResultSchema, type CliOptions, DEFAULT_TOOL_INPUT_SCHEMA, HelloRequiredError, InboundToolSchema, LocalRelayMcpServer, type LocalRelayMcpServerOptions, NormalizedToolSchema, RelayBridgeServer, type RelayBridgeServerOptions, type RelayCallToolResult, type RelayClientToServerMessage, RelayClientToServerMessageSchema, type RelayDescriptor, RelayDescriptorSchema, type RelayInvokeArgs, RelayInvokeArgsSchema, RelayRegistry, type RelayServerToClientMessage, RelayServerToClientMessageSchema, type RelaySourceInfo, RelaySourceInfoSchema, type RelayToBrowserMessage, RelayToBrowserMessageSchema, type RelayTool, type RelayToolAnnotations, type ResolvedInvocation, type ServerHelloMessage, ServerHelloMessageSchema, type SourceInfo, type Tool, type ToolAnnotations, ToolAnnotationsSchema, ToolSchema, buildPublicToolName, extractSanitizedDomain, normalizeInboundTool, parseCliOptions, printHelp, sanitizeName };
1059
- //# sourceMappingURL=index.d.ts.map
1100
+ export { type AggregatedTool, type BrowserToRelayMessage, BrowserToRelayMessageSchema, CallToolRequestParamsSchema, type CallToolResult, CallToolResultSchema, type CliOptions, DEFAULT_TOOL_INPUT_SCHEMA, HelloRequiredError, InboundToolSchema, LocalRelayMcpServer, type LocalRelayMcpServerOptions, NormalizedToolSchema, RelayBridgeServer, type RelayBridgeServerOptions, type RelayCallToolResult, type RelayClientToServerMessage, RelayClientToServerMessageSchema, type RelayDescriptor, RelayDescriptorSchema, type RelayHelloAcceptedMessage, RelayHelloAcceptedMessageSchema, type RelayHelloRejectedMessage, RelayHelloRejectedMessageSchema, type RelayInvokeArgs, RelayInvokeArgsSchema, RelayRegistry, type RelayServerToClientMessage, RelayServerToClientMessageSchema, type RelaySourceInfo, RelaySourceInfoSchema, type RelayToBrowserMessage, RelayToBrowserMessageSchema, type RelayTool, type RelayToolAnnotations, type ResolvedInvocation, type ServerHelloMessage, ServerHelloMessageSchema, type SourceInfo, type Tool, type ToolAnnotations, ToolAnnotationsSchema, ToolSchema, buildPublicToolName, extractSanitizedDomain, normalizeInboundTool, parseCliOptions, printHelp, sanitizeName };
1101
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/protocol.ts","../src/schemas.ts","../src/registry.ts","../src/bridgeServer.ts","../src/cli-utils.ts","../src/mcpRelayServer.ts","../src/naming.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { C as NormalizedToolSchema, D as normalizeInboundTool, E as ToolSchema, S as InboundToolSchema, T as ToolAnnotationsSchema, _ as extractSanitizedDomain, a as BrowserToRelayMessageSchema, b as CallToolResultSchema, c as RelayHelloAcceptedMessageSchema, d as RelaySourceInfoSchema, f as RelayToBrowserMessageSchema, g as buildPublicToolName, h as RelayRegistry, i as RelayBridgeServer, l as RelayHelloRejectedMessageSchema, m as HelloRequiredError, n as parseCliOptions, o as RelayClientToServerMessageSchema, p as ServerHelloMessageSchema, r as printHelp, s as RelayDescriptorSchema, t as LocalRelayMcpServer, u as RelayServerToClientMessageSchema, v as sanitizeName, w as RelayInvokeArgsSchema, x as DEFAULT_TOOL_INPUT_SCHEMA, y as CallToolRequestParamsSchema } from "./mcpRelayServer-BL8kNwIK.mjs";
2
+
3
+ export { BrowserToRelayMessageSchema, CallToolRequestParamsSchema, CallToolResultSchema, DEFAULT_TOOL_INPUT_SCHEMA, HelloRequiredError, InboundToolSchema, LocalRelayMcpServer, NormalizedToolSchema, RelayBridgeServer, RelayClientToServerMessageSchema, RelayDescriptorSchema, RelayHelloAcceptedMessageSchema, RelayHelloRejectedMessageSchema, RelayInvokeArgsSchema, RelayRegistry, RelayServerToClientMessageSchema, RelaySourceInfoSchema, RelayToBrowserMessageSchema, ServerHelloMessageSchema, ToolAnnotationsSchema, ToolSchema, buildPublicToolName, extractSanitizedDomain, normalizeInboundTool, parseCliOptions, printHelp, sanitizeName };