@mcp-b/webmcp-local-relay 2.3.2 → 3.0.0-beta.20260702171253

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
@@ -58,14 +58,14 @@ Add one script tag to expose your page's WebMCP tools to the relay:
58
58
  <script src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@latest/dist/browser/embed.js"></script>
59
59
  ```
60
60
 
61
- That's it. If your page already registers tools on `navigator.modelContext`, they'll be picked up automatically.
61
+ That's it. If your page already registers tools on `document.modelContext`, they'll be picked up automatically.
62
62
 
63
63
  New to WebMCP? Here's the full setup:
64
64
 
65
65
  ```html
66
66
  <script src="https://cdn.jsdelivr.net/npm/@mcp-b/global@latest/dist/index.iife.js"></script>
67
67
  <script>
68
- navigator.modelContext.registerTool({
68
+ document.modelContext.registerTool({
69
69
  name: 'get_page_title',
70
70
  description: 'Get the current page title',
71
71
  inputSchema: { type: 'object', properties: {} },
@@ -199,14 +199,14 @@ npx @mcp-b/webmcp-local-relay --widget-origin https://myapp.com
199
199
  │ Widget iframe │
200
200
  │ embed.js injects widget.html │
201
201
  └──────────────────┬───────────────────┘
202
- │ navigator.modelContext
202
+ │ document.modelContext
203
203
  ┌──────────────────▼───────────────────┐
204
204
  │ Host page │
205
205
  │ WebMCP runtime + registered tools │
206
206
  └──────────────────────────────────────┘
207
207
  ```
208
208
 
209
- **How it connects:** The embed script injects a hidden iframe into the host page. The iframe opens a WebSocket to the relay on `localhost`. Tools are discovered via `navigator.modelContext` (or `navigator.modelContextTesting` as fallback) and forwarded to the relay, which registers them as standard MCP tools over stdio.
209
+ **How it connects:** The embed script injects a hidden iframe into the host page. The iframe opens a WebSocket to the relay on `localhost`. Tools are discovered via `document.modelContext` (or `navigator.modelContextTesting` as fallback) and forwarded to the relay, which registers them as standard MCP tools over stdio.
210
210
  If the relay is temporarily unavailable, the widget reconnects automatically using exponential backoff (1.5x multiplier) from `500ms` up to `3000ms`, stopping after 100 attempts.
211
211
 
212
212
  **Client mode:** When a second relay instance starts and the port is already in use (`EADDRINUSE`), it automatically falls back to **client mode**. In client mode the relay connects as a WebSocket client to the existing server relay and proxies tool operations through it. If the server relay later stops, the client attempts to promote itself back to server mode. This enables multiple MCP clients to share the same browser connections without manual configuration.
@@ -220,7 +220,7 @@ Supported page runtimes:
220
220
 
221
221
  Runtime dispatch behavior in the browser embed/widget layer:
222
222
 
223
- - Uses `navigator.modelContext.listTools` + `callTool` when present.
223
+ - Uses `document.modelContext.listTools` + `callTool` when present.
224
224
  - Falls back to `navigator.modelContextTesting.listTools` + `executeTool`.
225
225
 
226
226
  ### WebMCP Standard Status
@@ -229,7 +229,7 @@ WebMCP is an emerging web platform proposal. This relay works today with polyfil
229
229
 
230
230
  - [W3C WebML CG draft](https://webmachinelearning.github.io/webmcp/)
231
231
  - [Proposal repository](https://github.com/webmachinelearning/webmcp)
232
- - [Proposal details (`navigator.modelContext`, `registerTool`, etc.)](https://github.com/webmachinelearning/webmcp/blob/main/docs/proposal.md)
232
+ - [Proposal details (`document.modelContext`, `registerTool`, etc.)](https://github.com/webmachinelearning/webmcp/blob/main/docs/proposal.md)
233
233
 
234
234
  For Chromium/Chrome Canary native preview testing:
235
235
 
@@ -294,13 +294,13 @@ Produces `webmcp-local-relay-<version>.mcpb` for distribution via Claude Desktop
294
294
 
295
295
  ### Plugin and Skill Files
296
296
 
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 |
302
- | `skills/webmcp-local-relay/SKILL.md` | Claude Code skill documentation |
303
- | `manifest.json` | MCPB bundle manifest |
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 |
302
+ | `.agents/skills/webmcp-local-relay/SKILL.md` | Claude Code skill workspace source |
303
+ | `manifest.json` | MCPB bundle manifest |
304
304
 
305
305
  ### References
306
306
 
@@ -1 +1,2 @@
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()}})();
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)}function y(){let e=v();return e?Promise.resolve(e.listTools()).then(e=>Array.isArray(e)?e:[]):Promise.resolve([])}let b=!1,x=null,S=``;function C(e){if(typeof e!=`object`||!e)return JSON.stringify(e)??`undefined`;if(Array.isArray(e))return`[${e.map(C).join(`,`)}]`;let t=e;return`{${Object.keys(t).sort().map(e=>`${JSON.stringify(e)}:${C(t[e])}`).join(`,`)}}`}function w(e){return e.map(C).sort().join(`
2
+ `)}function T(){b=!1,y().then(e=>{let t=w(e);t===S||!r||(S=t,r.postMessage({type:`webmcp.tools.changed`,tools:e},i.widgetOrigin))}).catch(e=>{c(`Failed to sync tool changes:`,e)})}function E(){b||(b=!0,setTimeout(T,0))}function D(){x||(x=setInterval(E,2e3))}function O(){let e=!1,t=_();if(t)try{t.addEventListener(`toolchange`,E),e=!0}catch(e){c(`addEventListener on modelContext threw:`,e)}let n=navigator.modelContextTesting;if(n){if(typeof n.addEventListener==`function`)try{n.addEventListener(`toolchange`,E),e=!0}catch(e){c(`addEventListener on modelContextTesting threw:`,e)}else if(typeof n.registerToolsChangedCallback==`function`)try{n.registerToolsChangedCallback(E),e=!0}catch(e){c(`Failed to subscribe via registerToolsChangedCallback:`,e)}}return e}function k(){if(D(),E(),O())return;let e=0,t=100,n=()=>{setTimeout(()=>{if(e++,!O()){if(e>=40){c(`Could not subscribe to tool changes after 40 retries. Dynamic tool updates will rely on polling.`);return}t=Math.min(Math.round(t*1.5),1e3),n()}},t)};n()}function A(e,t,n){!e||typeof e!=`object`||!(`postMessage`in e)||e.postMessage(n,t)}function j(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 M(e,t){y().then(n=>{A(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:n})}).catch(n=>{c(`Failed to list tools:`,n),A(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 N=!1;function P(t,n){if(N)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=I();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),A(t,n,{type:`webmcp.elicitation.request`,callId:a,params:r})})},N=!0,c(`Elicitation bridge installed`)}let F=0;function I(){return F+=1,`elicit_${String(Date.now())}_${String(F)}`}function L(t,n){let r=v();if(!r){A(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:`No WebMCP runtime found on this page`});return}n.source&&P(n.source,n.origin),Promise.resolve(r.invoke(String(t.toolName??``),m(t.args))).then(r=>{A(n.source,n.origin,{type:`webmcp.tools.invoke.response`,requestId:t.requestId,result:e(r)?r:{}})}).catch(e=>{A(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:String(e instanceof Error?e.message:e)})})}async function R(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),e.requestTimeout&&n.set(`requestTimeout`,e.requestTimeout);let a=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{let r=await t.text(),i=`<script>window.__WEBMCP_RELAY_CONFIG=${JSON.stringify(Object.fromEntries(n))};<\/script>`,o=new Blob([r.replace(`</head>`,`${i}</head>`)],{type:`text/html`});a=URL.createObjectURL(o),e.widgetOrigin=window.location.origin}}catch(e){c(`Failed to fetch widget HTML for blob URL:`,e)}let o=document.createElement(`iframe`);o.src=a??`${e.widgetUrl}?${n.toString()}`,o.style.display=`none`,o.setAttribute(`aria-hidden`,`true`),o.setAttribute(`data-webmcp-relay`,`1`),o.setAttribute(`allow`,`loopback-network; local-network; local-network-access`),document.body.appendChild(o),r=o.contentWindow,o.addEventListener(`load`,()=>{r=o.contentWindow,a&&URL.revokeObjectURL(a)}),o.addEventListener(`error`,()=>{console.error(`[webmcp-relay-embed] Failed to load relay widget iframe from:`,o.src,`-- WebMCP tools will NOT be relayed. Check network connectivity and widget URL.`),a&&URL.revokeObjectURL(a)})}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=j(t.data);if(a){if(a.type===`webmcp.tools.list.request`){M(a,t);return}a.type===`webmcp.tools.invoke.request`&&L(a,t)}});let t=()=>{R(i).catch(e=>{console.error(`[webmcp-relay-embed] Failed to inject relay widget:`,e)})};document.body?t():document.addEventListener(`DOMContentLoaded`,t,{once:!0}),k(),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&r&&r.postMessage({type:`webmcp.connect`},i.widgetOrigin)})}})();
@@ -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=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>
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,c=[1e4,2e4,3e4],l=c.length;function u(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 d(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 f(){let e=u();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}S(e)}function p(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 m(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function h(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 g(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function _(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(g(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 v(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(g(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function y(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(g(e))}catch{}}function b(e){let t=_(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 x(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=p(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 S(e){let n=new Map,o=null,u=null,f=0,g=!1,S=null,C=500,w=null,T=`idle`,E=0,D=null,O=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))};function k(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 A=(n,r)=>{let s=[],c=()=>{S&&(clearTimeout(S),S=null)},l=()=>{a(n,JSON.stringify({type:`tools/list`,tools:s}))};w&&(clearTimeout(w),w=null),o=r,u=n,C=500,E=0,g=!1,T=`idle`,n.addEventListener(`message`,s=>{let u;try{u=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let d=p(u);if(d){o={hello:d,host:r.host,port:r.port};return}if(m(u)){c(),g=!0,v(e,r),l();return}let f=h(u);if(f){c(),g=!1,y(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:f.message,reason:f.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,f.reason,f.message);try{n.close(1008,f.message)}catch{}return}if(!(!t(u)||typeof u.type!=`string`)){if(u.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(u.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(u.type===`elicitation-response`){window.parent.postMessage({type:`webmcp.elicitation.response`,callId:u.callId,result:t(u.result)?u.result:{action:`decline`,content:null}},e.hostOrigin);return}if(u.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(u.type));return}k(`webmcp.tools.invoke`,{toolName:u.toolName,args:t(u.args)?u.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:u.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:u.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}}),n.addEventListener(`close`,()=>{if(u===n){if(g=!1,u=null,c(),O(`WebSocket connection lost`),f+=1,f>=5){f=0,P();return}N()}},{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)}}),k(`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})),S=setTimeout(()=>{S=null,!(u!==n||n.readyState!==WebSocket.OPEN||g)&&(g=!0,v(e,r),l())},250)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},j=async t=>{let n=await x({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):(A(n.socket,r),!0)},M=async()=>{T=`discovering`;try{for(let t of b(e))if(await j(t))return f=0,!0;return!1}finally{T===`discovering`&&(T=`idle`)}},N=()=>{if(!o||w)return;let e={host:o.host,port:o.port},t=Math.min(s,Math.round(C*(.85+Math.random()*.3)));C=Math.min(Math.round(C*1.5),s),w=setTimeout(()=>{w=null,j(e).then(e=>{if(e){f=0;return}if(f>=5){f=0,P();return}N()})},t)},P=()=>{if(w)return;if(E>=l){L();return}let e=c[E];w=setTimeout(()=>{w=null,M().then(e=>{e||(E+=1,P())})},e)},F=()=>{document.visibilityState===`visible`&&T===`dormant`&&z()},I=()=>{document.removeEventListener(`visibilitychange`,F),D!==null&&(clearInterval(D),D=null)},L=()=>{T!==`dormant`&&(T=`dormant`,w&&(clearTimeout(w),w=null),document.addEventListener(`visibilitychange`,F),D=setInterval(()=>{R()},12e4))},R=async()=>{if(T!==`dormant`)return;let t=new Set,n=[],r=(e,r)=>{let i=`${e}:${String(r)}`;t.has(i)||(t.add(i),n.push({host:e,port:r}))};e.relayPortHint!==void 0&&r(e.relayHostHint,e.relayPortHint);let i=_(e);if(i&&r(i.host,i.port),n.length!==0){T=`discovering`,I();for(let e of n)if(await j(e)){E=0;return}L()}},z=()=>{I(),C=500,E=0,M().then(e=>{e||L()})};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){u&&g&&a(u,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){T===`dormant`?z():!u&&T!==`discovering`&&M();return}if(t(i)&&i.type===`webmcp.elicitation.request`){u&&u.readyState===WebSocket.OPEN&&a(u,JSON.stringify({type:`elicitation-request`,callId:i.callId,params:t(i.params)?i.params:{}}));return}let o=d(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&&M().then(e=>{e||P()})}f()})();</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=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
+ (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,c=[1e4,2e4,3e4],l=c.length;function u(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 d(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 f(){let e=u();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}S(e)}function p(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 m(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function h(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 g(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function _(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(g(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 v(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(g(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function y(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(g(e))}catch{}}function b(e){let t=_(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 x(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=p(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 S(e){let n=new Map,o=null,u=null,f=0,g=!1,S=null,C=500,w=null,T=`idle`,E=0,D=null,O=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))};function k(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 A=(n,r)=>{let s=[],c=()=>{S&&(clearTimeout(S),S=null)},l=()=>{a(n,JSON.stringify({type:`tools/list`,tools:s}))};w&&(clearTimeout(w),w=null),o=r,u=n,C=500,E=0,g=!1,T=`idle`,n.addEventListener(`message`,s=>{let u;try{u=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let d=p(u);if(d){o={hello:d,host:r.host,port:r.port};return}if(m(u)){c(),g=!0,v(e,r),l();return}let f=h(u);if(f){c(),g=!1,y(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:f.message,reason:f.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,f.reason,f.message);try{n.close(1008,f.message)}catch{}return}if(!(!t(u)||typeof u.type!=`string`)){if(u.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(u.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(u.type===`elicitation-response`){window.parent.postMessage({type:`webmcp.elicitation.response`,callId:u.callId,result:t(u.result)?u.result:{action:`decline`,content:null}},e.hostOrigin);return}if(u.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(u.type));return}k(`webmcp.tools.invoke`,{toolName:u.toolName,args:t(u.args)?u.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:u.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:u.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}}),n.addEventListener(`close`,()=>{if(u===n){if(g=!1,u=null,c(),O(`WebSocket connection lost`),f+=1,f>=5){f=0,P();return}N()}},{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)}}),k(`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})),S=setTimeout(()=>{S=null,!(u!==n||n.readyState!==WebSocket.OPEN||g)&&(g=!0,v(e,r),l())},250)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},j=async t=>{let n=await x({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):(A(n.socket,r),!0)},M=async()=>{T=`discovering`;try{for(let t of b(e))if(await j(t))return f=0,!0;return!1}finally{T===`discovering`&&(T=`idle`)}},N=()=>{if(!o||w)return;let e={host:o.host,port:o.port},t=Math.min(s,Math.round(C*(.85+Math.random()*.3)));C=Math.min(Math.round(C*1.5),s),w=setTimeout(()=>{w=null,j(e).then(e=>{if(e){f=0;return}if(f>=5){f=0,P();return}N()})},t)},P=()=>{if(w)return;if(E>=l){L();return}let e=c[E];w=setTimeout(()=>{w=null,M().then(e=>{e||(E+=1,P())})},e)},F=()=>{document.visibilityState===`visible`&&T===`dormant`&&z()},I=()=>{document.removeEventListener(`visibilitychange`,F),D!==null&&(clearInterval(D),D=null)},L=()=>{T!==`dormant`&&(T=`dormant`,w&&(clearTimeout(w),w=null),document.addEventListener(`visibilitychange`,F),D=setInterval(()=>{R()},12e4))},R=async()=>{if(T!==`dormant`)return;let t=new Set,n=[],r=(e,r)=>{let i=`${e}:${String(r)}`;t.has(i)||(t.add(i),n.push({host:e,port:r}))};e.relayPortHint!==void 0&&r(e.relayHostHint,e.relayPortHint);let i=_(e);if(i&&r(i.host,i.port),n.length!==0){T=`discovering`,I();for(let e of n)if(await j(e)){E=0;return}L()}},z=()=>{I(),C=500,E=0,M().then(e=>{e||L()})};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){u&&g&&a(u,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){T===`dormant`?z():!u&&T!==`discovering`&&M();return}if(t(i)&&i.type===`webmcp.elicitation.request`){u&&u.readyState===WebSocket.OPEN&&a(u,JSON.stringify({type:`elicitation-request`,callId:i.callId,params:t(i.params)?i.params:{}}));return}let o=d(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&&M().then(e=>{e||P()})}f()})();
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as parseCliOptions, t as LocalRelayMcpServer } from "./mcpRelayServer-BL8kNwIK.mjs";
2
+ import { n as parseCliOptions, t as LocalRelayMcpServer } from "./mcpRelayServer-BT1ct1Cc.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  let options;
@@ -17,7 +17,8 @@ const relay = new LocalRelayMcpServer({ bridgeOptions: {
17
17
  allowedOrigins: options.allowedOrigins,
18
18
  ...options.label ? { label: options.label } : {},
19
19
  ...options.relayId ? { relayId: options.relayId } : {},
20
- ...options.workspace ? { workspace: options.workspace } : {}
20
+ ...options.workspace ? { workspace: options.workspace } : {},
21
+ ...options.maxPayloadBytes ? { maxPayloadBytes: options.maxPayloadBytes } : {}
21
22
  } });
22
23
  try {
23
24
  await relay.start();
package/dist/cli.mjs.map CHANGED
@@ -1 +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;CAVA,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;CAIhD,EACd,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
+ {"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 ...(options.maxPayloadBytes ? { maxPayloadBytes: options.maxPayloadBytes } : {}),\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;CACF,UAAU,gBAAgB,QAAQ,KAAK,MAAM,EAAE,CAAC;SACzC,KAAK;CACZ,QAAQ,OAAO,MACb,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC,IACjF;CACD,QAAQ,KAAK,EAAE;;AAGjB,IAAI,QAAQ,eAAe,SAAS,IAAI,EACtC,QAAQ,OAAO,MACb,qHACD;AAcH,MAAM,QAAQ,IAAI,oBAAoB,EACpC;CAXA,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;CAC7D,GAAI,QAAQ,kBAAkB,EAAE,iBAAiB,QAAQ,iBAAiB,GAAG,EAAE;CAIlE,EACd,CAAC;AAEF,IAAI;CACF,MAAM,MAAM,OAAO;SACZ,KAAK;CACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;CAChE,IAAI,QAAQ,SAAS,SAAS,EAC5B,QAAQ,OAAO,MACb,mEAAmE,QAAQ,KAAK,GAAG,QAAQ,KAAK,KACjG;MAED,QAAQ,OAAO,MAAM,8DAA8D,QAAQ,IAAI;CAEjG,QAAQ,KAAK,EAAE;;AAGjB,IAAI;CACF,MAAM,MAAM,YAAY;SACjB,KAAK;CACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;CAChE,QAAQ,OAAO,MAAM,gEAAgE,QAAQ,IAAI;CACjG,QAAQ,KAAK,EAAE;;AAGjB,IAAI,MAAM,OAAO,SAAS,UACxB,QAAQ,OAAO,MACb,uDAAuD,QAAQ,KAAK,GAAG,MAAM,OAAO,KAAK,qBAAqB,QAAQ,eAAe,KAAK,KAAK,CAAC,KACjJ;KAED,QAAQ,OAAO,MACb,6EAA6E,QAAQ,KAAK,GAAG,MAAM,OAAO,KAAK,IAChH;;;;AAMH,MAAM,WAAW,OAAO,WAAmB;CACzC,IAAI,cACF;CAEF,eAAe;CACf,QAAQ,OAAO,MAAM,iCAAiC,OAAO,mBAAmB;CAChF,IAAI;EACF,MAAM,MAAM,MAAM;UACX,KAAK;EACZ,QAAQ,OAAO,MACb,+CAA+C,eAAe,QAAQ,IAAI,UAAU,IAAI,IACzF;EACD,QAAQ,KAAK,EAAE;;CAEjB,QAAQ,KAAK,EAAE;;AAGjB,IAAI,eAAe;AAEnB,QAAQ,GAAG,sBAAsB,QAAQ;CACvC,QAAQ,OAAO,MACb,mDAAmD,IAAI,SAAS,IAAI,QAAQ,IAC7E;CACD,AAAK,SAAS,oBAAoB;EAClC;AACF,QAAQ,GAAG,uBAAuB,WAAW;CAC3C,MAAM,UAAU,kBAAkB,QAAS,OAAO,SAAS,OAAO,UAAW,OAAO,OAAO;CAC3F,QAAQ,OAAO,MAAM,oDAAoD,QAAQ,IAAI;CACrF,AAAK,SAAS,qBAAqB;EACnC;AAEF,QAAQ,GAAG,gBAAgB;CACzB,AAAK,SAAS,SAAS;EACvB;AACF,QAAQ,GAAG,iBAAiB;CAC1B,AAAK,SAAS,UAAU;EACxB"}
package/dist/index.d.mts CHANGED
@@ -459,6 +459,7 @@ declare const RelayServerToClientMessageSchema: z.ZodDiscriminatedUnion<[z.ZodOb
459
459
  uri: z.ZodString;
460
460
  description: z.ZodOptional<z.ZodString>;
461
461
  mimeType: z.ZodOptional<z.ZodString>;
462
+ size: z.ZodOptional<z.ZodNumber>;
462
463
  annotations: z.ZodOptional<z.ZodObject<{
463
464
  audience: z.ZodOptional<z.ZodArray<z.ZodEnum<{
464
465
  user: "user";
@@ -722,7 +723,7 @@ interface RelayBridgeServerOptions {
722
723
  * Allowed host page origins reported by browser `hello` messages.
723
724
  * Use `["*"]` to allow all origins.
724
725
  *
725
- * Permissive by default for zero-config developer experience — any browser
726
+ * Permissive by default for zero-config developer experience: any browser
726
727
  * page can connect and register tools without additional setup. For production
727
728
  * or shared machines, restrict to trusted host origins via the
728
729
  * `--widget-origin` CLI flag or by passing explicit origins here.
@@ -732,7 +733,7 @@ interface RelayBridgeServerOptions {
732
733
  allowedOrigins?: string[];
733
734
  /**
734
735
  * Maximum WebSocket payload size in bytes.
735
- * @defaultValue `1000000`
736
+ * @defaultValue `10000000`
736
737
  */
737
738
  maxPayloadBytes?: number;
738
739
  /**
@@ -882,6 +883,9 @@ declare class RelayBridgeServer extends EventEmitter {
882
883
  private pushToolsToRelayClients;
883
884
  /**
884
885
  * Handles source disconnection and rejects in-flight calls owned by that source.
886
+ *
887
+ * Guarded against double-call: both 'error' and 'close' events fire on
888
+ * socket failure, but cleanup (and the stateChanged emission) runs only once.
885
889
  */
886
890
  private onSocketClose;
887
891
  private startHeartbeat;
@@ -933,6 +937,7 @@ interface CliOptions {
933
937
  label?: string;
934
938
  workspace?: string;
935
939
  relayId?: string;
940
+ maxPayloadBytes?: number;
936
941
  }
937
942
  /**
938
943
  * Parses supported CLI flags for relay startup.
@@ -985,6 +990,7 @@ declare class LocalRelayMcpServer {
985
990
  private readonly dynamicToolSignature;
986
991
  private syncing;
987
992
  private syncRequested;
993
+ private syncDebounceTimer;
988
994
  private connected;
989
995
  /**
990
996
  * Creates a local relay MCP server with static and dynamic tool registration.
@@ -1040,6 +1046,15 @@ declare class LocalRelayMcpServer {
1040
1046
  * because cmd.exe interprets `&`, `|`, and other metacharacters in URLs.
1041
1047
  */
1042
1048
  private openInBrowser;
1049
+ /**
1050
+ * Debounces rapid stateChanged events into a single sync pass.
1051
+ *
1052
+ * Browser reconnections emit multiple stateChanged events in quick
1053
+ * succession (hello, tools/list). Without debouncing, each triggers a
1054
+ * full sync that removes and re-registers tools — sending per-operation
1055
+ * notifications through the stdio transport and overwhelming the agent.
1056
+ */
1057
+ private debouncedSyncDynamicTools;
1043
1058
  /**
1044
1059
  * Coalesces concurrent sync requests into a single serialized sync loop.
1045
1060
  */
@@ -1068,6 +1083,10 @@ declare class LocalRelayMcpServer {
1068
1083
  private dynamicToolDescription;
1069
1084
  /**
1070
1085
  * Produces a stable signature for change detection of dynamic tool metadata.
1086
+ *
1087
+ * Deliberately excludes `sourceId` (which is a per-connection UUID that
1088
+ * changes on every browser reconnect) so that a reconnecting tab with the
1089
+ * same tools does not trigger a needless remove+re-register cycle.
1071
1090
  */
1072
1091
  private toolSignature;
1073
1092
  /**
@@ -1 +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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"}
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 CHANGED
@@ -1,3 +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";
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-BT1ct1Cc.mjs";
2
2
 
3
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 };
@@ -617,6 +617,10 @@ const SUPPORTED_SUBPROTOCOLS = new Set([
617
617
  RELAY_DISCOVERY_PROTOCOL,
618
618
  RELAY_INTERNAL_PROTOCOL
619
619
  ]);
620
+ function formatPayloadLimit(bytes) {
621
+ if (bytes >= 1e6 && bytes % 1e6 === 0) return `${String(bytes / 1e6)}MB`;
622
+ return `${String(bytes)} bytes`;
623
+ }
620
624
  /**
621
625
  * WebSocket relay between browser widget frames and MCP server calls.
622
626
  *
@@ -682,7 +686,7 @@ var RelayBridgeServer = class extends EventEmitter {
682
686
  this.portRangeEnd = options.portRangeEnd ?? Math.max(9348, this.preferredPort);
683
687
  this.persistPath = options.persistPath ?? defaultRelayPortPersistPath();
684
688
  this.allowedOrigins = options.allowedOrigins ?? ["*"];
685
- this.maxPayloadBytes = options.maxPayloadBytes ?? 1e6;
689
+ this.maxPayloadBytes = options.maxPayloadBytes ?? 1e7;
686
690
  this.invokeTimeoutMs = options.invokeTimeoutMs ?? 25e3;
687
691
  this.label = options.label;
688
692
  this.workspace = options.workspace;
@@ -690,7 +694,7 @@ var RelayBridgeServer = class extends EventEmitter {
690
694
  this.instanceId = randomUUID();
691
695
  if (this.preferredPort !== 0 && (this.preferredPort < 1 || this.preferredPort > 65535)) throw new Error(`Invalid port ${this.preferredPort}. Port must be 0 (auto-assign) or between 1 and 65535.`);
692
696
  if (this.portRangeEnd < this.preferredPort && this.preferredPort !== 0) throw new Error(`Invalid port range ${this.preferredPort}-${this.portRangeEnd}. rangeEnd must be greater than or equal to the preferred port.`);
693
- if (this.maxPayloadBytes <= 0) throw new Error(`Invalid maxPayloadBytes ${this.maxPayloadBytes}. Must be greater than 0.`);
697
+ if (!Number.isInteger(this.maxPayloadBytes) || this.maxPayloadBytes <= 0) throw new Error(`Invalid maxPayloadBytes ${this.maxPayloadBytes}. Must be a positive integer.`);
694
698
  if (this.invokeTimeoutMs <= 0) throw new Error(`Invalid invokeTimeoutMs ${this.invokeTimeoutMs}. Must be greater than 0.`);
695
699
  }
696
700
  /**
@@ -889,12 +893,13 @@ var RelayBridgeServer = class extends EventEmitter {
889
893
  socket.on("message", (raw) => {
890
894
  this.onSocketMessage(connectionId, raw);
891
895
  });
892
- socket.on("close", () => {
893
- this.onSocketClose(connectionId);
896
+ socket.on("close", (code) => {
897
+ this.onSocketClose(connectionId, code);
894
898
  });
895
899
  socket.on("error", (err) => {
896
900
  process.stderr.write(`[webmcp-local-relay] warn: socket error for connection ${connectionId}: ${err.message}\n`);
897
- this.onSocketClose(connectionId);
901
+ const code = err.code === "WS_ERR_UNSUPPORTED_MESSAGE_LENGTH" ? 1009 : void 0;
902
+ this.onSocketClose(connectionId, code);
898
903
  });
899
904
  this.startHeartbeat(connectionId);
900
905
  });
@@ -1049,7 +1054,7 @@ var RelayBridgeServer = class extends EventEmitter {
1049
1054
  sendElicitationResponse(connectionId, callId, result) {
1050
1055
  const socket = this.socketByConnectionId.get(connectionId);
1051
1056
  if (!socket || socket.readyState !== WebSocket.OPEN) {
1052
- process.stderr.write(`[webmcp-local-relay] warn: cannot send elicitation response — source ${connectionId} disconnected\n`);
1057
+ process.stderr.write(`[webmcp-local-relay] warn: cannot send elicitation response: source ${connectionId} disconnected\n`);
1053
1058
  return;
1054
1059
  }
1055
1060
  const message = {
@@ -1140,18 +1145,24 @@ var RelayBridgeServer = class extends EventEmitter {
1140
1145
  }
1141
1146
  /**
1142
1147
  * Handles source disconnection and rejects in-flight calls owned by that source.
1148
+ *
1149
+ * Guarded against double-call: both 'error' and 'close' events fire on
1150
+ * socket failure, but cleanup (and the stateChanged emission) runs only once.
1143
1151
  */
1144
- onSocketClose(connectionId) {
1152
+ onSocketClose(connectionId, code) {
1153
+ if (!this.socketByConnectionId.has(connectionId)) return;
1145
1154
  this.stopHeartbeat(connectionId);
1146
1155
  this.relayClientConnectionIds.delete(connectionId);
1147
1156
  this.registry.removeConnection(connectionId);
1148
1157
  this.socketByConnectionId.delete(connectionId);
1149
1158
  this.emit("stateChanged");
1159
+ const isPayloadExceeded = code === 1009;
1150
1160
  for (const [callId, pending] of this.pendingInvocations.entries()) {
1151
1161
  if (pending.connectionId !== connectionId) continue;
1152
1162
  clearTimeout(pending.timeoutId);
1153
1163
  this.pendingInvocations.delete(callId);
1154
- pending.reject(/* @__PURE__ */ new Error(`Tool source ${connectionId} disconnected during invocation`));
1164
+ if (isPayloadExceeded) pending.reject(/* @__PURE__ */ new Error(`Tool result exceeded maximum payload size (${formatPayloadLimit(this.maxPayloadBytes)}). Use --max-payload to increase the limit.`));
1165
+ else pending.reject(/* @__PURE__ */ new Error(`Tool source ${connectionId} disconnected during invocation`));
1155
1166
  }
1156
1167
  }
1157
1168
  startHeartbeat(connectionId) {
@@ -1535,6 +1546,14 @@ function parseCliOptions(argv) {
1535
1546
  i += 1;
1536
1547
  continue;
1537
1548
  }
1549
+ if (token === "--max-payload") {
1550
+ const raw = readFlagValue(token, i);
1551
+ i += 1;
1552
+ const value = Number(raw);
1553
+ if (!Number.isInteger(value) || value <= 0) throw new Error(`Invalid max-payload "${raw}". Must be a positive integer (bytes).`);
1554
+ options.maxPayloadBytes = value;
1555
+ continue;
1556
+ }
1538
1557
  if (token === "--help" || token === "-h") {
1539
1558
  printHelp();
1540
1559
  process.exit(0);
@@ -1563,6 +1582,7 @@ function printHelp() {
1563
1582
  " --label Human-readable relay label reported during discovery",
1564
1583
  " --workspace Optional workspace name reported during discovery",
1565
1584
  " --relay-id Stable relay identifier reported during discovery",
1585
+ " --max-payload Maximum WebSocket payload size in bytes (default: 10000000)",
1566
1586
  " --help, -h Show help",
1567
1587
  ""
1568
1588
  ].join("\n"));
@@ -1616,6 +1636,7 @@ var LocalRelayMcpServer = class {
1616
1636
  dynamicToolSignature = /* @__PURE__ */ new Map();
1617
1637
  syncing = false;
1618
1638
  syncRequested = false;
1639
+ syncDebounceTimer = null;
1619
1640
  connected = false;
1620
1641
  /**
1621
1642
  * Creates a local relay MCP server with static and dynamic tool registration.
@@ -1627,9 +1648,7 @@ var LocalRelayMcpServer = class {
1627
1648
  version: options.serverVersion ?? "0.0.0"
1628
1649
  });
1629
1650
  this.bridge.on("stateChanged", () => {
1630
- this.syncDynamicTools().catch((err) => {
1631
- this.logSyncError(err);
1632
- });
1651
+ this.debouncedSyncDynamicTools();
1633
1652
  });
1634
1653
  this.bridge.on("elicitationRequest", (request) => {
1635
1654
  (async () => {
@@ -1681,6 +1700,10 @@ var LocalRelayMcpServer = class {
1681
1700
  */
1682
1701
  async stop() {
1683
1702
  this.connected = false;
1703
+ if (this.syncDebounceTimer) {
1704
+ clearTimeout(this.syncDebounceTimer);
1705
+ this.syncDebounceTimer = null;
1706
+ }
1684
1707
  let mcpCloseError;
1685
1708
  try {
1686
1709
  await this.mcpServer.close();
@@ -1984,6 +2007,23 @@ var LocalRelayMcpServer = class {
1984
2007
  });
1985
2008
  }
1986
2009
  /**
2010
+ * Debounces rapid stateChanged events into a single sync pass.
2011
+ *
2012
+ * Browser reconnections emit multiple stateChanged events in quick
2013
+ * succession (hello, tools/list). Without debouncing, each triggers a
2014
+ * full sync that removes and re-registers tools — sending per-operation
2015
+ * notifications through the stdio transport and overwhelming the agent.
2016
+ */
2017
+ debouncedSyncDynamicTools() {
2018
+ if (this.syncDebounceTimer) return;
2019
+ this.syncDebounceTimer = setTimeout(() => {
2020
+ this.syncDebounceTimer = null;
2021
+ this.syncDynamicTools().catch((err) => {
2022
+ this.logSyncError(err);
2023
+ });
2024
+ }, 16);
2025
+ }
2026
+ /**
1987
2027
  * Coalesces concurrent sync requests into a single serialized sync loop.
1988
2028
  */
1989
2029
  async syncDynamicTools() {
@@ -2112,6 +2152,10 @@ var LocalRelayMcpServer = class {
2112
2152
  }
2113
2153
  /**
2114
2154
  * Produces a stable signature for change detection of dynamic tool metadata.
2155
+ *
2156
+ * Deliberately excludes `sourceId` (which is a per-connection UUID that
2157
+ * changes on every browser reconnect) so that a reconnecting tab with the
2158
+ * same tools does not trigger a needless remove+re-register cycle.
2115
2159
  */
2116
2160
  toolSignature(tool) {
2117
2161
  return JSON.stringify({
@@ -2125,10 +2169,7 @@ var LocalRelayMcpServer = class {
2125
2169
  execution: tool.execution,
2126
2170
  _meta: tool._meta,
2127
2171
  icons: tool.icons,
2128
- sources: tool.sources.map((source) => ({
2129
- sourceId: source.sourceId,
2130
- tabId: source.tabId
2131
- }))
2172
+ sources: tool.sources.map((source) => ({ tabId: source.tabId }))
2132
2173
  });
2133
2174
  }
2134
2175
  /**
@@ -2142,4 +2183,4 @@ var LocalRelayMcpServer = class {
2142
2183
 
2143
2184
  //#endregion
2144
2185
  export { NormalizedToolSchema as C, normalizeInboundTool as D, ToolSchema as E, InboundToolSchema as S, ToolAnnotationsSchema as T, extractSanitizedDomain as _, BrowserToRelayMessageSchema as a, CallToolResultSchema as b, RelayHelloAcceptedMessageSchema as c, RelaySourceInfoSchema as d, RelayToBrowserMessageSchema as f, buildPublicToolName as g, RelayRegistry as h, RelayBridgeServer as i, RelayHelloRejectedMessageSchema as l, HelloRequiredError as m, parseCliOptions as n, RelayClientToServerMessageSchema as o, ServerHelloMessageSchema as p, printHelp as r, RelayDescriptorSchema as s, LocalRelayMcpServer as t, RelayServerToClientMessageSchema as u, sanitizeName as v, RelayInvokeArgsSchema as w, DEFAULT_TOOL_INPUT_SCHEMA as x, CallToolRequestParamsSchema as y };
2145
- //# sourceMappingURL=mcpRelayServer-BL8kNwIK.mjs.map
2186
+ //# sourceMappingURL=mcpRelayServer-BT1ct1Cc.mjs.map