@mcp-b/webmcp-local-relay 6.0.2 → 6.1.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
@@ -46,7 +46,7 @@ Once connected, your AI client can see and call tools from any open browser tab
46
46
 
47
47
  1. `webmcp_list_sources` — see which tabs are connected
48
48
  2. `webmcp_list_tools` — see all available tools
49
- 3. Call any tool directly by name (e.g., `create_issue`, `search_docs`)
49
+ 3. Call any tool directly by name (e.g., `create_issue`, `search_docs`), or through `webmcp_call_tool`
50
50
 
51
51
  Tools appear and disappear automatically as you open, reload, and close tabs.
52
52
  Tools that require MCP task execution are omitted, and multi-round `input_required` results return an error.
@@ -127,12 +127,13 @@ npx @mcp-b/webmcp-local-relay
127
127
 
128
128
  ### Exposed Tools
129
129
 
130
- The relay exposes three static management tools that are always available:
130
+ The relay exposes four static management tools that are always available:
131
131
 
132
132
  | Tool | Description |
133
133
  | --------------------- | ----------------------------------------------------------------------------------- |
134
134
  | `webmcp_list_sources` | Lists connected browser tabs that publish tools, with tab metadata |
135
135
  | `webmcp_list_tools` | Lists all relayed tools with source info |
136
+ | `webmcp_call_tool` | Calls a relayed tool by name, for clients that don't refresh their tool list |
136
137
  | `webmcp_open_page` | Opens a URL, or in server mode refreshes a connected source page by matching origin |
137
138
 
138
139
  **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:
@@ -203,6 +204,7 @@ npx @mcp-b/webmcp-local-relay --widget-origin https://myapp.com
203
204
  │ LocalRelayMcpServer │
204
205
  │ webmcp_list_sources │
205
206
  │ webmcp_list_tools │
207
+ │ webmcp_call_tool │
206
208
  │ + dynamic tools from browser │
207
209
  └──────────────────┬───────────────────┘
208
210
  │ in process
@@ -234,13 +236,13 @@ Supported page runtimes:
234
236
 
235
237
  1. `@mcp-b/global` (recommended for the complete MCP-B runtime)
236
238
  2. Current native Chrome with `document.modelContext.getTools()` and object-input `executeTool()`
237
- 3. `@mcp-b/webmcp-polyfill`
239
+ 3. `@w3c/webmcp-polyfill` or `@mcp-b/webmcp-polyfill`
238
240
 
239
241
  Runtime dispatch behavior in the browser embed/widget layer:
240
242
 
241
243
  - Uses asynchronous `document.modelContext.getTools()` and the exact returned
242
244
  descriptor with `executeTool()` and object input, as defined by the current
243
- upstream API.
245
+ WebMCP draft.
244
246
  - Refreshes the descriptor before every invocation so Chrome never receives a
245
247
  stale registration object.
246
248
  - Relays the host page's tools plus tools registered by other same-origin
@@ -266,13 +268,14 @@ For Chromium/Chrome Canary native preview testing:
266
268
 
267
269
  ### Troubleshooting
268
270
 
269
- | Problem | Fix |
270
- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
271
- | `No sources connected` | Ensure the page loaded `embed.js` and the relay process is running |
272
- | `No tools listed` | Ensure tools are registered on the page's WebMCP runtime. If tools register after load, confirm your runtime emits the `toolchange` event |
273
- | `Tool not found` | Tab reloaded or disconnected — call `webmcp_list_tools` again to refresh |
274
- | Connection blocked | Verify `--widget-origin` matches your host page's origin (e.g., `https://myapp.com`), and relay port matches `data-relay-port` |
275
- | `Tool execution timed out` | A page tool exceeded the timeout (default 60s). Raise `data-request-timeout` and keep CLI `--invoke-timeout` slightly higher |
271
+ | Problem | Fix |
272
+ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
273
+ | `No sources connected` | Ensure the page loaded `embed.js` and the relay process is running |
274
+ | `No tools listed` | Ensure tools are registered on the page's WebMCP runtime. If tools register after load, confirm your runtime emits the `toolchange` event |
275
+ | `Tool not found` | Tab reloaded or disconnected — call `webmcp_list_tools` again to refresh |
276
+ | New page tool missing | Your client doesn't refresh its tool list on `tools/list_changed`. Find the tool with `webmcp_list_tools` and call it with `webmcp_call_tool` |
277
+ | Connection blocked | Verify `--widget-origin` matches your host page's origin (e.g., `https://myapp.com`), and relay port matches `data-relay-port` |
278
+ | `Tool execution timed out` | A page tool exceeded the timeout (default 60s). Raise `data-request-timeout` and keep CLI `--invoke-timeout` slightly higher |
276
279
 
277
280
  ---
278
281
 
@@ -5,7 +5,7 @@
5
5
  <title>WebMCP Relay Widget</title>
6
6
  </head>
7
7
  <body>
8
- <script>(function(){function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(t){if(!e(t))return!1;let n=Object.getPrototypeOf(t);return n===Object.prototype||n===null}function n(e,r=new WeakSet){if(e===null||typeof e==`string`||typeof e==`boolean`)return!0;if(typeof e==`number`)return Number.isFinite(e);if(typeof e!=`object`||r.has(e))return!1;r.add(e);try{return(Array.isArray(e)?e:t(e)?Object.values(e):null)?.every(e=>n(e,r))??!1}catch{return!1}finally{r.delete(e)}}function r(t){if(!e(t))return!1;switch(t.type){case`text`:return typeof t.text==`string`;case`image`:case`audio`:return typeof t.data==`string`&&typeof t.mimeType==`string`;case`resource_link`:return typeof t.uri==`string`&&typeof t.name==`string`;case`resource`:{let n=t.resource;return e(n)&&typeof n.uri==`string`&&(typeof n.text==`string`||typeof n.blob==`string`)}default:return!1}}function i(t){return e(t)&&n(t)&&Array.isArray(t.content)&&t.content.every(r)&&(t.isError===void 0||typeof t.isError==`boolean`)&&(t._meta===void 0||e(t._meta))}function a(e){if(typeof e==`string`)return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}function o(e){if(i(e))return e;let t={content:[{type:`text`,text:a(e)}],isError:!1};return n(e)&&(t.structuredContent=e),t}let s=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),c=9333,l=new Set;function u(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function d(e){let t;try{t=JSON.parse(e)}catch{}return u(t)?t.resultType===`input_required`?{isError:!0,content:[{type:`text`,text:`The WebMCP local relay cannot forward MCP input_required results. Multi-round tool flows require direct McpServer registration.`}]}:o(t):o(typeof t==`string`?t:e)}function f(e,t){let n=new Map;for(let r of e){let e=n.get(r.name);e&&(l.has(r.name)||(l.add(r.name),console.warn(`[webmcp-relay-widget] More than one frame registered a tool named "${r.name}". Only one is relayed, preferring the host page's own tool.`)),e.window===t||r.window!==t)||n.set(r.name,r)}return[...n.values()]}function p(e){return s.has(e)}function m(e){return e.replace(/[\r\n]/g,``)}function h(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function g(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let _=[1e4,2e4,3e4];function v(e=window.location.search){let t=new URLSearchParams(e),n=globalThis.__WEBMCP_RELAY_CONFIG;function r(e){return t.get(e)??n?.[e]??null}let i=r(`hostOrigin`);if(!i)return null;let a=r(`relayHost`)||`127.0.0.1`;if(!p(a))return console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,a),null;let o=r(`relayPort`),s=o?Number(o):c;if(!Number.isInteger(s)||s<1||s>65535)return console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,o),null;let l=r(`requestTimeout`),u=l?Number(l):6e4;return!Number.isInteger(u)||u<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,l),null):{autoConnect:r(`autoConnect`)!==`false`,hostOrigin:i,hostTitle:r(`hostTitle`)||``,hostUrl:r(`hostUrl`)||i,relayHostHint:a,relayId:r(`relayId`)||void 0,relayPortHint:s,relayWorkspace:r(`relayWorkspace`)||void 0,requestTimeoutMs:u,tabId:r(`tabId`)||crypto.randomUUID()}}function y(){let e=v();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}j(e)}function b(e){if(!u(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`)return null;let t={type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port};return typeof e.label==`string`&&(t.label=e.label),typeof e.relayId==`string`&&(t.relayId=e.relayId),typeof e.workspace==`string`&&(t.workspace=e.workspace),t}function x(e){return!u(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function S(e){return g({hostOrigin:e.hostOrigin,relayId:e.relayId??null,workspace:e.relayWorkspace??null})}function C(e){try{let t=sessionStorage.getItem(S(e));if(!t)return null;let n=JSON.parse(t);return!u(n)||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 w(e,t){try{sessionStorage.setItem(S(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function T(e){try{sessionStorage.removeItem(S(e))}catch{}}function E(e){let t=C(e),n=new Set,r=[],i=(e,t)=>{let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=c;t<=9348;t+=1)i(e,t);return r}async function D(e){let t=`ws://${e.host.includes(`:`)&&!e.host.startsWith(`[`)?`[${e.host}]`: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=b(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1e4);i.addEventListener(`open`,()=>{clearTimeout(l),l=setTimeout(()=>{a(null)},1200)},{once:!0}),i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function O(e){if(typeof e.inputSchema==`string`)return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because this browser's WebMCP predates Chrome 155. Update Chrome, or turn off the WebMCP testing flag to use the polyfill.`),null;if(e.inputSchema!==void 0&&!u(e.inputSchema))return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because its input schema is malformed.`),null;let t={name:e.name,title:e.title,description:e.description};return e.inputSchema!==void 0&&(t.inputSchema=e.inputSchema),e.annotations!==void 0&&(t.annotations=e.annotations),t}function k(){return window.parent.document.modelContext}async function A(){let e=k();return e?f(await e.getTools(),window.parent).flatMap(e=>{let t=O(e);return t?[{registered:e,descriptor:t}]:[]}):[]}function j(e){let t=[],n=[],r=0,i=``,a=null,o=null,s=!1,c=!1,l=null,f=null,p=`idle`,g=0,v=null,y=async()=>{for(;;){let e=r,a=await A();if(e!==r)continue;let l=a.map(({descriptor:e})=>e),u=l.map(e=>JSON.stringify(e)).sort().join(`
8
+ <script>(function(){function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(t){if(!e(t))return!1;let n=Object.getPrototypeOf(t);return n===Object.prototype||n===null}function n(e,r=new WeakSet){if(e===null||typeof e==`string`||typeof e==`boolean`)return!0;if(typeof e==`number`)return Number.isFinite(e);if(typeof e!=`object`||r.has(e))return!1;r.add(e);try{return(Array.isArray(e)?e:t(e)?Object.values(e):null)?.every(e=>n(e,r))??!1}catch{return!1}finally{r.delete(e)}}function r(t){if(!e(t))return!1;switch(t.type){case`text`:return typeof t.text==`string`;case`image`:case`audio`:return typeof t.data==`string`&&typeof t.mimeType==`string`;case`resource_link`:return typeof t.uri==`string`&&typeof t.name==`string`;case`resource`:{let n=t.resource;return e(n)&&typeof n.uri==`string`&&(typeof n.text==`string`||typeof n.blob==`string`)}default:return!1}}function i(t){return e(t)&&n(t)&&Array.isArray(t.content)&&t.content.every(r)&&(t.isError===void 0||typeof t.isError==`boolean`)&&(t._meta===void 0||e(t._meta))}function a(e){if(typeof e==`string`)return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}function o(e){if(i(e))return e;let t={content:[{type:`text`,text:a(e)}],isError:!1};return n(e)&&(t.structuredContent=e),t}let s=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),c=9333,l=new Set;function u(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function d(e){let t;try{t=JSON.parse(e)}catch{}return u(t)?t.resultType===`input_required`?{isError:!0,content:[{type:`text`,text:`The WebMCP local relay cannot forward MCP input_required results. Multi-round tool flows require direct McpServer registration.`}]}:o(t):o(typeof t==`string`?t:e)}function f(e,t){let n=new Map;for(let r of e){let e=n.get(r.name);e&&(l.has(r.name)||(l.add(r.name),console.warn(`[webmcp-relay-widget] More than one frame registered a tool named "${r.name}". Only one is relayed, preferring the host page's own tool.`)),e.window===t||r.window!==t)||n.set(r.name,r)}return[...n.values()]}function p(e){return s.has(e)}function m(e){return e.replace(/[\r\n]/g,``)}function h(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function g(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let _=[1e4,2e4,3e4];function v(e=window.location.search){let t=new URLSearchParams(e),n=globalThis.__WEBMCP_RELAY_CONFIG;function r(e){return t.get(e)??n?.[e]??null}let i=r(`hostOrigin`);if(!i)return null;let a=r(`relayHost`)||`127.0.0.1`;if(!p(a))return console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,a),null;let o=r(`relayPort`),s=o?Number(o):c;if(!Number.isInteger(s)||s<1||s>65535)return console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,o),null;let l=r(`requestTimeout`),u=l?Number(l):6e4;return!Number.isInteger(u)||u<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,l),null):{autoConnect:r(`autoConnect`)!==`false`,hostOrigin:i,hostTitle:r(`hostTitle`)||``,hostUrl:r(`hostUrl`)||i,relayHostHint:a,relayId:r(`relayId`)||void 0,relayPortHint:s,relayWorkspace:r(`relayWorkspace`)||void 0,requestTimeoutMs:u,tabId:r(`tabId`)||crypto.randomUUID()}}function y(){let e=v();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}j(e)}function b(e){if(!u(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`)return null;let t={type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port};return typeof e.label==`string`&&(t.label=e.label),typeof e.relayId==`string`&&(t.relayId=e.relayId),typeof e.workspace==`string`&&(t.workspace=e.workspace),t}function x(e){return!u(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function S(e){return g({hostOrigin:e.hostOrigin,relayId:e.relayId??null,workspace:e.relayWorkspace??null})}function C(e){try{let t=sessionStorage.getItem(S(e));if(!t)return null;let n=JSON.parse(t);return!u(n)||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 w(e,t){try{sessionStorage.setItem(S(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function T(e){try{sessionStorage.removeItem(S(e))}catch{}}function E(e){let t=C(e),n=new Set,r=[],i=(e,t)=>{let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=c;t<=9348;t+=1)i(e,t);return r}async function D(e){let t=`ws://${e.host.includes(`:`)&&!e.host.startsWith(`[`)?`[${e.host}]`: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=b(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1e4);i.addEventListener(`open`,()=>{clearTimeout(l),l=setTimeout(()=>{a(null)},1200)},{once:!0}),i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function O(e){if(typeof e.inputSchema==`string`)return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because this browser's WebMCP predates Chrome 155. Update Chrome, or turn off the WebMCP testing flag to use the polyfill.`),null;let t=e.inputSchema??void 0,n=e.annotations??void 0;if(t!==void 0&&!u(t))return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because its input schema is malformed.`),null;let r={name:e.name,title:e.title,description:e.description};return t!==void 0&&(r.inputSchema=t),n!==void 0&&(r.annotations=n),r}function k(){return window.parent.document.modelContext}async function A(){let e=k();return e?f(await e.getTools(),window.parent).flatMap(e=>{let t=O(e);return t?[{registered:e,descriptor:t}]:[]}):[]}function j(e){let t=[],n=[],r=0,i=``,a=null,o=null,s=!1,c=!1,l=null,f=null,p=`idle`,g=0,v=null,y=async()=>{for(;;){let e=r,a=await A();if(e!==r)continue;let l=a.map(({descriptor:e})=>e),u=l.map(e=>JSON.stringify(e)).sort().join(`
9
9
  `);if(t=a,n=l,u===i)return;i=u,o&&s&&c&&h(o,JSON.stringify({type:`tools/changed`,tools:n}));return}},S=()=>{r++,y().catch(e=>{console.warn(`[webmcp-relay-widget] Failed to refresh WebMCP tools:`,e)})},O=k();typeof O?.addEventListener==`function`&&O.addEventListener(`toolchange`,S),y().catch(e=>{console.warn(`[webmcp-relay-widget] Failed to read WebMCP tools:`,e)});let j=(r,i)=>{let _=()=>{l&&(clearTimeout(l),l=null)},v=()=>{c=!0,h(r,JSON.stringify({type:`tools/list`,tools:n}))};f&&(clearTimeout(f),f=null);let S=new Map;a=i,o=r,s=!1;let C=!1;c=!1,p=`idle`,r.addEventListener(`message`,n=>{let o;try{o=JSON.parse(String(n.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let c=b(o);if(c){a={hello:c,host:i.host,port:i.port};return}if(u(o)&&o.type===`hello/accepted`){_(),s=!0,g=0,w(e,i),y().then(v).catch(e=>{console.warn(`[webmcp-relay-widget] Failed to refresh WebMCP tools:`,e),v()});return}let l=x(o);if(l){_(),s=!1,C=!0,T(e),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,l.reason,l.message);try{r.close(1008,l.message)}catch{}return}if(!u(o)||typeof o.type!=`string`)return;let f=o;if(f.type===`ping`){h(r,JSON.stringify({type:`pong`}));return}if(f.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(f.type===`cancel`){if(typeof f.callId!=`string`)return;S.get(f.callId)?.abort(Error(`Tool execution cancelled`)),S.delete(f.callId);return}if(f.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,m(o.type));return}let{callId:p}=f;if(typeof p!=`string`||S.has(p))return;let E=String(f.toolName??``),D=new AbortController;S.set(p,D),(async()=>{await y(),D.signal.throwIfAborted();let n=t.find(({registered:e})=>e.name===E),i=k();if(!n||!i)throw Error(`Tool not found: ${E}`);let a;try{let t=new Promise((e,t)=>{D.signal.addEventListener(`abort`,()=>t(D.signal.reason),{once:!0})});a=setTimeout(()=>{D.abort(Error(`Tool execution timed out`))},e.requestTimeoutMs);let o=i.executeTool(n.registered,u(f.args)?f.args:{},{signal:D.signal}),s=await Promise.race([o,t]);h(r,JSON.stringify({type:`result`,callId:p,result:d(s)}))}finally{clearTimeout(a)}})().catch(e=>{S.get(p)===D&&h(r,JSON.stringify({type:`result`,callId:p,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))}).finally(()=>{S.get(p)===D&&S.delete(p)})}),r.addEventListener(`close`,()=>{for(let e of S.values())e.abort(Error(`Relay connection closed`));S.clear(),o===r&&(s=!1,o=null,_(),C?I():F())},{once:!0}),r.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{r.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),y().catch(e=>{console.warn(`[webmcp-relay-widget] Failed to read WebMCP tools:`,e)}).then(()=>{h(r,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),l=setTimeout(()=>{l=null,!(o!==r||r.readyState!==WebSocket.OPEN||s)&&(console.warn(`[webmcp-relay-widget] Relay did not acknowledge browser hello`),C=!0,r.close(4e3,`Browser hello was not acknowledged`))},1e3)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{r.close()}catch{}})},M=t=>(!e.relayId||t.hello.relayId===e.relayId)&&(!e.relayWorkspace||t.hello.workspace===e.relayWorkspace),N=async e=>{let t=await D({host:e.host,port:e.port});return t?M(t.endpoint)?(j(t.socket,t.endpoint),!0):(t.socket.close(),!1):!1},P=async()=>{p=`discovering`;try{let[t,...n]=E(e);if(t&&await N(t))return!0;let r=await Promise.all(n.map(e=>D(e))),i=r.find(e=>e!==null&&M(e.endpoint));for(let e of r)e&&e!==i&&e.socket.close();return i?(j(i.socket,i.endpoint),!0):!1}finally{p===`discovering`&&(p=`idle`)}},F=()=>{if(!a||f)return;let e={host:a.host,port:a.port},t=Math.round(500*(.85+Math.random()*.3));f=setTimeout(()=>{f=null,N(e).then(e=>{e||I()})},t)},I=()=>{if(f)return;let e=_[g];if(e===void 0){z();return}f=setTimeout(()=>{f=null,g+=1,P().then(e=>{e||I()})},e)},L=()=>{document.visibilityState===`visible`&&p===`dormant`&&V()},R=()=>{document.removeEventListener(`visibilitychange`,L),v!==null&&(clearInterval(v),v=null)},z=()=>{p!==`dormant`&&(p=`dormant`,f&&(clearTimeout(f),f=null),document.addEventListener(`visibilitychange`,L),v=setInterval(()=>{B()},12e4))},B=async()=>{if(p!==`dormant`)return;let t=[{host:e.relayHostHint,port:e.relayPortHint}],n=C(e);n&&(n.host!==e.relayHostHint||n.port!==e.relayPortHint)&&t.push(n),p=`discovering`,R();for(let e of t)if(await N(e))return;z()},V=()=>{R(),g=0,P().then(e=>{e||z()})};window.addEventListener(`message`,t=>{if(t.source!==window.parent||t.origin!==e.hostOrigin)return;let n=t.data;u(n)&&n.type===`webmcp.connect`&&(p===`dormant`?V():!o&&p!==`discovering`&&P())}),e.autoConnect&&P().then(e=>{e||I()})}y()})();</script>
10
10
  </body>
11
11
  </html>
@@ -1,2 +1,2 @@
1
- (function(){function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(t){if(!e(t))return!1;let n=Object.getPrototypeOf(t);return n===Object.prototype||n===null}function n(e,r=new WeakSet){if(e===null||typeof e==`string`||typeof e==`boolean`)return!0;if(typeof e==`number`)return Number.isFinite(e);if(typeof e!=`object`||r.has(e))return!1;r.add(e);try{return(Array.isArray(e)?e:t(e)?Object.values(e):null)?.every(e=>n(e,r))??!1}catch{return!1}finally{r.delete(e)}}function r(t){if(!e(t))return!1;switch(t.type){case`text`:return typeof t.text==`string`;case`image`:case`audio`:return typeof t.data==`string`&&typeof t.mimeType==`string`;case`resource_link`:return typeof t.uri==`string`&&typeof t.name==`string`;case`resource`:{let n=t.resource;return e(n)&&typeof n.uri==`string`&&(typeof n.text==`string`||typeof n.blob==`string`)}default:return!1}}function i(t){return e(t)&&n(t)&&Array.isArray(t.content)&&t.content.every(r)&&(t.isError===void 0||typeof t.isError==`boolean`)&&(t._meta===void 0||e(t._meta))}function a(e){if(typeof e==`string`)return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}function o(e){if(i(e))return e;let t={content:[{type:`text`,text:a(e)}],isError:!1};return n(e)&&(t.structuredContent=e),t}let s=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),c=9333,l=new Set;function u(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function d(e){let t;try{t=JSON.parse(e)}catch{}return u(t)?t.resultType===`input_required`?{isError:!0,content:[{type:`text`,text:`The WebMCP local relay cannot forward MCP input_required results. Multi-round tool flows require direct McpServer registration.`}]}:o(t):o(typeof t==`string`?t:e)}function f(e,t){let n=new Map;for(let r of e){let e=n.get(r.name);e&&(l.has(r.name)||(l.add(r.name),console.warn(`[webmcp-relay-widget] More than one frame registered a tool named "${r.name}". Only one is relayed, preferring the host page's own tool.`)),e.window===t||r.window!==t)||n.set(r.name,r)}return[...n.values()]}function p(e){return s.has(e)}function m(e){return e.replace(/[\r\n]/g,``)}function h(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function g(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let _=[1e4,2e4,3e4];function v(e=window.location.search){let t=new URLSearchParams(e),n=globalThis.__WEBMCP_RELAY_CONFIG;function r(e){return t.get(e)??n?.[e]??null}let i=r(`hostOrigin`);if(!i)return null;let a=r(`relayHost`)||`127.0.0.1`;if(!p(a))return console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,a),null;let o=r(`relayPort`),s=o?Number(o):c;if(!Number.isInteger(s)||s<1||s>65535)return console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,o),null;let l=r(`requestTimeout`),u=l?Number(l):6e4;return!Number.isInteger(u)||u<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,l),null):{autoConnect:r(`autoConnect`)!==`false`,hostOrigin:i,hostTitle:r(`hostTitle`)||``,hostUrl:r(`hostUrl`)||i,relayHostHint:a,relayId:r(`relayId`)||void 0,relayPortHint:s,relayWorkspace:r(`relayWorkspace`)||void 0,requestTimeoutMs:u,tabId:r(`tabId`)||crypto.randomUUID()}}function y(){let e=v();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}j(e)}function b(e){if(!u(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`)return null;let t={type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port};return typeof e.label==`string`&&(t.label=e.label),typeof e.relayId==`string`&&(t.relayId=e.relayId),typeof e.workspace==`string`&&(t.workspace=e.workspace),t}function x(e){return!u(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function S(e){return g({hostOrigin:e.hostOrigin,relayId:e.relayId??null,workspace:e.relayWorkspace??null})}function C(e){try{let t=sessionStorage.getItem(S(e));if(!t)return null;let n=JSON.parse(t);return!u(n)||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 w(e,t){try{sessionStorage.setItem(S(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function T(e){try{sessionStorage.removeItem(S(e))}catch{}}function E(e){let t=C(e),n=new Set,r=[],i=(e,t)=>{let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=c;t<=9348;t+=1)i(e,t);return r}async function D(e){let t=`ws://${e.host.includes(`:`)&&!e.host.startsWith(`[`)?`[${e.host}]`: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=b(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1e4);i.addEventListener(`open`,()=>{clearTimeout(l),l=setTimeout(()=>{a(null)},1200)},{once:!0}),i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function O(e){if(typeof e.inputSchema==`string`)return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because this browser's WebMCP predates Chrome 155. Update Chrome, or turn off the WebMCP testing flag to use the polyfill.`),null;if(e.inputSchema!==void 0&&!u(e.inputSchema))return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because its input schema is malformed.`),null;let t={name:e.name,title:e.title,description:e.description};return e.inputSchema!==void 0&&(t.inputSchema=e.inputSchema),e.annotations!==void 0&&(t.annotations=e.annotations),t}function k(){return window.parent.document.modelContext}async function A(){let e=k();return e?f(await e.getTools(),window.parent).flatMap(e=>{let t=O(e);return t?[{registered:e,descriptor:t}]:[]}):[]}function j(e){let t=[],n=[],r=0,i=``,a=null,o=null,s=!1,c=!1,l=null,f=null,p=`idle`,g=0,v=null,y=async()=>{for(;;){let e=r,a=await A();if(e!==r)continue;let l=a.map(({descriptor:e})=>e),u=l.map(e=>JSON.stringify(e)).sort().join(`
1
+ (function(){function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(t){if(!e(t))return!1;let n=Object.getPrototypeOf(t);return n===Object.prototype||n===null}function n(e,r=new WeakSet){if(e===null||typeof e==`string`||typeof e==`boolean`)return!0;if(typeof e==`number`)return Number.isFinite(e);if(typeof e!=`object`||r.has(e))return!1;r.add(e);try{return(Array.isArray(e)?e:t(e)?Object.values(e):null)?.every(e=>n(e,r))??!1}catch{return!1}finally{r.delete(e)}}function r(t){if(!e(t))return!1;switch(t.type){case`text`:return typeof t.text==`string`;case`image`:case`audio`:return typeof t.data==`string`&&typeof t.mimeType==`string`;case`resource_link`:return typeof t.uri==`string`&&typeof t.name==`string`;case`resource`:{let n=t.resource;return e(n)&&typeof n.uri==`string`&&(typeof n.text==`string`||typeof n.blob==`string`)}default:return!1}}function i(t){return e(t)&&n(t)&&Array.isArray(t.content)&&t.content.every(r)&&(t.isError===void 0||typeof t.isError==`boolean`)&&(t._meta===void 0||e(t._meta))}function a(e){if(typeof e==`string`)return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}function o(e){if(i(e))return e;let t={content:[{type:`text`,text:a(e)}],isError:!1};return n(e)&&(t.structuredContent=e),t}let s=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),c=9333,l=new Set;function u(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function d(e){let t;try{t=JSON.parse(e)}catch{}return u(t)?t.resultType===`input_required`?{isError:!0,content:[{type:`text`,text:`The WebMCP local relay cannot forward MCP input_required results. Multi-round tool flows require direct McpServer registration.`}]}:o(t):o(typeof t==`string`?t:e)}function f(e,t){let n=new Map;for(let r of e){let e=n.get(r.name);e&&(l.has(r.name)||(l.add(r.name),console.warn(`[webmcp-relay-widget] More than one frame registered a tool named "${r.name}". Only one is relayed, preferring the host page's own tool.`)),e.window===t||r.window!==t)||n.set(r.name,r)}return[...n.values()]}function p(e){return s.has(e)}function m(e){return e.replace(/[\r\n]/g,``)}function h(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function g(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let _=[1e4,2e4,3e4];function v(e=window.location.search){let t=new URLSearchParams(e),n=globalThis.__WEBMCP_RELAY_CONFIG;function r(e){return t.get(e)??n?.[e]??null}let i=r(`hostOrigin`);if(!i)return null;let a=r(`relayHost`)||`127.0.0.1`;if(!p(a))return console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,a),null;let o=r(`relayPort`),s=o?Number(o):c;if(!Number.isInteger(s)||s<1||s>65535)return console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,o),null;let l=r(`requestTimeout`),u=l?Number(l):6e4;return!Number.isInteger(u)||u<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,l),null):{autoConnect:r(`autoConnect`)!==`false`,hostOrigin:i,hostTitle:r(`hostTitle`)||``,hostUrl:r(`hostUrl`)||i,relayHostHint:a,relayId:r(`relayId`)||void 0,relayPortHint:s,relayWorkspace:r(`relayWorkspace`)||void 0,requestTimeoutMs:u,tabId:r(`tabId`)||crypto.randomUUID()}}function y(){let e=v();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}j(e)}function b(e){if(!u(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`)return null;let t={type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port};return typeof e.label==`string`&&(t.label=e.label),typeof e.relayId==`string`&&(t.relayId=e.relayId),typeof e.workspace==`string`&&(t.workspace=e.workspace),t}function x(e){return!u(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function S(e){return g({hostOrigin:e.hostOrigin,relayId:e.relayId??null,workspace:e.relayWorkspace??null})}function C(e){try{let t=sessionStorage.getItem(S(e));if(!t)return null;let n=JSON.parse(t);return!u(n)||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 w(e,t){try{sessionStorage.setItem(S(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function T(e){try{sessionStorage.removeItem(S(e))}catch{}}function E(e){let t=C(e),n=new Set,r=[],i=(e,t)=>{let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=c;t<=9348;t+=1)i(e,t);return r}async function D(e){let t=`ws://${e.host.includes(`:`)&&!e.host.startsWith(`[`)?`[${e.host}]`: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=b(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1e4);i.addEventListener(`open`,()=>{clearTimeout(l),l=setTimeout(()=>{a(null)},1200)},{once:!0}),i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function O(e){if(typeof e.inputSchema==`string`)return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because this browser's WebMCP predates Chrome 155. Update Chrome, or turn off the WebMCP testing flag to use the polyfill.`),null;let t=e.inputSchema??void 0,n=e.annotations??void 0;if(t!==void 0&&!u(t))return console.warn(`[webmcp-relay-widget] Tool "${e.name}" was not relayed because its input schema is malformed.`),null;let r={name:e.name,title:e.title,description:e.description};return t!==void 0&&(r.inputSchema=t),n!==void 0&&(r.annotations=n),r}function k(){return window.parent.document.modelContext}async function A(){let e=k();return e?f(await e.getTools(),window.parent).flatMap(e=>{let t=O(e);return t?[{registered:e,descriptor:t}]:[]}):[]}function j(e){let t=[],n=[],r=0,i=``,a=null,o=null,s=!1,c=!1,l=null,f=null,p=`idle`,g=0,v=null,y=async()=>{for(;;){let e=r,a=await A();if(e!==r)continue;let l=a.map(({descriptor:e})=>e),u=l.map(e=>JSON.stringify(e)).sort().join(`
2
2
  `);if(t=a,n=l,u===i)return;i=u,o&&s&&c&&h(o,JSON.stringify({type:`tools/changed`,tools:n}));return}},S=()=>{r++,y().catch(e=>{console.warn(`[webmcp-relay-widget] Failed to refresh WebMCP tools:`,e)})},O=k();typeof O?.addEventListener==`function`&&O.addEventListener(`toolchange`,S),y().catch(e=>{console.warn(`[webmcp-relay-widget] Failed to read WebMCP tools:`,e)});let j=(r,i)=>{let _=()=>{l&&(clearTimeout(l),l=null)},v=()=>{c=!0,h(r,JSON.stringify({type:`tools/list`,tools:n}))};f&&(clearTimeout(f),f=null);let S=new Map;a=i,o=r,s=!1;let C=!1;c=!1,p=`idle`,r.addEventListener(`message`,n=>{let o;try{o=JSON.parse(String(n.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let c=b(o);if(c){a={hello:c,host:i.host,port:i.port};return}if(u(o)&&o.type===`hello/accepted`){_(),s=!0,g=0,w(e,i),y().then(v).catch(e=>{console.warn(`[webmcp-relay-widget] Failed to refresh WebMCP tools:`,e),v()});return}let l=x(o);if(l){_(),s=!1,C=!0,T(e),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,l.reason,l.message);try{r.close(1008,l.message)}catch{}return}if(!u(o)||typeof o.type!=`string`)return;let f=o;if(f.type===`ping`){h(r,JSON.stringify({type:`pong`}));return}if(f.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(f.type===`cancel`){if(typeof f.callId!=`string`)return;S.get(f.callId)?.abort(Error(`Tool execution cancelled`)),S.delete(f.callId);return}if(f.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,m(o.type));return}let{callId:p}=f;if(typeof p!=`string`||S.has(p))return;let E=String(f.toolName??``),D=new AbortController;S.set(p,D),(async()=>{await y(),D.signal.throwIfAborted();let n=t.find(({registered:e})=>e.name===E),i=k();if(!n||!i)throw Error(`Tool not found: ${E}`);let a;try{let t=new Promise((e,t)=>{D.signal.addEventListener(`abort`,()=>t(D.signal.reason),{once:!0})});a=setTimeout(()=>{D.abort(Error(`Tool execution timed out`))},e.requestTimeoutMs);let o=i.executeTool(n.registered,u(f.args)?f.args:{},{signal:D.signal}),s=await Promise.race([o,t]);h(r,JSON.stringify({type:`result`,callId:p,result:d(s)}))}finally{clearTimeout(a)}})().catch(e=>{S.get(p)===D&&h(r,JSON.stringify({type:`result`,callId:p,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))}).finally(()=>{S.get(p)===D&&S.delete(p)})}),r.addEventListener(`close`,()=>{for(let e of S.values())e.abort(Error(`Relay connection closed`));S.clear(),o===r&&(s=!1,o=null,_(),C?I():F())},{once:!0}),r.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{r.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),y().catch(e=>{console.warn(`[webmcp-relay-widget] Failed to read WebMCP tools:`,e)}).then(()=>{h(r,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),l=setTimeout(()=>{l=null,!(o!==r||r.readyState!==WebSocket.OPEN||s)&&(console.warn(`[webmcp-relay-widget] Relay did not acknowledge browser hello`),C=!0,r.close(4e3,`Browser hello was not acknowledged`))},1e3)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{r.close()}catch{}})},M=t=>(!e.relayId||t.hello.relayId===e.relayId)&&(!e.relayWorkspace||t.hello.workspace===e.relayWorkspace),N=async e=>{let t=await D({host:e.host,port:e.port});return t?M(t.endpoint)?(j(t.socket,t.endpoint),!0):(t.socket.close(),!1):!1},P=async()=>{p=`discovering`;try{let[t,...n]=E(e);if(t&&await N(t))return!0;let r=await Promise.all(n.map(e=>D(e))),i=r.find(e=>e!==null&&M(e.endpoint));for(let e of r)e&&e!==i&&e.socket.close();return i?(j(i.socket,i.endpoint),!0):!1}finally{p===`discovering`&&(p=`idle`)}},F=()=>{if(!a||f)return;let e={host:a.host,port:a.port},t=Math.round(500*(.85+Math.random()*.3));f=setTimeout(()=>{f=null,N(e).then(e=>{e||I()})},t)},I=()=>{if(f)return;let e=_[g];if(e===void 0){z();return}f=setTimeout(()=>{f=null,g+=1,P().then(e=>{e||I()})},e)},L=()=>{document.visibilityState===`visible`&&p===`dormant`&&V()},R=()=>{document.removeEventListener(`visibilitychange`,L),v!==null&&(clearInterval(v),v=null)},z=()=>{p!==`dormant`&&(p=`dormant`,f&&(clearTimeout(f),f=null),document.addEventListener(`visibilitychange`,L),v=setInterval(()=>{B()},12e4))},B=async()=>{if(p!==`dormant`)return;let t=[{host:e.relayHostHint,port:e.relayPortHint}],n=C(e);n&&(n.host!==e.relayHostHint||n.port!==e.relayPortHint)&&t.push(n),p=`discovering`,R();for(let e of t)if(await N(e))return;z()},V=()=>{R(),g=0,P().then(e=>{e||z()})};window.addEventListener(`message`,t=>{if(t.source!==window.parent||t.origin!==e.hostOrigin)return;let n=t.data;u(n)&&n.type===`webmcp.connect`&&(p===`dormant`?V():!o&&p!==`discovering`&&P())}),e.autoConnect&&P().then(e=>{e||I()})}y()})();
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as parseCliOptions, r as printHelp, t as LocalRelayMcpServer } from "./mcpRelayServer-DkidP4o7.mjs";
2
+ import { n as parseCliOptions, r as printHelp, t as LocalRelayMcpServer } from "./mcpRelayServer-BYF9j2fv.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  let options;
@@ -16,7 +16,7 @@ if (options.help) {
16
16
  if (options.allowedOrigins.includes("*")) process.stderr.write("[webmcp-local-relay] WARNING: accepting connections from ALL host page origins. Use --widget-origin to restrict.\n");
17
17
  const relay = new LocalRelayMcpServer({
18
18
  bridgeOptions: options,
19
- serverVersion: "6.0.2"
19
+ serverVersion: "6.1.0"
20
20
  });
21
21
  try {
22
22
  await relay.start();
package/dist/index.d.mts CHANGED
@@ -1065,6 +1065,7 @@ declare class LocalRelayMcpServer {
1065
1065
  * Registers a single dynamic tool and returns a removal handle.
1066
1066
  */
1067
1067
  private registerDynamicTool;
1068
+ private invokeRelayedTool;
1068
1069
  /**
1069
1070
  * Builds a display description for relayed tools including source context.
1070
1071
  * In client mode, source metadata is unavailable, so tools are labeled `[WebMCP relay]`.
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-DkidP4o7.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-BYF9j2fv.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 };
@@ -1434,6 +1434,7 @@ const JsonSchemaObjectSchema = z.record(z.string(), z.unknown());
1434
1434
  const RELAY_TOOL_NAMES = new Set([
1435
1435
  "webmcp_list_sources",
1436
1436
  "webmcp_list_tools",
1437
+ "webmcp_call_tool",
1437
1438
  "webmcp_open_page"
1438
1439
  ]);
1439
1440
  function textResult(text) {
@@ -1560,7 +1561,7 @@ var LocalRelayMcpServer = class {
1560
1561
  };
1561
1562
  });
1562
1563
  this.mcpServer.registerTool("webmcp_list_tools", {
1563
- description: "List WebMCP tools available from connected browser sources. Returns tool definitions including name, description, input schema, and source info.",
1564
+ description: "List WebMCP tools available from connected browser sources. Returns tool definitions including name, description, input schema, and source info. Use webmcp_call_tool to call one by name.",
1564
1565
  inputSchema: z.object({}),
1565
1566
  annotations: {
1566
1567
  readOnlyHint: true,
@@ -1580,6 +1581,17 @@ var LocalRelayMcpServer = class {
1580
1581
  structuredContent: info
1581
1582
  };
1582
1583
  });
1584
+ this.mcpServer.registerTool("webmcp_call_tool", {
1585
+ description: "Call a WebMCP tool from a connected browser page by name. Use webmcp_list_tools first to see available tools and their input schemas.",
1586
+ inputSchema: z.object({
1587
+ name: z.string().describe("Tool name as returned by webmcp_list_tools."),
1588
+ arguments: RelayInvokeArgsSchema.describe("Arguments matching the tool input schema.")
1589
+ }),
1590
+ annotations: { readOnlyHint: false }
1591
+ }, async ({ name, arguments: args }, context) => {
1592
+ if (!this.listAggregatedTools().some((tool) => tool.name === name)) return errorResult(`Tool "${name}" not found. Call webmcp_list_tools to see available tools.`);
1593
+ return this.invokeRelayedTool(name, args ?? {}, context.mcpReq.signal);
1594
+ });
1583
1595
  this.mcpServer.registerTool("webmcp_open_page", {
1584
1596
  description: "Open a URL in the user's default browser, or refresh a connected source page. Use to launch WebMCP-enabled pages or reload stale connections.",
1585
1597
  inputSchema: z.object({
@@ -1728,17 +1740,18 @@ var LocalRelayMcpServer = class {
1728
1740
  if (tool.annotations) registration.annotations = tool.annotations;
1729
1741
  if (tool.icons) registration.icons = tool.icons;
1730
1742
  if (tool._meta) registration._meta = tool._meta;
1731
- return this.mcpServer.registerTool(tool.name, registration, async (args, context) => {
1732
- try {
1733
- return await this.bridge.invokeTool(tool.name, args, { signal: context.mcpReq.signal });
1734
- } catch (err) {
1735
- if (context.mcpReq.signal.aborted) throw err;
1736
- const message = err instanceof Error ? err.message : String(err);
1737
- const details = err instanceof Error ? err.stack ?? err.message : String(err);
1738
- process.stderr.write(`[webmcp-local-relay] error: dynamic tool "${tool.name}" invocation failed: ${details}\n`);
1739
- return errorResult(`Failed to invoke relayed tool "${tool.name}": ${message}`);
1740
- }
1741
- });
1743
+ return this.mcpServer.registerTool(tool.name, registration, (args, context) => this.invokeRelayedTool(tool.name, args, context.mcpReq.signal));
1744
+ }
1745
+ async invokeRelayedTool(name, args, signal) {
1746
+ try {
1747
+ return await this.bridge.invokeTool(name, args, { signal });
1748
+ } catch (err) {
1749
+ if (signal.aborted) throw err;
1750
+ const message = err instanceof Error ? err.message : String(err);
1751
+ const details = err instanceof Error ? err.stack ?? err.message : String(err);
1752
+ process.stderr.write(`[webmcp-local-relay] error: relayed tool "${name}" invocation failed: ${details}\n`);
1753
+ return errorResult(`Failed to invoke relayed tool "${name}": ${message}`);
1754
+ }
1742
1755
  }
1743
1756
  /**
1744
1757
  * Builds a display description for relayed tools including source context.
@@ -1775,4 +1788,4 @@ var LocalRelayMcpServer = class {
1775
1788
 
1776
1789
  //#endregion
1777
1790
  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 };
1778
- //# sourceMappingURL=mcpRelayServer-DkidP4o7.mjs.map
1791
+ //# sourceMappingURL=mcpRelayServer-BYF9j2fv.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcpRelayServer-DkidP4o7.mjs","names":[],"sources":["../src/portStrategy.ts","../src/protocol.ts","../src/naming.ts","../src/registry.ts","../src/schemas.ts","../src/bridgeServer.ts","../src/cli-utils.ts","../src/mcpRelayServer.ts"],"sourcesContent":["import type { AddressInfo } from 'node:net';\n\nexport function isTcpAddress(address: AddressInfo | string | null): address is AddressInfo {\n return address !== null && typeof address !== 'string';\n}\n\nexport const DEFAULT_RELAY_PORT = 9333;\nexport const DEFAULT_RELAY_PORT_RANGE_END = 9348;\n","import type { CallToolResult, Tool, ToolAnnotations } from '@modelcontextprotocol/server';\nimport {\n CallToolRequestParamsSchema,\n CallToolResultSchema,\n ToolAnnotationsSchema,\n ToolSchema,\n} from '@modelcontextprotocol/core';\nimport { z } from 'zod/v4';\n\n/**\n * SDK-derived canonical tool schema used internally by the relay.\n */\nexport const NormalizedToolSchema = ToolSchema.extend({\n name: ToolSchema.shape.name.min(1),\n});\n\n/**\n * SDK-derived argument schema for tool invocation payloads.\n */\nexport const RelayInvokeArgsSchema = CallToolRequestParamsSchema.shape.arguments;\n\n/**\n * Default input schema applied when inbound payload omits or provides\n * a non-object input schema.\n */\nexport const DEFAULT_TOOL_INPUT_SCHEMA: Tool['inputSchema'] = {\n type: 'object',\n properties: {},\n};\n\n/** SDK tool schema with a browser-compatible default input schema. */\nexport const InboundToolSchema = NormalizedToolSchema.extend({\n // WebMCP accepts any JSON object as an input schema; MCP requires `type: 'object'`.\n inputSchema: z\n .looseObject({})\n .transform((schema) => ({ type: 'object', ...schema }))\n .pipe(NormalizedToolSchema.shape.inputSchema)\n .optional(),\n});\n\n/**\n * Canonical normalized relay tool shape.\n */\nexport type RelayTool = z.infer<typeof NormalizedToolSchema>;\n\n/**\n * Canonical relay tool annotations shape.\n */\nexport type RelayToolAnnotations = ToolAnnotations;\n\n/**\n * Canonical relay call result shape.\n */\nexport type RelayCallToolResult = CallToolResult;\n\n/**\n * Invocation argument object shape derived from MCP SDK request params.\n */\nexport type RelayInvokeArgs = Exclude<z.infer<typeof RelayInvokeArgsSchema>, undefined>;\n\n/**\n * Applies the browser default input schema and validates the complete SDK tool.\n */\nexport function normalizeInboundTool(inbound: z.infer<typeof InboundToolSchema>): RelayTool {\n return NormalizedToolSchema.parse({\n ...inbound,\n inputSchema: inbound.inputSchema ?? DEFAULT_TOOL_INPUT_SCHEMA,\n });\n}\n\nexport { CallToolRequestParamsSchema, CallToolResultSchema, ToolAnnotationsSchema, ToolSchema };\n","/**\n * Maximum tool name length supported across MCP clients.\n */\nexport const MAX_MCP_TOOL_NAME_LENGTH = 128;\n\n/**\n * Number of tab-id characters appended when disambiguation is required.\n */\nconst TAB_ID_DISAMBIGUATION_LENGTH = 4;\n\n/**\n * Converts arbitrary text into MCP-safe identifier characters.\n */\nexport function sanitizeName(value: string): string {\n return value.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\n/**\n * Extracts and sanitizes a domain label from an origin or URL.\n */\nexport function extractSanitizedDomain(originOrUrl?: string): string {\n if (!originOrUrl) {\n return 'unknown';\n }\n\n try {\n const parsed = new URL(originOrUrl);\n if (!parsed.hostname) {\n return 'unknown';\n }\n\n const isLocalhost =\n parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1' ||\n parsed.hostname === '[::1]';\n\n const domain = isLocalhost ? `localhost_${parsed.port || '80'}` : parsed.hostname;\n return sanitizeName(domain);\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Builds a public tool name for MCP registration.\n *\n * Names can include a short tab-id suffix when multiple tabs publish the same\n * original tool name.\n */\nexport function buildPublicToolName(options: {\n originalToolName: string;\n tabId?: string;\n disambiguate?: boolean;\n}): string {\n const safeName = sanitizeName(options.originalToolName);\n\n if (!options.disambiguate || !options.tabId) {\n return safeName.slice(0, MAX_MCP_TOOL_NAME_LENGTH);\n }\n\n const shortTab = sanitizeName(options.tabId).slice(0, TAB_ID_DISAMBIGUATION_LENGTH);\n const suffix = `_${shortTab}`;\n const available = MAX_MCP_TOOL_NAME_LENGTH - suffix.length;\n return `${safeName.slice(0, available)}${suffix}`;\n}\n","import { buildPublicToolName, MAX_MCP_TOOL_NAME_LENGTH } from './naming.js';\nimport type { RelayTool } from './protocol.js';\nimport type { BrowserHelloMessage } from './schemas.js';\n\n/**\n * Internal metadata tracked for each active browser source.\n */\ninterface SourceMetadata {\n sourceId: string;\n tabId: string;\n // Optional keys, matching the wire shape in RelaySourceInfoSchema: a browser\n // source may omit these entirely rather than send them as undefined.\n origin?: string | undefined;\n url?: string | undefined;\n title?: string | undefined;\n iconUrl?: string | undefined;\n connectedAt: number;\n lastSeenAt: number;\n}\n\n/**\n * Public source metadata exposed by registry APIs.\n */\nexport interface SourceInfo extends SourceMetadata {\n toolCount: number;\n}\n\n/**\n * Aggregated relayed tool metadata across one or more sources.\n *\n * Extends {@link RelayTool} with relay-specific fields so new SDK tool\n * properties are automatically inherited without manual duplication.\n */\nexport interface AggregatedTool extends RelayTool {\n originalName: string;\n sources: SourceInfo[];\n}\n\n/**\n * Internal record linking a source to a concrete tool implementation.\n */\ninterface ToolProvider {\n sourceId: string;\n tabId: string;\n tool: RelayTool;\n publicToolName: string;\n}\n\n/**\n * Invocation target selected by the registry.\n */\nexport interface ResolvedInvocation {\n connectionId: string;\n tool: RelayTool;\n publicToolName: string;\n}\n\n/**\n * Error thrown when a source attempts to register tools before `hello`.\n */\nexport class HelloRequiredError extends Error {\n readonly connectionId: string;\n\n constructor(connectionId: string) {\n super(`Connection ${connectionId} must send hello before tools`);\n this.name = 'HelloRequiredError';\n this.connectionId = connectionId;\n }\n}\n\n/**\n * In-memory source and tool registry used by the relay bridge.\n */\nexport class RelayRegistry {\n private readonly sourceByConnectionId = new Map<string, SourceMetadata>();\n private readonly toolsByConnectionId = new Map<string, ToolProvider[]>();\n private readonly providersByPublicToolName = new Map<string, ToolProvider[]>();\n\n /**\n * @param now Time provider used for recency ordering.\n */\n constructor(private readonly now: () => number = () => Date.now()) {}\n\n /**\n * Upserts source metadata from a browser `hello` message.\n *\n * `sourceId` currently matches `connectionId`, but remains conceptually distinct\n * so stable source identity can be introduced in the future.\n */\n upsertSource(connectionId: string, hello: BrowserHelloMessage): void {\n const now = this.now();\n const existing = this.sourceByConnectionId.get(connectionId);\n\n this.sourceByConnectionId.set(connectionId, {\n sourceId: connectionId,\n tabId: hello.tabId,\n origin: hello.origin ?? existing?.origin,\n url: hello.url ?? existing?.url,\n title: hello.title ?? existing?.title,\n iconUrl: hello.iconUrl ?? existing?.iconUrl,\n connectedAt: existing?.connectedAt ?? now,\n lastSeenAt: now,\n });\n\n if (existing && existing.tabId !== hello.tabId) {\n for (const provider of this.toolsByConnectionId.get(connectionId) ?? []) {\n provider.tabId = hello.tabId;\n }\n this.rebuildPublicNames();\n }\n }\n\n /**\n * Replaces the full tool set for a source connection.\n */\n registerTools(connectionId: string, tools: RelayTool[]): void {\n const source = this.sourceByConnectionId.get(connectionId);\n if (!source) {\n throw new HelloRequiredError(connectionId);\n }\n\n this.touchConnection(connectionId);\n\n const providers: ToolProvider[] = tools.map((tool) => ({\n sourceId: connectionId,\n tabId: source.tabId,\n tool,\n publicToolName: '',\n }));\n\n this.toolsByConnectionId.set(connectionId, providers);\n this.rebuildPublicNames();\n }\n\n /**\n * Removes a source and all of its tool registrations.\n */\n removeConnection(connectionId: string): void {\n this.toolsByConnectionId.delete(connectionId);\n this.sourceByConnectionId.delete(connectionId);\n this.rebuildPublicNames();\n }\n\n /**\n * Marks a source as recently seen without mutating identity metadata.\n */\n touchConnection(connectionId: string): void {\n const source = this.sourceByConnectionId.get(connectionId);\n if (!source) {\n return;\n }\n\n source.lastSeenAt = this.now();\n }\n\n /**\n * Lists active sources that currently publish at least one tool.\n */\n listSources(): SourceInfo[] {\n return Array.from(this.sourceByConnectionId.values())\n .map((source) => this.toSourceInfo(source))\n .filter((source) => source.toolCount > 0)\n .sort((a, b) => this.compareRecency(b.lastSeenAt, a.lastSeenAt, b.sourceId, a.sourceId));\n }\n\n /**\n * Lists aggregated tools ordered by public tool name.\n */\n listTools(): AggregatedTool[] {\n const result: AggregatedTool[] = [];\n\n for (const [publicToolName, providers] of this.providersByPublicToolName.entries()) {\n const rankedProviders = this.sortProvidersByRecency(providers);\n const primaryProvider = rankedProviders[0];\n if (!primaryProvider) {\n continue;\n }\n\n const sources = rankedProviders\n .map((provider) => {\n const source = this.sourceByConnectionId.get(provider.sourceId);\n return source ? this.toSourceInfo(source) : undefined;\n })\n .filter((source): source is SourceInfo => Boolean(source));\n\n result.push({\n ...primaryProvider.tool,\n name: publicToolName,\n originalName: primaryProvider.tool.name,\n sources,\n });\n }\n\n return result.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n /**\n * Resolves a tool invocation to a concrete source provider.\n *\n * Resolution order:\n * 1. `sourceId`: exact source id, then fallback to tab id.\n * 2. `requestTabId`: strict tab id match with no fallback.\n * 3. Default: most recently seen provider.\n */\n resolveInvocation(options: {\n toolName: string;\n sourceId?: string;\n requestTabId?: string;\n }): ResolvedInvocation | null {\n const providers = this.providersByPublicToolName.get(options.toolName);\n if (!providers || providers.length === 0) {\n return null;\n }\n\n const rankedProviders = this.sortProvidersByRecency(providers);\n\n let provider: ToolProvider | undefined;\n if (options.sourceId) {\n provider =\n rankedProviders.find((candidate) => candidate.sourceId === options.sourceId) ??\n rankedProviders.find((candidate) => candidate.tabId === options.sourceId);\n } else if (options.requestTabId) {\n provider = rankedProviders.find((candidate) => candidate.tabId === options.requestTabId);\n } else {\n provider = rankedProviders[0];\n }\n\n if (!provider) {\n return null;\n }\n\n return {\n connectionId: provider.sourceId,\n tool: provider.tool,\n publicToolName: provider.publicToolName,\n };\n }\n\n /**\n * Converts internal source metadata to public source info.\n */\n private toSourceInfo(source: SourceMetadata): SourceInfo {\n return {\n ...source,\n toolCount: this.toolsByConnectionId.get(source.sourceId)?.length ?? 0,\n };\n }\n\n /**\n * Rebuilds public tool names after source set changes.\n *\n * When the same original tool name appears in multiple tabs, names are\n * disambiguated with a short tab suffix.\n */\n private rebuildPublicNames(): void {\n const byOriginalName = new Map<string, ToolProvider[]>();\n for (const providers of this.toolsByConnectionId.values()) {\n for (const provider of providers) {\n const key = provider.tool.name;\n const group = byOriginalName.get(key) ?? [];\n group.push(provider);\n byOriginalName.set(key, group);\n }\n }\n\n this.providersByPublicToolName.clear();\n const allocations: Array<{ candidate: string; key: string; provider: ToolProvider }> = [];\n\n for (const [originalName, providers] of byOriginalName) {\n const uniqueTabIds = new Set(providers.map((p) => p.tabId));\n const disambiguate = uniqueTabIds.size > 1;\n\n for (const provider of providers) {\n allocations.push({\n candidate: buildPublicToolName({\n originalToolName: originalName,\n tabId: provider.tabId,\n disambiguate,\n }),\n key: disambiguate ? `${originalName}\\0${provider.tabId}` : originalName,\n provider,\n });\n }\n }\n\n const nameByKey = new Map<string, string>();\n const usedNames = new Set<string>();\n for (const { candidate, key, provider } of allocations.sort((a, b) =>\n a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n )) {\n let publicToolName = nameByKey.get(key);\n if (!publicToolName) {\n publicToolName = candidate;\n for (let index = 2; usedNames.has(publicToolName); index += 1) {\n const suffix = `_${String(index)}`;\n publicToolName = `${candidate.slice(0, MAX_MCP_TOOL_NAME_LENGTH - suffix.length)}${suffix}`;\n }\n nameByKey.set(key, publicToolName);\n usedNames.add(publicToolName);\n }\n\n provider.publicToolName = publicToolName;\n\n const existing = this.providersByPublicToolName.get(publicToolName) ?? [];\n existing.push(provider);\n this.providersByPublicToolName.set(publicToolName, existing);\n }\n }\n\n /**\n * Returns providers sorted by source recency.\n */\n private sortProvidersByRecency(providers: ToolProvider[]): ToolProvider[] {\n return providers.slice().sort((a, b) => {\n const aSource = this.sourceByConnectionId.get(a.sourceId);\n const bSource = this.sourceByConnectionId.get(b.sourceId);\n const aLastSeen = aSource?.lastSeenAt ?? 0;\n const bLastSeen = bSource?.lastSeenAt ?? 0;\n\n return this.compareRecency(bLastSeen, aLastSeen, b.sourceId, a.sourceId);\n });\n }\n\n /**\n * Compares two source recency tuples and stabilizes order by source id.\n */\n private compareRecency(\n leftLastSeen: number,\n rightLastSeen: number,\n leftId: string,\n rightId: string\n ): number {\n const timeDiff = leftLastSeen - rightLastSeen;\n if (timeDiff !== 0) {\n return timeDiff;\n }\n return leftId.localeCompare(rightId);\n }\n}\n","import { z } from 'zod/v4';\nimport {\n CallToolResultSchema,\n InboundToolSchema,\n NormalizedToolSchema,\n normalizeInboundTool,\n RelayInvokeArgsSchema,\n type RelayTool,\n} from './protocol.js';\n\n/** Parses each tool on its own, so one malformed tool does not hide the page's other tools. */\nfunction parseBrowserTools(tools: unknown[]): RelayTool[] {\n return tools.flatMap((raw) => {\n const parsed = InboundToolSchema.safeParse(raw);\n if (!parsed.success) {\n process.stderr.write(\n `[webmcp-local-relay] warn: skipped a malformed page tool: ${parsed.error.message}\\n`\n );\n return [];\n }\n const tool = normalizeInboundTool(parsed.data);\n return tool.execution?.taskSupport === 'required' ? [] : [tool];\n });\n}\n\nconst BrowserHelloMessageSchema = z.object({\n type: z.literal('hello'),\n tabId: z.string().min(1),\n origin: z.string().optional(),\n url: z.string().optional(),\n title: z.string().optional(),\n iconUrl: z.string().optional(),\n});\n\nconst BrowserToolsListMessageSchema = z.object({\n type: z.literal('tools/list'),\n tools: z.array(z.unknown()).transform(parseBrowserTools),\n});\n\nconst BrowserToolsChangedMessageSchema = z.object({\n type: z.literal('tools/changed'),\n tools: z.array(z.unknown()).transform(parseBrowserTools),\n});\n\nconst BrowserToolResultMessageSchema = z.object({\n type: z.literal('result'),\n callId: z.string().min(1),\n result: z.unknown(),\n});\n\nconst BrowserPongMessageSchema = z.object({\n type: z.literal('pong'),\n});\n\n/**\n * Union schema for all browser-to-relay protocol messages.\n */\nexport const BrowserToRelayMessageSchema = z.discriminatedUnion('type', [\n BrowserHelloMessageSchema,\n BrowserToolsListMessageSchema,\n BrowserToolsChangedMessageSchema,\n BrowserToolResultMessageSchema,\n BrowserPongMessageSchema,\n]);\n\n/**\n * Browser-to-relay message payload.\n */\nexport type BrowserToRelayMessage = z.infer<typeof BrowserToRelayMessageSchema>;\n\n/**\n * Browser source bootstrap message.\n */\nexport type BrowserHelloMessage = z.infer<typeof BrowserHelloMessageSchema>;\n\n/**\n * Shared relay identity used for discovery and attach verification.\n */\nexport const RelayDescriptorSchema = z.object({\n host: z.string().min(1),\n instanceId: z.string().min(1),\n label: z.string().min(1).optional(),\n port: z.number().int().min(1).max(65535),\n relayId: z.string().min(1).optional(),\n workspace: z.string().min(1).optional(),\n});\n\n/**\n * Relay identity used for discovery and attach verification.\n */\nexport type RelayDescriptor = z.infer<typeof RelayDescriptorSchema>;\n\n/**\n * Schema for server hello messages sent immediately after WebSocket connect.\n */\nexport const ServerHelloMessageSchema = RelayDescriptorSchema.extend({\n type: z.literal('server-hello'),\n service: z.literal('webmcp-local-relay'),\n version: z.literal(1),\n});\n\n/**\n * Server hello payload.\n */\nexport type ServerHelloMessage = z.infer<typeof ServerHelloMessageSchema>;\n\n/**\n * Schema for acceptance of a browser source hello message.\n */\nexport const RelayHelloAcceptedMessageSchema = z.object({\n type: z.literal('hello/accepted'),\n});\n\n/**\n * Acceptance payload for a browser source hello message.\n */\nexport type RelayHelloAcceptedMessage = z.infer<typeof RelayHelloAcceptedMessageSchema>;\n\n/**\n * Schema for rejection of a browser source hello message.\n */\nexport const RelayHelloRejectedMessageSchema = z.object({\n type: z.literal('hello/rejected'),\n reason: z.string().min(1),\n message: z.string().min(1),\n});\n\n/**\n * Rejection payload for a browser source hello message.\n */\nexport type RelayHelloRejectedMessage = z.infer<typeof RelayHelloRejectedMessageSchema>;\n\nconst RelayInvokeMessageSchema = z.object({\n type: z.literal('invoke'),\n callId: z.string().min(1),\n toolName: z.string().min(1),\n args: RelayInvokeArgsSchema,\n});\n\nconst RelayCancelMessageSchema = z.object({\n type: z.literal('cancel'),\n callId: z.string().min(1),\n});\n\nconst RelayPingMessageSchema = z.object({\n type: z.literal('ping'),\n});\n\nconst RelayReloadMessageSchema = z.object({\n type: z.literal('reload'),\n});\n\n/**\n * Union schema for all relay-to-browser protocol messages.\n */\nexport const RelayToBrowserMessageSchema = z.discriminatedUnion('type', [\n ServerHelloMessageSchema,\n RelayHelloAcceptedMessageSchema,\n RelayHelloRejectedMessageSchema,\n RelayInvokeMessageSchema,\n RelayCancelMessageSchema,\n RelayPingMessageSchema,\n RelayReloadMessageSchema,\n]);\n\n/**\n * Relay-to-browser message payload.\n */\nexport type RelayToBrowserMessage = z.infer<typeof RelayToBrowserMessageSchema>;\n\n/**\n * Schema for source metadata transmitted in relay-to-relay messages.\n */\nexport const RelaySourceInfoSchema = z.object({\n sourceId: z.string(),\n tabId: z.string(),\n origin: z.string().optional(),\n url: z.string().optional(),\n title: z.string().optional(),\n iconUrl: z.string().optional(),\n connectedAt: z.number(),\n lastSeenAt: z.number(),\n toolCount: z.number(),\n});\n\n/**\n * Source metadata transmitted in relay-to-relay messages.\n */\nexport type RelaySourceInfo = z.infer<typeof RelaySourceInfoSchema>;\n\nconst RelayClientHelloSchema = z.object({\n type: z.literal('relay/hello'),\n});\n\nconst RelayClientListToolsSchema = z.object({\n type: z.literal('relay/list-tools'),\n});\n\nconst RelayClientInvokeSchema = z.object({\n type: z.literal('relay/invoke'),\n callId: z.string().min(1),\n toolName: z.string().min(1),\n args: RelayInvokeArgsSchema,\n});\n\nconst RelayClientCancelSchema = z.object({\n type: z.literal('relay/cancel'),\n callId: z.string().min(1),\n});\n\n/**\n * Union schema for all relay-client-to-server messages.\n */\nexport const RelayClientToServerMessageSchema = z.discriminatedUnion('type', [\n RelayClientHelloSchema,\n RelayClientListToolsSchema,\n RelayClientInvokeSchema,\n RelayClientCancelSchema,\n]);\n\n/**\n * Relay client to server message payload.\n */\nexport type RelayClientToServerMessage = z.infer<typeof RelayClientToServerMessageSchema>;\n\nconst RelayToolsPayloadFields = {\n tools: z.array(NormalizedToolSchema),\n sources: z.array(RelaySourceInfoSchema),\n toolSourceMap: z.record(z.string(), z.array(z.string())),\n};\n\nconst RelayServerToolsSchema = z.object({\n type: z.literal('relay/tools'),\n ...RelayToolsPayloadFields,\n});\n\nconst RelayServerResultSchema = z.object({\n type: z.literal('relay/result'),\n callId: z.string().min(1),\n result: CallToolResultSchema,\n});\n\nconst RelayServerToolsChangedSchema = z.object({\n type: z.literal('relay/tools-changed'),\n ...RelayToolsPayloadFields,\n});\n\n/**\n * Union schema for all relay-server-to-client messages.\n */\nexport const RelayServerToClientMessageSchema = z.discriminatedUnion('type', [\n ServerHelloMessageSchema,\n RelayServerToolsSchema,\n RelayServerResultSchema,\n RelayServerToolsChangedSchema,\n]);\n\n/**\n * Relay server to client message payload.\n */\nexport type RelayServerToClientMessage = z.infer<typeof RelayServerToClientMessageSchema>;\n","import { z } from 'zod/v4';\nimport { randomUUID } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\n\nimport WebSocket, { WebSocketServer } from 'ws';\nimport { DEFAULT_RELAY_PORT, DEFAULT_RELAY_PORT_RANGE_END, isTcpAddress } from './portStrategy.js';\nimport {\n CallToolResultSchema,\n type RelayCallToolResult,\n type RelayInvokeArgs,\n type RelayTool,\n} from './protocol.js';\nimport { RelayRegistry } from './registry.js';\nimport {\n type BrowserToRelayMessage,\n BrowserToRelayMessageSchema,\n type RelayClientToServerMessage,\n RelayClientToServerMessageSchema,\n type RelayServerToClientMessage,\n RelayServerToClientMessageSchema,\n type RelayHelloAcceptedMessage,\n type RelayHelloRejectedMessage,\n type RelaySourceInfo,\n type RelayToBrowserMessage,\n type ServerHelloMessage,\n} from './schemas.js';\n\nconst RELAY_BROWSER_PROTOCOL = 'webmcp.v1';\nconst RELAY_DISCOVERY_PROTOCOL = 'webmcp-discovery.v1';\nconst RELAY_INTERNAL_PROTOCOL = 'webmcp-relay.v1';\n/**\n * How long a joining relay waits for the relay on a port to greet it. Too short, and a busy\n * relay is mistaken for another service, so a second relay starts and splits pages from clients.\n */\nconst RELAY_SERVER_MESSAGE_TIMEOUT_MS = 5_000;\nconst HEARTBEAT_INTERVAL_MS = 15_000;\nconst HEARTBEAT_DEAD_THRESHOLD_MS = 25_000;\nconst RawMessageTypeSchema = z.object({ type: z.unknown().optional() });\nconst RelayProtocolTypeSchema = z.string().startsWith('relay/');\nconst SUPPORTED_SUBPROTOCOLS = new Set([\n RELAY_BROWSER_PROTOCOL,\n RELAY_DISCOVERY_PROTOCOL,\n RELAY_INTERNAL_PROTOCOL,\n]);\n\n/**\n * In-flight relay invocation waiting for a browser `result` message.\n */\ninterface PendingInvocation {\n connectionId: string;\n resolve: (result: RelayCallToolResult) => void;\n reject: (error: Error) => void;\n cancel: (reason: Error) => void;\n}\n\nfunction formatPayloadLimit(bytes: number): string {\n if (bytes >= 1_000_000 && bytes % 1_000_000 === 0) {\n return `${String(bytes / 1_000_000)}MB`;\n }\n return `${String(bytes)} bytes`;\n}\n\n/**\n * Runtime options for {@link RelayBridgeServer}.\n */\nexport interface RelayBridgeServerOptions {\n /**\n * Network interface used by the local WebSocket server.\n * @defaultValue `\"127.0.0.1\"`\n */\n host?: string;\n /**\n * Preferred WebSocket port for browser widget connections.\n * @defaultValue `9333`\n */\n port?: number;\n /**\n * Whether the preferred port came from an explicit CLI/API override.\n * Explicit ports fail if occupied by a non-relay process.\n * @defaultValue `false`\n */\n portExplicitlySet?: boolean;\n /**\n * Inclusive upper bound for automatic port discovery.\n * @defaultValue `9348`\n */\n portRangeEnd?: number;\n /**\n * @deprecated Ignored. The relay no longer caches its port, so it always joins a relay\n * that owns the preferred port instead of starting a second one elsewhere.\n */\n persistPath?: string;\n /**\n * Allowed host page origins verified from browser WebSocket requests.\n * Use `[\"*\"]` to allow all origins.\n *\n * Permissive by default for zero-config developer experience: any browser\n * page can connect and register tools without additional setup. For production\n * or shared machines, restrict to trusted host origins via the\n * `--widget-origin` CLI flag or by passing explicit origins here.\n *\n * @defaultValue `[\"*\"]`\n */\n allowedOrigins?: string[];\n /**\n * Maximum WebSocket payload size in bytes.\n * @defaultValue `10000000`\n */\n maxPayloadBytes?: number;\n /**\n * Timeout used for browser tool invocations.\n * @defaultValue `65000`\n */\n invokeTimeoutMs?: number;\n /**\n * Human-readable relay label reported in discovery handshakes.\n */\n label?: string;\n /**\n * Optional workspace name reported in discovery handshakes.\n */\n workspace?: string;\n /**\n * Stable relay identifier used to select between multiple relays.\n */\n relayId?: string;\n}\n\n/**\n * WebSocket relay between browser widget frames and MCP server calls.\n *\n * Operates in two modes:\n * - **server** (default): Runs a WebSocket server, accepts browser and relay\n * client connections.\n * - **client** (fallback on EADDRINUSE): Connects as a WebSocket client to an\n * existing server relay and proxies tool operations through it.\n */\nexport class RelayBridgeServer extends EventEmitter {\n /**\n * Registry for connected sources and aggregated tool definitions.\n * Only actively used in server mode; remains empty when operating as a client.\n */\n readonly registry: RelayRegistry;\n\n private readonly host: string;\n private readonly preferredPort: number;\n private desiredPort: number;\n private readonly portExplicitlySet: boolean;\n private readonly portRangeEnd: number;\n private readonly allowedOrigins: string[];\n private readonly maxPayloadBytes: number;\n private readonly invokeTimeoutMs: number;\n private readonly label: string | undefined;\n private readonly workspace: string | undefined;\n private readonly relayId: string | undefined;\n private readonly instanceId: string;\n\n private wss: WebSocketServer | null = null;\n private readonly socketByConnectionId = new Map<string, WebSocket>();\n private readonly requestOriginByConnectionId = new Map<string, string>();\n private readonly pendingInvocations = new Map<string, PendingInvocation>();\n private readonly browserClientConnectionIds = new Set<string>();\n private readonly relayClientConnectionIds = new Set<string>();\n private readonly relayClientInvocations = new Map<string, Map<string, AbortController>>();\n private readonly heartbeatIntervalByConnectionId = new Map<\n string,\n ReturnType<typeof setInterval>\n >();\n private readonly lastPongByConnectionId = new Map<string, number>();\n private readonly onStateChangedPushRelay = () => {\n this.pushToolsToRelayClients();\n };\n\n private _mode: 'server' | 'client' = 'server';\n private clientSocket: WebSocket | null = null;\n private clientReconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private clientReconnectDelay = 500;\n /**\n * Maximum delay for relay-to-relay reconnection backoff in client mode.\n */\n private readonly clientMaxReconnectDelay = 3_000;\n private readonly clientPendingInvocations = new Map<string, PendingInvocation>();\n private clientTools: RelayTool[] = [];\n private clientSources: RelaySourceInfo[] = [];\n private clientToolSourceMap: Record<string, string[]> = {};\n private stopping = false;\n\n /**\n * Creates a relay bridge server instance.\n */\n constructor(options: RelayBridgeServerOptions = {}, registry?: RelayRegistry) {\n super();\n this.registry = registry ?? new RelayRegistry();\n\n this.host = options.host ?? '127.0.0.1';\n this.preferredPort = options.port ?? DEFAULT_RELAY_PORT;\n this.desiredPort = this.preferredPort;\n this.portExplicitlySet = options.portExplicitlySet ?? false;\n this.portRangeEnd =\n options.portRangeEnd ?? Math.max(DEFAULT_RELAY_PORT_RANGE_END, this.preferredPort);\n this.allowedOrigins = options.allowedOrigins ?? ['*'];\n // 10MB default: large enough for typical JSON API responses while\n // still protecting the relay process from unbounded memory growth.\n this.maxPayloadBytes = options.maxPayloadBytes ?? 10_000_000;\n this.invokeTimeoutMs = options.invokeTimeoutMs ?? 65_000;\n this.label = options.label;\n this.workspace = options.workspace;\n this.relayId = options.relayId;\n this.instanceId = randomUUID();\n\n if (\n !Number.isInteger(this.preferredPort) ||\n this.preferredPort < 0 ||\n this.preferredPort > 65535\n ) {\n throw new Error(\n `Invalid port ${this.preferredPort}. Port must be 0 (auto-assign) or between 1 and 65535.`\n );\n }\n if (\n !Number.isInteger(this.portRangeEnd) ||\n this.portRangeEnd < 1 ||\n this.portRangeEnd > 65535 ||\n (this.portRangeEnd < this.preferredPort && this.preferredPort !== 0)\n ) {\n throw new Error(\n `Invalid port range ${this.preferredPort}-${this.portRangeEnd}. rangeEnd must be an integer between the preferred port and 65535.`\n );\n }\n if (!Number.isInteger(this.maxPayloadBytes) || this.maxPayloadBytes <= 0) {\n throw new Error(\n `Invalid maxPayloadBytes ${this.maxPayloadBytes}. Must be a positive integer.`\n );\n }\n if (!Number.isInteger(this.invokeTimeoutMs) || this.invokeTimeoutMs <= 0) {\n throw new Error(\n `Invalid invokeTimeoutMs ${this.invokeTimeoutMs}. Must be a positive integer.`\n );\n }\n }\n\n /**\n * Current operating mode.\n */\n get mode(): 'server' | 'client' {\n return this._mode;\n }\n\n /**\n * Resolved listening port. In client mode this is the port of the server\n * relay being proxied through.\n */\n get port(): number {\n return this.desiredPort;\n }\n\n /**\n * Tools received from the server relay (client mode only).\n * Returns an empty array in server mode.\n */\n listToolsFromRelay(): RelayTool[] {\n return this._mode === 'client' ? [...this.clientTools] : [];\n }\n\n /**\n * Source metadata received from the server relay (client mode only).\n * Returns an empty array in server mode.\n */\n listSourcesFromRelay(): RelaySourceInfo[] {\n return this._mode === 'client' ? [...this.clientSources] : [];\n }\n\n /**\n * Tool-to-source mapping received from the server relay (client mode only).\n * Maps public tool names to arrays of source IDs.\n * Returns an empty record in server mode.\n */\n getToolSourceMapFromRelay(): Record<string, string[]> {\n return this._mode === 'client' ? { ...this.clientToolSourceMap } : {};\n }\n\n /**\n * Starts the bridge. Attempts to bind a WebSocket server (server mode).\n * If a compatible relay already owns a candidate port, joins it in client mode.\n * If a non-relay process owns the port, continues searching the reserved range.\n */\n async start(): Promise<void> {\n if (this.wss || this.clientSocket) {\n return;\n }\n\n this.stopping = false;\n await this.startUsingPortStrategy();\n }\n\n private async startUsingPortStrategy(): Promise<void> {\n if (this.preferredPort === 0) {\n await this.startAsServer(0);\n return;\n }\n\n const lastPort = this.portExplicitlySet ? this.preferredPort : this.portRangeEnd;\n for (let port = this.preferredPort; port <= lastPort; port++) {\n try {\n await this.startAsServer(port);\n return;\n } catch (err) {\n if (!this.isAddressInUseError(err)) {\n throw err;\n }\n\n if (await this.tryAttachToExistingRelay(port)) {\n return;\n }\n\n if (this.portExplicitlySet) {\n throw new Error(\n `Port ${port} is already in use by a non-WebMCP service and cannot be shared.`\n );\n }\n\n process.stderr.write(\n `[webmcp-local-relay] info: port ${port} is occupied by a non-relay service, trying next port\\n`\n );\n }\n }\n\n throw new Error(\n `No compatible relay port was available in the range ${this.preferredPort}-${this.portRangeEnd}.`\n );\n }\n\n private isAddressInUseError(cause: unknown): boolean {\n return (\n cause instanceof Error &&\n ('code' in cause ? cause.code === 'EADDRINUSE' : cause.message.includes('EADDRINUSE'))\n );\n }\n\n private async tryAttachToExistingRelay(port: number): Promise<boolean> {\n try {\n await this.startAsClient(port);\n process.stderr.write(\n `[webmcp-local-relay] info: discovered compatible relay on port ${port}, switching to client mode\\n`\n );\n return true;\n } catch (error) {\n // The caller reports every false here as \"occupied by a non-relay service\". A relay\n // that is stuck past the hello timeout lands here too, so name the real reason rather\n // than leaving the operator with a wrong hypothesis.\n process.stderr.write(\n `[webmcp-local-relay] info: port ${port} did not complete a relay handshake: ${error instanceof Error ? error.message : String(error)}\\n`\n );\n return false;\n }\n }\n\n /**\n * Stops all relay resources and rejects any pending invocations.\n */\n async stop(): Promise<void> {\n this.stopping = true;\n\n if (this.clientReconnectTimer) {\n clearTimeout(this.clientReconnectTimer);\n this.clientReconnectTimer = null;\n }\n\n if (this._mode === 'client') {\n for (const pending of this.clientPendingInvocations.values()) {\n pending.cancel(new Error('Relay client stopped'));\n }\n this.clientTools = [];\n this.clientSources = [];\n this.clientToolSourceMap = {};\n\n if (this.clientSocket) {\n try {\n this.clientSocket.close(1000, 'Relay client shutting down');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: error closing client socket during shutdown: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n this.clientSocket = null;\n }\n return;\n }\n\n this.off('stateChanged', this.onStateChangedPushRelay);\n\n for (const pending of this.pendingInvocations.values()) {\n pending.cancel(new Error('Relay server stopped before tool invocation completed'));\n }\n for (const invocations of this.relayClientInvocations.values()) {\n for (const controller of invocations.values()) controller.abort();\n }\n this.relayClientInvocations.clear();\n\n for (const [connectionId, socket] of this.socketByConnectionId) {\n this.registry.removeConnection(connectionId);\n try {\n socket.close(1001, 'Relay shutting down');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: error closing socket during shutdown: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }\n\n for (const intervalId of this.heartbeatIntervalByConnectionId.values()) {\n clearInterval(intervalId);\n }\n this.heartbeatIntervalByConnectionId.clear();\n this.lastPongByConnectionId.clear();\n\n this.socketByConnectionId.clear();\n this.requestOriginByConnectionId.clear();\n this.browserClientConnectionIds.clear();\n this.relayClientConnectionIds.clear();\n\n const wss = this.wss;\n this.wss = null;\n\n if (!wss) {\n return;\n }\n\n // Peers that stopped responding never acknowledge the close, and wss.close waits for them.\n const lingering = setTimeout(() => {\n for (const client of wss.clients) client.terminate();\n }, 500);\n await new Promise<void>((resolve) => {\n wss.close((err?: Error) => {\n if (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: WebSocket server close error: ${err.message}\\n`\n );\n }\n resolve();\n });\n });\n clearTimeout(lingering);\n }\n\n /**\n * Sends a reload message to a connected browser source.\n * Only supported in server mode.\n */\n reloadSource(connectionId: string): void {\n if (this._mode !== 'server') {\n throw new Error('reloadSource is only supported in server mode');\n }\n const socket = this.socketByConnectionId.get(connectionId);\n if (!socket || socket.readyState !== WebSocket.OPEN) {\n throw new Error(`Source ${connectionId} is not connected`);\n }\n const message: RelayToBrowserMessage = { type: 'reload' };\n try {\n socket.send(JSON.stringify(message));\n } catch (err) {\n throw new Error(`Failed to send reload: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n /**\n * Invokes a tool either locally (server mode) or through the upstream relay\n * (client mode).\n */\n async invokeTool(\n toolName: string,\n args: RelayInvokeArgs,\n options: {\n sourceId?: string;\n requestTabId?: string;\n /** Cancels the invocation and forwards cancellation to the browser runtime. */\n signal?: AbortSignal;\n } = {}\n ): Promise<RelayCallToolResult> {\n options.signal?.throwIfAborted();\n if (this._mode === 'client') {\n return this.invokeToolViaRelay(toolName, args, options.signal);\n }\n\n return this.invokeToolLocally(toolName, args, options);\n }\n\n private async startAsServer(port = this.desiredPort): Promise<void> {\n const wss = await new Promise<WebSocketServer>((resolve, reject) => {\n const server = new WebSocketServer({\n handleProtocols: (protocols) => {\n for (const protocol of protocols) {\n if (SUPPORTED_SUBPROTOCOLS.has(protocol)) {\n return protocol;\n }\n }\n return false;\n },\n host: this.host,\n port,\n maxPayload: this.maxPayloadBytes,\n });\n\n const onListening = () => {\n server.off('error', onError);\n resolve(server);\n };\n const onError = (err: Error) => {\n server.off('listening', onListening);\n reject(err);\n };\n\n server.once('listening', onListening);\n server.once('error', onError);\n });\n\n wss.on('connection', (socket: WebSocket, request) => {\n const requestOrigin = request.headers.origin;\n if (requestOrigin && socket.protocol === RELAY_INTERNAL_PROTOCOL) {\n socket.close(1008, 'Browser connections cannot use the relay protocol');\n return;\n }\n\n const connectionId = randomUUID();\n this.socketByConnectionId.set(connectionId, socket);\n if (requestOrigin) {\n this.requestOriginByConnectionId.set(connectionId, requestOrigin);\n }\n\n socket.on('message', (raw: WebSocket.RawData) => {\n this.onSocketMessage(connectionId, raw);\n });\n\n socket.on('close', (code: number) => {\n this.onSocketClose(connectionId, code);\n });\n\n socket.on('error', (err: Error) => {\n process.stderr.write(\n `[webmcp-local-relay] warn: socket error for connection ${connectionId}: ${err.message}\\n`\n );\n const code =\n 'code' in err && err.code === 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH' ? 1009 : undefined;\n this.onSocketClose(connectionId, code);\n });\n\n try {\n socket.send(JSON.stringify(this.buildServerHello()));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send server hello to connection ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n socket.close(1011, 'Failed to send server hello');\n return;\n }\n\n this.startHeartbeat(connectionId);\n });\n\n wss.on('error', (err: Error) => {\n // Logged, not re-emitted: nothing listens for 'error' on this EventEmitter, and\n // emit('error') without a listener rethrows, killing the relay via uncaughtException.\n process.stderr.write(`[webmcp-local-relay] error: WebSocket server error: ${err.message}\\n`);\n });\n\n this.wss = wss;\n this._mode = 'server';\n\n const address = wss.address();\n if (isTcpAddress(address)) {\n this.desiredPort = address.port;\n }\n\n this.on('stateChanged', this.onStateChangedPushRelay);\n }\n\n private buildServerHello(): ServerHelloMessage {\n return {\n type: 'server-hello',\n service: 'webmcp-local-relay',\n version: 1,\n host: this.host,\n instanceId: this.instanceId,\n label: this.label,\n port: this.desiredPort,\n relayId: this.relayId ?? this.instanceId,\n workspace: this.workspace,\n };\n }\n\n /**\n * `async` is load-bearing: {@link onRelayClientMessage} consumes this through\n * `.then(onResult, onError)`, so a synchronous throw would escape the socket\n * message handler as an uncaught exception and leave the relay client waiting\n * for a `relay/result` that never arrives.\n */\n private async invokeToolLocally(\n toolName: string,\n args: RelayInvokeArgs,\n options: { sourceId?: string; requestTabId?: string; signal?: AbortSignal }\n ): Promise<RelayCallToolResult> {\n const invocation: Parameters<RelayRegistry['resolveInvocation']>[0] = { toolName };\n if (options.sourceId !== undefined) invocation.sourceId = options.sourceId;\n if (options.requestTabId !== undefined) invocation.requestTabId = options.requestTabId;\n const resolved = this.registry.resolveInvocation(invocation);\n\n if (!resolved) {\n throw new Error(`No active browser source provides tool \"${toolName}\"`);\n }\n\n const socket = this.socketByConnectionId.get(resolved.connectionId);\n if (!socket || socket.readyState !== WebSocket.OPEN) {\n throw new Error(\n `Tool source ${resolved.connectionId} disconnected before invocation of \"${toolName}\"`\n );\n }\n\n return this.requestTool(\n socket,\n this.pendingInvocations,\n resolved.connectionId,\n {\n type: 'invoke',\n callId: randomUUID(),\n toolName: resolved.tool.name,\n args,\n },\n `Invocation for tool \"${toolName}\" timed out after ${this.invokeTimeoutMs}ms`,\n options.signal\n );\n }\n\n /** Handles a raw message according to the negotiated WebSocket protocol. */\n private onSocketMessage(connectionId: string, raw: WebSocket.RawData): void {\n const text = this.rawDataToUtf8(raw);\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(text);\n } catch (err) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}...` : text;\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid JSON from connection ${connectionId} (${err instanceof Error ? err.message : 'parse error'}): ${preview}\\n`\n );\n return;\n }\n\n const typeField = RawMessageTypeSchema.safeParse(parsedJson).data?.type;\n const socket = this.socketByConnectionId.get(connectionId);\n if (!socket) {\n return;\n }\n\n if (socket.protocol === RELAY_INTERNAL_PROTOCOL) {\n const relayMsg = RelayClientToServerMessageSchema.safeParse(parsedJson);\n if (relayMsg.success) {\n this.onRelayClientMessage(connectionId, relayMsg.data);\n } else {\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid relay message from ${connectionId}: ${relayMsg.error.message}\\n`\n );\n }\n return;\n }\n\n if (RelayProtocolTypeSchema.safeParse(typeField).success) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} used the relay protocol without negotiating it\\n`\n );\n socket.close(1008, 'Relay protocol was not negotiated');\n return;\n }\n\n const parsedMessage = BrowserToRelayMessageSchema.safeParse(parsedJson);\n if (!parsedMessage.success) {\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid message from connection ${connectionId} (type=${typeField}): ${parsedMessage.error.message}\\n`\n );\n return;\n }\n\n this.registry.touchConnection(connectionId);\n this.onBrowserClientMessage(connectionId, socket, parsedMessage.data);\n }\n\n private onBrowserClientMessage(\n connectionId: string,\n socket: WebSocket,\n message: BrowserToRelayMessage\n ): void {\n if (message.type !== 'hello' && !this.browserClientConnectionIds.has(connectionId)) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} sent ${message.type} before hello, ignoring\\n`\n );\n return;\n }\n\n switch (message.type) {\n case 'hello':\n try {\n const origin = this.requestOriginByConnectionId.get(connectionId) ?? message.origin;\n if (!this.isHostOriginAllowed(origin)) {\n process.stderr.write(\n `[webmcp-local-relay] warn: rejecting source ${connectionId} with disallowed host origin: ${origin ?? 'missing'}\\n`\n );\n this.sendHelloRejected(\n socket,\n {\n type: 'hello/rejected',\n reason: 'host-origin-not-allowed',\n message: 'Host page origin is not allowed by this relay.',\n },\n 1008,\n 'Host origin not allowed'\n );\n break;\n }\n this.registry.upsertSource(connectionId, { ...message, origin });\n this.browserClientConnectionIds.add(connectionId);\n this.sendHelloAccepted(socket, { type: 'hello/accepted' });\n this.emit('stateChanged');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error: failed to process hello from connection ${connectionId}: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`\n );\n }\n break;\n\n case 'tools/list':\n case 'tools/changed':\n try {\n this.registry.registerTools(connectionId, message.tools);\n this.emit('stateChanged');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error: failed to register tools for connection ${connectionId}: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`\n );\n socket.close(1008, 'Invalid tool list');\n }\n break;\n\n case 'result': {\n const pending = this.pendingInvocations.get(message.callId);\n if (!pending) {\n process.stderr.write(\n `[webmcp-local-relay] warn: received result for unknown callId ${message.callId}\\n`\n );\n break;\n }\n if (pending.connectionId !== connectionId) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} returned result for another source's callId ${message.callId}\\n`\n );\n break;\n }\n\n pending.resolve(this.normalizeCallToolResult(message.result));\n break;\n }\n\n case 'pong':\n this.lastPongByConnectionId.set(connectionId, Date.now());\n break;\n }\n }\n\n /**\n * Handles relay-protocol messages from relay client connections.\n */\n private onRelayClientMessage(connectionId: string, message: RelayClientToServerMessage): void {\n if (message.type !== 'relay/hello' && !this.relayClientConnectionIds.has(connectionId)) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} sent ${message.type} before relay/hello, ignoring\\n`\n );\n return;\n }\n\n switch (message.type) {\n case 'relay/hello':\n this.relayClientConnectionIds.add(connectionId);\n break;\n\n case 'relay/list-tools': {\n const response = this.buildRelayToolsPayload('relay/tools');\n const socket = this.socketByConnectionId.get(connectionId);\n if (socket?.readyState === WebSocket.OPEN) {\n try {\n socket.send(JSON.stringify(response));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send relay tools response to ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }\n break;\n }\n\n case 'relay/invoke': {\n const { callId, toolName, args } = message;\n let invocations = this.relayClientInvocations.get(connectionId);\n if (!invocations) {\n invocations = new Map();\n this.relayClientInvocations.set(connectionId, invocations);\n }\n if (invocations.has(callId)) break;\n const controller = new AbortController();\n invocations.set(callId, controller);\n void this.invokeToolLocally(toolName, args ?? {}, { signal: controller.signal })\n .then(\n (result) => {\n if (!controller.signal.aborted) this.sendRelayResult(connectionId, callId, result);\n },\n (error) => {\n if (!controller.signal.aborted) {\n this.sendRelayResult(connectionId, callId, {\n content: [\n {\n type: 'text',\n text: `Relay invocation failed: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n });\n }\n }\n )\n .finally(() => {\n invocations.delete(callId);\n if (invocations.size === 0) this.relayClientInvocations.delete(connectionId);\n });\n break;\n }\n\n case 'relay/cancel':\n this.relayClientInvocations.get(connectionId)?.get(message.callId)?.abort();\n break;\n }\n }\n\n private sendRelayResult(connectionId: string, callId: string, result: RelayCallToolResult): void {\n const socket = this.socketByConnectionId.get(connectionId);\n if (socket?.readyState !== WebSocket.OPEN) {\n process.stderr.write(\n `[webmcp-local-relay] warn: relay client ${connectionId} disconnected before result for callId ${callId} could be delivered\\n`\n );\n return;\n }\n\n try {\n socket.send(JSON.stringify({ type: 'relay/result', callId, result }));\n } catch (error) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send relay result to ${connectionId}: ${error instanceof Error ? error.message : String(error)}\\n`\n );\n }\n }\n\n /**\n * Pushes current tool state to all connected relay clients.\n */\n private pushToolsToRelayClients(): void {\n if (this.relayClientConnectionIds.size === 0) {\n return;\n }\n\n const message = this.buildRelayToolsPayload('relay/tools-changed');\n const payload = JSON.stringify(message);\n\n for (const connectionId of this.relayClientConnectionIds) {\n const socket = this.socketByConnectionId.get(connectionId);\n if (socket?.readyState === WebSocket.OPEN) {\n try {\n socket.send(payload);\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to push tool update to relay client ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }\n }\n }\n\n /**\n * Handles source disconnection and rejects in-flight calls owned by that source.\n *\n * Guarded against double-call: both 'error' and 'close' events fire on\n * socket failure, but cleanup (and the stateChanged emission) runs only once.\n */\n private onSocketClose(connectionId: string, code?: number): void {\n // Guard against double invocation: ws fires both 'error' and 'close' for\n // maxPayload violations. The second call is a no-op.\n if (!this.socketByConnectionId.has(connectionId)) return;\n\n this.stopHeartbeat(connectionId);\n this.requestOriginByConnectionId.delete(connectionId);\n this.browserClientConnectionIds.delete(connectionId);\n this.relayClientConnectionIds.delete(connectionId);\n for (const controller of this.relayClientInvocations.get(connectionId)?.values() ?? []) {\n controller.abort();\n }\n this.relayClientInvocations.delete(connectionId);\n this.registry.removeConnection(connectionId);\n this.socketByConnectionId.delete(connectionId);\n this.emit('stateChanged');\n\n // WS close code 1009 = \"Message Too Big\": the browser sent a response\n // that exceeded maxPayloadBytes. Surface a clear error instead of letting\n // the invocation time out silently after invokeTimeoutMs.\n const isPayloadExceeded = code === 1009;\n\n for (const pending of this.pendingInvocations.values()) {\n if (pending.connectionId !== connectionId) {\n continue;\n }\n\n if (isPayloadExceeded) {\n pending.reject(\n new Error(\n `Tool result exceeded maximum payload size (${formatPayloadLimit(this.maxPayloadBytes)}). Use --max-payload to increase the limit.`\n )\n );\n } else {\n pending.reject(new Error(`Tool source ${connectionId} disconnected during invocation`));\n }\n }\n }\n\n private startHeartbeat(connectionId: string): void {\n this.lastPongByConnectionId.set(connectionId, Date.now());\n\n const intervalId = setInterval(() => {\n const socket = this.socketByConnectionId.get(connectionId);\n if (!socket || socket.readyState !== WebSocket.OPEN) {\n this.stopHeartbeat(connectionId);\n return;\n }\n\n // Skip heartbeat for relay client connections; they have their own reconnect logic.\n if (this.relayClientConnectionIds.has(connectionId)) {\n return;\n }\n\n const lastPong = this.lastPongByConnectionId.get(connectionId) ?? 0;\n if (Date.now() - lastPong > HEARTBEAT_DEAD_THRESHOLD_MS) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} missed heartbeat, closing\\n`\n );\n this.stopHeartbeat(connectionId);\n // The peer is gone, so a close handshake would only hold the source for 30 s.\n socket.terminate();\n return;\n }\n\n try {\n socket.send(JSON.stringify({ type: 'ping' }));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send heartbeat ping to ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n this.heartbeatIntervalByConnectionId.set(connectionId, intervalId);\n }\n\n private stopHeartbeat(connectionId: string): void {\n const intervalId = this.heartbeatIntervalByConnectionId.get(connectionId);\n if (intervalId !== undefined) {\n clearInterval(intervalId);\n this.heartbeatIntervalByConnectionId.delete(connectionId);\n }\n this.lastPongByConnectionId.delete(connectionId);\n }\n\n private async startAsClient(port = this.desiredPort): Promise<void> {\n const previousMode = this._mode;\n const previousPort = this.desiredPort;\n this._mode = 'client';\n this.desiredPort = port;\n\n try {\n const { bufferedMessages, socket } = await this.connectToRelayServer(port);\n this.clientSocket = socket;\n this.clientReconnectDelay = 500;\n this.setupClientHandlers(socket, bufferedMessages);\n } catch (error) {\n this._mode = previousMode;\n this.desiredPort = previousPort;\n throw error;\n }\n }\n\n private async connectToRelayServer(\n port: number\n ): Promise<{ bufferedMessages: RelayServerToClientMessage[]; socket: WebSocket }> {\n return new Promise((resolve, reject) => {\n const wsUrl = `ws://${this.host}:${port}`;\n const ws = new WebSocket(wsUrl, RELAY_INTERNAL_PROTOCOL);\n const bufferedMessages: RelayServerToClientMessage[] = [];\n\n const cleanup = () => {\n clearTimeout(timeoutId);\n ws.off('close', onCloseBeforeReady);\n ws.off('error', onErrorBeforeReady);\n ws.off('message', onMessageBeforeReady);\n ws.off('open', onOpen);\n };\n\n const rejectWith = (error: Error) => {\n cleanup();\n ws.once('error', () => {\n // Ignore late socket errors from ports that failed relay verification.\n });\n ws.terminate();\n reject(error);\n };\n\n const finish = () => {\n cleanup();\n resolve({ bufferedMessages, socket: ws });\n };\n\n const onOpen = () => {\n try {\n this.sendRelayClientHandshake(ws);\n } catch (err) {\n rejectWith(\n new Error(\n `Failed to send handshake to relay server at ${wsUrl}: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n }\n };\n\n const onErrorBeforeReady = (err: Error) => {\n rejectWith(new Error(`Failed to connect to relay server at ${wsUrl}: ${err.message}`));\n };\n\n const onCloseBeforeReady = () => {\n rejectWith(new Error(`Connection to ${wsUrl} closed before relay verification completed`));\n };\n\n const onMessageBeforeReady = (raw: WebSocket.RawData) => {\n const message = this.parseRelayServerMessage(raw);\n if (!message) {\n rejectWith(new Error(`Received a non-relay response while probing ${wsUrl}`));\n return;\n }\n\n bufferedMessages.push(message);\n\n if (message.type === 'server-hello') {\n if (message.service !== 'webmcp-local-relay') {\n rejectWith(new Error(`Unexpected relay service \"${message.service}\" at ${wsUrl}`));\n return;\n }\n finish();\n return;\n }\n\n if (message.type === 'relay/tools' || message.type === 'relay/tools-changed') {\n finish();\n }\n };\n\n const timeoutId = setTimeout(() => {\n rejectWith(new Error(`Timed out waiting for relay hello from ${wsUrl}`));\n }, RELAY_SERVER_MESSAGE_TIMEOUT_MS);\n\n ws.once('open', onOpen);\n ws.once('error', onErrorBeforeReady);\n ws.once('close', onCloseBeforeReady);\n ws.on('message', onMessageBeforeReady);\n });\n }\n\n private setupClientHandlers(\n ws: WebSocket,\n bufferedMessages: RelayServerToClientMessage[] = []\n ): void {\n for (const message of bufferedMessages) {\n this.processRelayServerMessage(message);\n }\n\n ws.on('message', (raw: WebSocket.RawData) => {\n const message = this.parseRelayServerMessage(raw);\n if (!message) {\n return;\n }\n\n this.processRelayServerMessage(message);\n });\n\n ws.on('close', () => {\n this.clientSocket = null;\n for (const pending of this.clientPendingInvocations.values()) {\n pending.reject(new Error('Relay server connection lost during invocation'));\n }\n\n this.clientTools = [];\n this.clientSources = [];\n this.clientToolSourceMap = {};\n this.emit('stateChanged');\n\n if (!this.stopping) {\n this.scheduleReconnect();\n }\n });\n\n ws.on('error', (err: Error) => {\n process.stderr.write(\n `[webmcp-local-relay] warn: relay client socket error: ${err.message}\\n`\n );\n });\n }\n\n private sendRelayClientHandshake(ws: WebSocket): void {\n ws.send(JSON.stringify({ type: 'relay/hello' }));\n ws.send(JSON.stringify({ type: 'relay/list-tools' }));\n }\n\n private parseRelayServerMessage(raw: WebSocket.RawData): RelayServerToClientMessage | null {\n const text = this.rawDataToUtf8(raw);\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}...` : text;\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid JSON from relay server (${err instanceof Error ? err.message : 'parse error'}): ${preview}\\n`\n );\n return null;\n }\n\n const message = RelayServerToClientMessageSchema.safeParse(parsed);\n if (!message.success) {\n const typeField = RawMessageTypeSchema.safeParse(parsed).data?.type ?? 'unknown';\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid relay server message (type=${typeField}): ${message.error.message}\\n`\n );\n return null;\n }\n\n return message.data;\n }\n\n private processRelayServerMessage(message: RelayServerToClientMessage): void {\n switch (message.type) {\n case 'server-hello':\n break;\n\n case 'relay/tools':\n case 'relay/tools-changed':\n this.clientTools = message.tools;\n this.clientSources = message.sources;\n this.clientToolSourceMap = message.toolSourceMap;\n this.emit('stateChanged');\n break;\n\n case 'relay/result': {\n const pending = this.clientPendingInvocations.get(message.callId);\n if (!pending) {\n process.stderr.write(\n `[webmcp-local-relay] warn: received relay result for unknown callId ${message.callId}\\n`\n );\n break;\n }\n pending.resolve(message.result);\n break;\n }\n }\n }\n\n private scheduleReconnect(): void {\n if (this.clientReconnectTimer || this.stopping) {\n return;\n }\n\n const delay = this.clientReconnectDelay;\n this.clientReconnectDelay = Math.min(\n this.clientReconnectDelay * 1.5,\n this.clientMaxReconnectDelay\n );\n\n this.clientReconnectTimer = setTimeout(() => {\n this.clientReconnectTimer = null;\n if (this.stopping) {\n return;\n }\n\n void this.reconnectWithModePromotion();\n }, delay);\n }\n\n /**\n * Attempts to promote from client to server mode when reconnecting.\n * Re-runs the same attach-or-bind strategy used during startup.\n */\n private async reconnectWithModePromotion(): Promise<void> {\n try {\n await this.startUsingPortStrategy();\n if (this.stopping) {\n // stop() ran while this attempt was connecting; undo what it just started.\n await this.stop();\n return;\n }\n this.clientReconnectDelay = 500;\n if (this._mode === 'server') {\n process.stderr.write('[webmcp-local-relay] info: promoted from client to server mode\\n');\n this.emit('stateChanged');\n }\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: reconnection failed: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n if (!this.stopping) {\n this.scheduleReconnect();\n }\n }\n }\n\n private async invokeToolViaRelay(\n toolName: string,\n args: RelayInvokeArgs,\n signal?: AbortSignal\n ): Promise<RelayCallToolResult> {\n if (!this.clientSocket || this.clientSocket.readyState !== WebSocket.OPEN) {\n throw new Error('Not connected to relay server');\n }\n\n return this.requestTool(\n this.clientSocket,\n this.clientPendingInvocations,\n 'relay-server',\n { type: 'relay/invoke', callId: randomUUID(), toolName, args },\n `Proxied invocation for tool \"${toolName}\" timed out after ${this.invokeTimeoutMs}ms`,\n signal\n );\n }\n\n private requestTool(\n socket: WebSocket,\n invocations: Map<string, PendingInvocation>,\n connectionId: string,\n message:\n | Extract<RelayToBrowserMessage, { type: 'invoke' }>\n | Extract<RelayClientToServerMessage, { type: 'relay/invoke' }>,\n timeoutMessage: string,\n signal?: AbortSignal\n ): Promise<RelayCallToolResult> {\n signal?.throwIfAborted();\n const { callId } = message;\n return new Promise<RelayCallToolResult>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n pending.cancel(new Error(timeoutMessage));\n }, this.invokeTimeoutMs);\n const onAbort = () => pending.cancel(signal?.reason);\n const cleanup = () => {\n if (!invocations.delete(callId)) return false;\n clearTimeout(timeoutId);\n signal?.removeEventListener('abort', onAbort);\n return true;\n };\n const pending: PendingInvocation = {\n connectionId,\n resolve: (result) => {\n if (cleanup()) resolve(result);\n },\n reject: (reason) => {\n if (cleanup()) reject(reason);\n },\n cancel: (reason) => {\n if (!cleanup()) return;\n try {\n if (socket.readyState === WebSocket.OPEN) {\n const cancellation: RelayToBrowserMessage | RelayClientToServerMessage = {\n type: message.type === 'invoke' ? 'cancel' : 'relay/cancel',\n callId,\n };\n socket.send(JSON.stringify(cancellation));\n }\n } catch (error) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to cancel call ${callId}: ${error instanceof Error ? error.message : String(error)}\\n`\n );\n }\n reject(reason);\n },\n };\n invocations.set(callId, pending);\n signal?.addEventListener('abort', onAbort, { once: true });\n\n try {\n socket.send(JSON.stringify(message));\n } catch (err) {\n pending.reject(\n new Error(\n `Failed to send invocation: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n }\n });\n }\n\n /**\n * Builds the complete relay response payload including tools and source metadata.\n */\n private buildRelayToolsPayload(\n type: 'relay/tools' | 'relay/tools-changed'\n ): RelayServerToClientMessage {\n const tools = this.registry.listTools();\n const sources = this.registry.listSources();\n const toolSourceMap: Record<string, string[]> = {};\n\n for (const tool of tools) {\n toolSourceMap[tool.name] = tool.sources.map((s) => s.sourceId);\n }\n\n return {\n type,\n tools: tools.map(({ originalName: _originalName, sources: _sources, ...tool }) => tool),\n sources,\n toolSourceMap,\n };\n }\n\n private isHostOriginAllowed(origin: string | undefined): boolean {\n if (this.allowedOrigins.includes('*')) {\n return true;\n }\n\n if (!origin) {\n return false;\n }\n\n return this.allowedOrigins.includes(origin);\n }\n\n private sendHelloAccepted(socket: WebSocket, message: RelayHelloAcceptedMessage): void {\n try {\n socket.send(JSON.stringify(message));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send hello acceptance: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n socket.close(1011, 'Failed to send hello acceptance');\n }\n }\n\n private sendHelloRejected(\n socket: WebSocket,\n message: RelayHelloRejectedMessage,\n closeCode: number,\n closeReason: string\n ): void {\n try {\n socket.send(JSON.stringify(message), () => {\n try {\n socket.close(closeCode, closeReason);\n } catch {\n // Ignore close failures after a rejection send attempt.\n }\n });\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send hello rejection: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n socket.close(closeCode, closeReason);\n }\n }\n\n /**\n * Validates browser tool results against CallToolResultSchema.\n * Non-conforming payloads are wrapped as error results with diagnostic text.\n */\n private normalizeCallToolResult(result: unknown): RelayCallToolResult {\n const parsed = CallToolResultSchema.safeParse(result);\n if (parsed.success) {\n return parsed.data;\n }\n\n const preview = JSON.stringify(result)?.slice(0, 500) ?? 'undefined';\n process.stderr.write(\n `[webmcp-local-relay] warn: tool returned invalid CallToolResult (${parsed.error.message}), wrapping as error: ${preview}\\n`\n );\n\n return {\n content: [\n {\n type: 'text',\n text: `Tool returned an invalid result (expected {content: [...]}): ${preview}`,\n },\n ],\n isError: true,\n };\n }\n\n /**\n * Converts WebSocket raw data variants to a UTF-8 string payload.\n */\n private rawDataToUtf8(raw: WebSocket.RawData): string {\n if (Array.isArray(raw)) return Buffer.concat(raw).toString('utf8');\n if (Buffer.isBuffer(raw)) return raw.toString('utf8');\n return Buffer.from(raw).toString('utf8');\n }\n}\n","import { parseArgs } from 'node:util';\n\nimport { DEFAULT_RELAY_PORT } from './portStrategy.js';\n\n/**\n * Parsed CLI options for relay startup.\n */\nexport interface CliOptions {\n help: boolean;\n host: string;\n port: number;\n portExplicitlySet: boolean;\n allowedOrigins: string[];\n label?: string;\n workspace?: string;\n relayId?: string;\n maxPayloadBytes?: number;\n invokeTimeoutMs?: number;\n}\n\nfunction parsePositiveInteger(name: string, value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid ${name} \"${value}\". Must be a positive integer.`);\n }\n return parsed;\n}\n\n/**\n * Parses supported CLI flags for relay startup.\n */\nexport function parseCliOptions(argv: string[]): CliOptions {\n const { values } = parseArgs({\n args: argv,\n options: {\n help: { type: 'boolean', short: 'h' },\n host: { type: 'string', short: 'H' },\n 'allowed-origin': { type: 'string' },\n 'invoke-timeout': { type: 'string' },\n label: { type: 'string' },\n 'max-payload': { type: 'string' },\n port: { type: 'string', short: 'p' },\n 'relay-id': { type: 'string' },\n 'widget-origin': { type: 'string' },\n workspace: { type: 'string' },\n 'ws-origin': { type: 'string' },\n },\n strict: true,\n });\n\n const port = values.port === undefined ? DEFAULT_RELAY_PORT : Number(values.port);\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new Error(`Invalid port \"${values.port}\". Port must be an integer between 1 and 65535.`);\n }\n\n const invokeTimeoutMs = parsePositiveInteger('invoke-timeout', values['invoke-timeout']);\n const maxPayloadBytes = parsePositiveInteger('max-payload', values['max-payload']);\n\n const allowedOrigins = (\n values['widget-origin'] ??\n values['allowed-origin'] ??\n values['ws-origin']\n )\n ?.split(',')\n .map((origin) => origin.trim())\n .filter(Boolean) ?? ['*'];\n if (allowedOrigins.length === 0) {\n throw new Error('--widget-origin must include at least one origin.');\n }\n\n const options: CliOptions = {\n allowedOrigins,\n help: values.help ?? false,\n host: values.host ?? '127.0.0.1',\n port,\n portExplicitlySet: values.port !== undefined,\n };\n if (values.label) options.label = values.label;\n if (invokeTimeoutMs !== undefined) options.invokeTimeoutMs = invokeTimeoutMs;\n if (maxPayloadBytes !== undefined) options.maxPayloadBytes = maxPayloadBytes;\n if (values['relay-id']) options.relayId = values['relay-id'];\n if (values.workspace) options.workspace = values.workspace;\n return options;\n}\n\n/**\n * Prints CLI usage to stderr.\n */\nexport function printHelp(): void {\n process.stderr.write(\n [\n 'webmcp-local-relay',\n '',\n 'Usage:',\n ' webmcp-local-relay [--host 127.0.0.1] [--port 9333] [--widget-origin https://myapp.example.com]',\n '',\n 'Options:',\n ' --host, -H Bind host for local websocket relay (default: 127.0.0.1)',\n ' --port, -p Preferred root port for the local relay cluster (default: 9333)',\n ' --widget-origin Allowed host page origin(s), comma-separated (default: *)',\n ' --allowed-origin Deprecated alias for --widget-origin',\n ' --ws-origin Deprecated alias for --widget-origin',\n ' --label Human-readable relay label reported during discovery',\n ' --workspace Optional workspace name reported during discovery',\n ' --relay-id Stable relay identifier reported during discovery',\n ' --invoke-timeout Browser tool invocation timeout in milliseconds (default: 65000)',\n ' --max-payload Maximum WebSocket payload size in bytes (default: 10000000)',\n ' --help, -h Show help',\n '',\n ].join('\\n')\n );\n}\n","import { execFile } from 'node:child_process';\n\nimport {\n type CallToolResult,\n fromJsonSchema,\n McpServer,\n type RegisteredTool,\n type Transport,\n} from '@modelcontextprotocol/server';\nimport { StdioServerTransport } from '@modelcontextprotocol/server/stdio';\nimport { z } from 'zod/v4';\n\nimport { RelayBridgeServer, type RelayBridgeServerOptions } from './bridgeServer.js';\nimport type { AggregatedTool, SourceInfo } from './registry.js';\nimport type { RelayInvokeArgs } from './protocol.js';\n\nconst JsonSchemaObjectSchema = z.record(z.string(), z.unknown());\n\n/** Tools the relay registers itself, matching `registerStaticTools`. */\nconst RELAY_TOOL_NAMES = new Set(['webmcp_list_sources', 'webmcp_list_tools', 'webmcp_open_page']);\n\nfunction textResult(text: string): CallToolResult {\n return { content: [{ type: 'text', text }] };\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { content: [{ type: 'text', text }], isError: true };\n}\n\ninterface RelaySourcesResult {\n mode?: 'client';\n count: number;\n sources: SourceInfo[];\n}\n\n/**\n * Base options shared by all {@link LocalRelayMcpServer} configurations.\n */\ntype BrowserLauncher = (\n command: string,\n args: readonly string[],\n callback: (cause: Error | null) => void\n) => void;\n\ninterface LocalRelayMcpServerBaseOptions {\n /** Platform launcher; override to run without opening a browser. */\n launchBrowser?: BrowserLauncher;\n /**\n * MCP server name reported during initialization.\n */\n serverName?: string;\n /**\n * MCP server version reported during initialization.\n */\n serverVersion?: string;\n}\n\n/**\n * Construction options for {@link LocalRelayMcpServer}.\n *\n * Provide either an existing `bridge` instance OR `bridgeOptions` to create\n * one internally — not both.\n */\nexport type LocalRelayMcpServerOptions = LocalRelayMcpServerBaseOptions &\n (\n | { bridge: RelayBridgeServer; bridgeOptions?: never }\n | { bridge?: never; bridgeOptions?: RelayBridgeServerOptions }\n );\n\n/**\n * MCP server facade that exposes browser-relayed tools over MCP transport.\n */\nexport class LocalRelayMcpServer {\n /**\n * Underlying WebSocket bridge used for browser communication.\n */\n readonly bridge: RelayBridgeServer;\n\n private readonly mcpServer: McpServer;\n private readonly launchBrowser: BrowserLauncher;\n private readonly dynamicTools = new Map<string, { handle: RegisteredTool; signature: string }>();\n /** Page tools that share a name with a relay tool, already reported once. */\n private readonly shadowedToolNames = new Set<string>();\n\n private syncDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n private connected = false;\n\n /**\n * Creates a local relay MCP server with static and dynamic tool registration.\n */\n constructor(options: LocalRelayMcpServerOptions = {}) {\n this.bridge = options.bridge ?? new RelayBridgeServer(options.bridgeOptions);\n this.launchBrowser = options.launchBrowser ?? execFile;\n\n this.mcpServer = new McpServer({\n name: options.serverName ?? 'webmcp-local-relay',\n version: options.serverVersion ?? '0.0.0',\n });\n\n this.bridge.on('stateChanged', () => {\n this.debouncedSyncDynamicTools();\n });\n\n this.registerStaticTools();\n }\n\n /**\n * Starts the browser bridge and synchronizes dynamic MCP tools.\n */\n async start(): Promise<void> {\n await this.bridge.start();\n this.syncDynamicTools();\n }\n\n /**\n * Connects the MCP server to a transport.\n *\n * This may be called exactly once per instance lifecycle.\n */\n async connect(transport: Transport): Promise<void> {\n if (this.connected) {\n throw new Error('MCP server transport already connected');\n }\n\n await this.mcpServer.connect(transport);\n this.connected = true;\n\n this.mcpServer.server.onerror = (error) => {\n process.stderr.write(\n `[webmcp-local-relay] error: MCP protocol error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\n`\n );\n };\n }\n\n /**\n * Convenience helper that connects the server over stdio transport.\n */\n async startStdio(): Promise<void> {\n await this.connect(new StdioServerTransport());\n }\n\n /**\n * Stops MCP transport and bridge resources.\n */\n async stop(): Promise<void> {\n this.connected = false;\n\n if (this.syncDebounceTimer) {\n clearTimeout(this.syncDebounceTimer);\n this.syncDebounceTimer = null;\n }\n\n let mcpCloseError: unknown;\n try {\n await this.mcpServer.close();\n } catch (err) {\n mcpCloseError = err;\n process.stderr.write(\n `[webmcp-local-relay] error: failed to close MCP server: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`\n );\n }\n await this.bridge.stop();\n if (mcpCloseError) {\n process.stderr.write(\n '[webmcp-local-relay] warn: shutdown completed with errors (MCP server close failed)\\n'\n );\n }\n }\n\n /**\n * Returns dynamic tool names currently registered in MCP.\n */\n listDynamicToolNames(): string[] {\n return Array.from(this.dynamicTools.keys()).sort();\n }\n\n /**\n * Registers built-in management tools exposed by the relay.\n */\n private registerStaticTools(): void {\n this.mcpServer.registerTool(\n 'webmcp_list_sources',\n {\n description: 'List connected browser tool sources and their metadata.',\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n },\n },\n async () => {\n const clientMode = this.bridge.mode === 'client';\n const sources = clientMode\n ? this.bridge.listSourcesFromRelay()\n : this.bridge.registry.listSources();\n const info: RelaySourcesResult = {\n count: sources.length,\n sources,\n };\n if (clientMode) info.mode = 'client';\n return {\n content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],\n structuredContent: info,\n };\n }\n );\n\n this.mcpServer.registerTool(\n 'webmcp_list_tools',\n {\n description:\n 'List WebMCP tools available from connected browser sources. Returns tool definitions including name, description, input schema, and source info.',\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n },\n },\n async () => {\n const tools = this.listAggregatedTools();\n const info = { count: tools.length, tools };\n return {\n content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],\n structuredContent: info,\n };\n }\n );\n\n this.mcpServer.registerTool(\n 'webmcp_open_page',\n {\n description:\n \"Open a URL in the user's default browser, or refresh a connected source page. Use to launch WebMCP-enabled pages or reload stale connections.\",\n inputSchema: z.object({\n url: z.string().describe('URL to open or match for refresh.'),\n refresh: z\n .boolean()\n .optional()\n .describe(\n 'If true, refresh the connected source matching this URL instead of opening a new tab.'\n ),\n }),\n annotations: { readOnlyHint: false },\n },\n async ({ url, refresh }) => {\n const parsed = URL.parse(url);\n if (!parsed) {\n return errorResult(`Invalid URL: ${url}`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return errorResult(`Only http: and https: URLs are allowed. Got: ${parsed.protocol}`);\n }\n\n const existing =\n this.bridge.mode === 'server'\n ? this.bridge.registry\n .listSources()\n .find((source) => source.url && URL.parse(source.url)?.origin === parsed.origin)\n : undefined;\n\n if (refresh) {\n if (this.bridge.mode === 'client') {\n return errorResult(\n 'Refresh is not supported in client mode. Only the server relay can reload sources.'\n );\n }\n if (!existing) {\n return errorResult(\n `No connected source matches origin ${parsed.origin}. The page may not be open or connected.`\n );\n }\n try {\n this.bridge.reloadSource(existing.sourceId);\n } catch (err) {\n return errorResult(\n `Failed to reload source: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n return textResult(\n `Reload sent to source ${existing.sourceId} (${existing.url ?? existing.origin}).`\n );\n }\n\n try {\n await this.openInBrowser(url);\n } catch (err) {\n return errorResult(\n `Failed to open browser: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n return textResult(\n existing\n ? `Opened ${url} in the default browser. Note: a source from ${existing.url ?? existing.origin} is already connected.`\n : `Opened ${url} in the default browser.`\n );\n }\n );\n }\n\n /**\n * Opens a URL in the user's default browser using the platform open command.\n *\n * Passes the re-serialized `URL.href` directly to the platform launcher.\n * No shell or command-language string is involved.\n */\n private openInBrowser(url: string): Promise<void> {\n const safeUrl = new URL(url).href;\n const command =\n process.platform === 'darwin'\n ? 'open'\n : process.platform === 'win32'\n ? 'explorer.exe'\n : 'xdg-open';\n return new Promise((resolve, reject) => {\n this.launchBrowser(command, [safeUrl], (err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n }\n\n /**\n * Debounces rapid stateChanged events into a single sync pass.\n *\n * Browser reconnections emit multiple stateChanged events in quick\n * succession (hello, tools/list). Without debouncing, each triggers a\n * full sync that removes and re-registers tools — sending per-operation\n * notifications through the stdio transport and overwhelming the agent.\n */\n private debouncedSyncDynamicTools(): void {\n if (this.syncDebounceTimer) {\n return;\n }\n\n this.syncDebounceTimer = setTimeout(() => {\n this.syncDebounceTimer = null;\n try {\n this.syncDynamicTools();\n } catch (err) {\n const details = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(\n `[webmcp-local-relay] error: failed to sync dynamic tools: ${details}\\n`\n );\n }\n }, 16);\n }\n\n /**\n * Returns the current aggregated tool list, dispatching based on bridge mode.\n */\n private listAggregatedTools(): AggregatedTool[] {\n return this.bridge.mode === 'client'\n ? this.buildAggregatedToolsFromRelay()\n : this.bridge.registry.listTools();\n }\n\n /**\n * Converts relay client tool descriptors to aggregated tool shape.\n * Populates per-tool source metadata from the relay's tool-source mapping.\n */\n private buildAggregatedToolsFromRelay(): AggregatedTool[] {\n const toolSourceMap = this.bridge.getToolSourceMapFromRelay();\n const allSources = this.bridge.listSourcesFromRelay();\n const sourceById = new Map(allSources.map((s) => [s.sourceId, s]));\n\n return this.bridge\n .listToolsFromRelay()\n .map((tool) => {\n const sourceIds = toolSourceMap[tool.name] ?? [];\n const sources: SourceInfo[] = [];\n for (const id of sourceIds) {\n const source = sourceById.get(id);\n if (source) {\n sources.push(source);\n } else {\n process.stderr.write(\n `[webmcp-local-relay] warn: tool \"${tool.name}\" references unknown sourceId \"${id}\"\\n`\n );\n }\n }\n\n return {\n ...tool,\n originalName: tool.name,\n sources,\n };\n })\n .sort((left, right) => left.name.localeCompare(right.name));\n }\n\n /**\n * Applies current aggregated tool state to MCP dynamic registrations.\n */\n private syncDynamicTools(): void {\n const tools = this.listAggregatedTools();\n const nextNames = new Set(tools.map((tool) => tool.name));\n\n for (const [name, registration] of this.dynamicTools) {\n if (nextNames.has(name)) {\n continue;\n }\n\n registration.handle.remove();\n this.dynamicTools.delete(name);\n }\n\n for (const tool of tools) {\n if (RELAY_TOOL_NAMES.has(tool.name)) {\n if (!this.shadowedToolNames.has(tool.name)) {\n this.shadowedToolNames.add(tool.name);\n process.stderr.write(\n `[webmcp-local-relay] warn: skipped page tool \"${tool.name}\" because a built-in relay tool has that name\\n`\n );\n }\n continue;\n }\n\n const signature = this.toolSignature(tool);\n const current = this.dynamicTools.get(tool.name);\n\n if (current?.signature === signature) {\n continue;\n }\n\n if (current) {\n current.handle.remove();\n this.dynamicTools.delete(tool.name);\n }\n\n try {\n const handle = this.registerDynamicTool(tool);\n this.dynamicTools.set(tool.name, { handle, signature });\n } catch (err) {\n const details = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(\n `[webmcp-local-relay] warn: skipped dynamic tool \"${tool.name}\" because its schema could not be compiled: ${details}\\n`\n );\n }\n }\n }\n\n /**\n * Registers a single dynamic tool and returns a removal handle.\n */\n private registerDynamicTool(tool: AggregatedTool): RegisteredTool {\n // Validate the container before handing JSON Schema validation to the SDK compiler.\n const inputSchema = fromJsonSchema<RelayInvokeArgs>(\n JsonSchemaObjectSchema.parse(tool.inputSchema)\n );\n const outputSchema =\n tool.outputSchema !== undefined\n ? fromJsonSchema(JsonSchemaObjectSchema.parse(tool.outputSchema))\n : undefined;\n\n const registration: {\n [K in 'title' | 'annotations' | 'icons' | '_meta']?: Exclude<AggregatedTool[K], undefined>;\n } & {\n description: string;\n inputSchema: typeof inputSchema;\n outputSchema?: NonNullable<typeof outputSchema>;\n } = { description: this.dynamicToolDescription(tool), inputSchema };\n if (tool.title !== undefined) registration.title = tool.title;\n if (outputSchema) registration.outputSchema = outputSchema;\n if (tool.annotations) registration.annotations = tool.annotations;\n if (tool.icons) registration.icons = tool.icons;\n if (tool._meta) registration._meta = tool._meta;\n return this.mcpServer.registerTool(\n tool.name,\n registration,\n async (args: RelayInvokeArgs, context) => {\n try {\n return await this.bridge.invokeTool(tool.name, args, { signal: context.mcpReq.signal });\n } catch (err) {\n if (context.mcpReq.signal.aborted) throw err;\n const message = err instanceof Error ? err.message : String(err);\n const details = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(\n `[webmcp-local-relay] error: dynamic tool \"${tool.name}\" invocation failed: ${details}\\n`\n );\n return errorResult(`Failed to invoke relayed tool \"${tool.name}\": ${message}`);\n }\n }\n );\n }\n\n /**\n * Builds a display description for relayed tools including source context.\n * In client mode, source metadata is unavailable, so tools are labeled `[WebMCP relay]`.\n */\n private dynamicToolDescription(tool: AggregatedTool): string {\n const source = tool.sources[0];\n const sourceLabel = source\n ? `[WebMCP ${source.tabId}${source.title ? ` • ${source.title}` : ''}]`\n : '[WebMCP relay]';\n\n return `${sourceLabel} ${tool.description ?? `Relayed tool ${tool.originalName}`}`;\n }\n\n /**\n * Produces a stable signature for change detection of dynamic tool metadata.\n *\n * Deliberately excludes `sourceId` (which is a per-connection UUID that\n * changes on every browser reconnect) so that a reconnecting tab with the\n * same tools does not trigger a needless remove+re-register cycle.\n */\n private toolSignature(tool: AggregatedTool): string {\n return JSON.stringify({\n originalName: tool.originalName,\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n annotations: tool.annotations,\n _meta: tool._meta,\n icons: tool.icons,\n source: tool.sources[0]\n ? { tabId: tool.sources[0].tabId, title: tool.sources[0].title }\n : undefined,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcpRelayServer-BYF9j2fv.mjs","names":[],"sources":["../src/portStrategy.ts","../src/protocol.ts","../src/naming.ts","../src/registry.ts","../src/schemas.ts","../src/bridgeServer.ts","../src/cli-utils.ts","../src/mcpRelayServer.ts"],"sourcesContent":["import type { AddressInfo } from 'node:net';\n\nexport function isTcpAddress(address: AddressInfo | string | null): address is AddressInfo {\n return address !== null && typeof address !== 'string';\n}\n\nexport const DEFAULT_RELAY_PORT = 9333;\nexport const DEFAULT_RELAY_PORT_RANGE_END = 9348;\n","import type { CallToolResult, Tool, ToolAnnotations } from '@modelcontextprotocol/server';\nimport {\n CallToolRequestParamsSchema,\n CallToolResultSchema,\n ToolAnnotationsSchema,\n ToolSchema,\n} from '@modelcontextprotocol/core';\nimport { z } from 'zod/v4';\n\n/**\n * SDK-derived canonical tool schema used internally by the relay.\n */\nexport const NormalizedToolSchema = ToolSchema.extend({\n name: ToolSchema.shape.name.min(1),\n});\n\n/**\n * SDK-derived argument schema for tool invocation payloads.\n */\nexport const RelayInvokeArgsSchema = CallToolRequestParamsSchema.shape.arguments;\n\n/**\n * Default input schema applied when inbound payload omits or provides\n * a non-object input schema.\n */\nexport const DEFAULT_TOOL_INPUT_SCHEMA: Tool['inputSchema'] = {\n type: 'object',\n properties: {},\n};\n\n/** SDK tool schema with a browser-compatible default input schema. */\nexport const InboundToolSchema = NormalizedToolSchema.extend({\n // WebMCP accepts any JSON object as an input schema; MCP requires `type: 'object'`.\n inputSchema: z\n .looseObject({})\n .transform((schema) => ({ type: 'object', ...schema }))\n .pipe(NormalizedToolSchema.shape.inputSchema)\n .optional(),\n});\n\n/**\n * Canonical normalized relay tool shape.\n */\nexport type RelayTool = z.infer<typeof NormalizedToolSchema>;\n\n/**\n * Canonical relay tool annotations shape.\n */\nexport type RelayToolAnnotations = ToolAnnotations;\n\n/**\n * Canonical relay call result shape.\n */\nexport type RelayCallToolResult = CallToolResult;\n\n/**\n * Invocation argument object shape derived from MCP SDK request params.\n */\nexport type RelayInvokeArgs = Exclude<z.infer<typeof RelayInvokeArgsSchema>, undefined>;\n\n/**\n * Applies the browser default input schema and validates the complete SDK tool.\n */\nexport function normalizeInboundTool(inbound: z.infer<typeof InboundToolSchema>): RelayTool {\n return NormalizedToolSchema.parse({\n ...inbound,\n inputSchema: inbound.inputSchema ?? DEFAULT_TOOL_INPUT_SCHEMA,\n });\n}\n\nexport { CallToolRequestParamsSchema, CallToolResultSchema, ToolAnnotationsSchema, ToolSchema };\n","/**\n * Maximum tool name length supported across MCP clients.\n */\nexport const MAX_MCP_TOOL_NAME_LENGTH = 128;\n\n/**\n * Number of tab-id characters appended when disambiguation is required.\n */\nconst TAB_ID_DISAMBIGUATION_LENGTH = 4;\n\n/**\n * Converts arbitrary text into MCP-safe identifier characters.\n */\nexport function sanitizeName(value: string): string {\n return value.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\n/**\n * Extracts and sanitizes a domain label from an origin or URL.\n */\nexport function extractSanitizedDomain(originOrUrl?: string): string {\n if (!originOrUrl) {\n return 'unknown';\n }\n\n try {\n const parsed = new URL(originOrUrl);\n if (!parsed.hostname) {\n return 'unknown';\n }\n\n const isLocalhost =\n parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1' ||\n parsed.hostname === '[::1]';\n\n const domain = isLocalhost ? `localhost_${parsed.port || '80'}` : parsed.hostname;\n return sanitizeName(domain);\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Builds a public tool name for MCP registration.\n *\n * Names can include a short tab-id suffix when multiple tabs publish the same\n * original tool name.\n */\nexport function buildPublicToolName(options: {\n originalToolName: string;\n tabId?: string;\n disambiguate?: boolean;\n}): string {\n const safeName = sanitizeName(options.originalToolName);\n\n if (!options.disambiguate || !options.tabId) {\n return safeName.slice(0, MAX_MCP_TOOL_NAME_LENGTH);\n }\n\n const shortTab = sanitizeName(options.tabId).slice(0, TAB_ID_DISAMBIGUATION_LENGTH);\n const suffix = `_${shortTab}`;\n const available = MAX_MCP_TOOL_NAME_LENGTH - suffix.length;\n return `${safeName.slice(0, available)}${suffix}`;\n}\n","import { buildPublicToolName, MAX_MCP_TOOL_NAME_LENGTH } from './naming.js';\nimport type { RelayTool } from './protocol.js';\nimport type { BrowserHelloMessage } from './schemas.js';\n\n/**\n * Internal metadata tracked for each active browser source.\n */\ninterface SourceMetadata {\n sourceId: string;\n tabId: string;\n // Optional keys, matching the wire shape in RelaySourceInfoSchema: a browser\n // source may omit these entirely rather than send them as undefined.\n origin?: string | undefined;\n url?: string | undefined;\n title?: string | undefined;\n iconUrl?: string | undefined;\n connectedAt: number;\n lastSeenAt: number;\n}\n\n/**\n * Public source metadata exposed by registry APIs.\n */\nexport interface SourceInfo extends SourceMetadata {\n toolCount: number;\n}\n\n/**\n * Aggregated relayed tool metadata across one or more sources.\n *\n * Extends {@link RelayTool} with relay-specific fields so new SDK tool\n * properties are automatically inherited without manual duplication.\n */\nexport interface AggregatedTool extends RelayTool {\n originalName: string;\n sources: SourceInfo[];\n}\n\n/**\n * Internal record linking a source to a concrete tool implementation.\n */\ninterface ToolProvider {\n sourceId: string;\n tabId: string;\n tool: RelayTool;\n publicToolName: string;\n}\n\n/**\n * Invocation target selected by the registry.\n */\nexport interface ResolvedInvocation {\n connectionId: string;\n tool: RelayTool;\n publicToolName: string;\n}\n\n/**\n * Error thrown when a source attempts to register tools before `hello`.\n */\nexport class HelloRequiredError extends Error {\n readonly connectionId: string;\n\n constructor(connectionId: string) {\n super(`Connection ${connectionId} must send hello before tools`);\n this.name = 'HelloRequiredError';\n this.connectionId = connectionId;\n }\n}\n\n/**\n * In-memory source and tool registry used by the relay bridge.\n */\nexport class RelayRegistry {\n private readonly sourceByConnectionId = new Map<string, SourceMetadata>();\n private readonly toolsByConnectionId = new Map<string, ToolProvider[]>();\n private readonly providersByPublicToolName = new Map<string, ToolProvider[]>();\n\n /**\n * @param now Time provider used for recency ordering.\n */\n constructor(private readonly now: () => number = () => Date.now()) {}\n\n /**\n * Upserts source metadata from a browser `hello` message.\n *\n * `sourceId` currently matches `connectionId`, but remains conceptually distinct\n * so stable source identity can be introduced in the future.\n */\n upsertSource(connectionId: string, hello: BrowserHelloMessage): void {\n const now = this.now();\n const existing = this.sourceByConnectionId.get(connectionId);\n\n this.sourceByConnectionId.set(connectionId, {\n sourceId: connectionId,\n tabId: hello.tabId,\n origin: hello.origin ?? existing?.origin,\n url: hello.url ?? existing?.url,\n title: hello.title ?? existing?.title,\n iconUrl: hello.iconUrl ?? existing?.iconUrl,\n connectedAt: existing?.connectedAt ?? now,\n lastSeenAt: now,\n });\n\n if (existing && existing.tabId !== hello.tabId) {\n for (const provider of this.toolsByConnectionId.get(connectionId) ?? []) {\n provider.tabId = hello.tabId;\n }\n this.rebuildPublicNames();\n }\n }\n\n /**\n * Replaces the full tool set for a source connection.\n */\n registerTools(connectionId: string, tools: RelayTool[]): void {\n const source = this.sourceByConnectionId.get(connectionId);\n if (!source) {\n throw new HelloRequiredError(connectionId);\n }\n\n this.touchConnection(connectionId);\n\n const providers: ToolProvider[] = tools.map((tool) => ({\n sourceId: connectionId,\n tabId: source.tabId,\n tool,\n publicToolName: '',\n }));\n\n this.toolsByConnectionId.set(connectionId, providers);\n this.rebuildPublicNames();\n }\n\n /**\n * Removes a source and all of its tool registrations.\n */\n removeConnection(connectionId: string): void {\n this.toolsByConnectionId.delete(connectionId);\n this.sourceByConnectionId.delete(connectionId);\n this.rebuildPublicNames();\n }\n\n /**\n * Marks a source as recently seen without mutating identity metadata.\n */\n touchConnection(connectionId: string): void {\n const source = this.sourceByConnectionId.get(connectionId);\n if (!source) {\n return;\n }\n\n source.lastSeenAt = this.now();\n }\n\n /**\n * Lists active sources that currently publish at least one tool.\n */\n listSources(): SourceInfo[] {\n return Array.from(this.sourceByConnectionId.values())\n .map((source) => this.toSourceInfo(source))\n .filter((source) => source.toolCount > 0)\n .sort((a, b) => this.compareRecency(b.lastSeenAt, a.lastSeenAt, b.sourceId, a.sourceId));\n }\n\n /**\n * Lists aggregated tools ordered by public tool name.\n */\n listTools(): AggregatedTool[] {\n const result: AggregatedTool[] = [];\n\n for (const [publicToolName, providers] of this.providersByPublicToolName.entries()) {\n const rankedProviders = this.sortProvidersByRecency(providers);\n const primaryProvider = rankedProviders[0];\n if (!primaryProvider) {\n continue;\n }\n\n const sources = rankedProviders\n .map((provider) => {\n const source = this.sourceByConnectionId.get(provider.sourceId);\n return source ? this.toSourceInfo(source) : undefined;\n })\n .filter((source): source is SourceInfo => Boolean(source));\n\n result.push({\n ...primaryProvider.tool,\n name: publicToolName,\n originalName: primaryProvider.tool.name,\n sources,\n });\n }\n\n return result.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n /**\n * Resolves a tool invocation to a concrete source provider.\n *\n * Resolution order:\n * 1. `sourceId`: exact source id, then fallback to tab id.\n * 2. `requestTabId`: strict tab id match with no fallback.\n * 3. Default: most recently seen provider.\n */\n resolveInvocation(options: {\n toolName: string;\n sourceId?: string;\n requestTabId?: string;\n }): ResolvedInvocation | null {\n const providers = this.providersByPublicToolName.get(options.toolName);\n if (!providers || providers.length === 0) {\n return null;\n }\n\n const rankedProviders = this.sortProvidersByRecency(providers);\n\n let provider: ToolProvider | undefined;\n if (options.sourceId) {\n provider =\n rankedProviders.find((candidate) => candidate.sourceId === options.sourceId) ??\n rankedProviders.find((candidate) => candidate.tabId === options.sourceId);\n } else if (options.requestTabId) {\n provider = rankedProviders.find((candidate) => candidate.tabId === options.requestTabId);\n } else {\n provider = rankedProviders[0];\n }\n\n if (!provider) {\n return null;\n }\n\n return {\n connectionId: provider.sourceId,\n tool: provider.tool,\n publicToolName: provider.publicToolName,\n };\n }\n\n /**\n * Converts internal source metadata to public source info.\n */\n private toSourceInfo(source: SourceMetadata): SourceInfo {\n return {\n ...source,\n toolCount: this.toolsByConnectionId.get(source.sourceId)?.length ?? 0,\n };\n }\n\n /**\n * Rebuilds public tool names after source set changes.\n *\n * When the same original tool name appears in multiple tabs, names are\n * disambiguated with a short tab suffix.\n */\n private rebuildPublicNames(): void {\n const byOriginalName = new Map<string, ToolProvider[]>();\n for (const providers of this.toolsByConnectionId.values()) {\n for (const provider of providers) {\n const key = provider.tool.name;\n const group = byOriginalName.get(key) ?? [];\n group.push(provider);\n byOriginalName.set(key, group);\n }\n }\n\n this.providersByPublicToolName.clear();\n const allocations: Array<{ candidate: string; key: string; provider: ToolProvider }> = [];\n\n for (const [originalName, providers] of byOriginalName) {\n const uniqueTabIds = new Set(providers.map((p) => p.tabId));\n const disambiguate = uniqueTabIds.size > 1;\n\n for (const provider of providers) {\n allocations.push({\n candidate: buildPublicToolName({\n originalToolName: originalName,\n tabId: provider.tabId,\n disambiguate,\n }),\n key: disambiguate ? `${originalName}\\0${provider.tabId}` : originalName,\n provider,\n });\n }\n }\n\n const nameByKey = new Map<string, string>();\n const usedNames = new Set<string>();\n for (const { candidate, key, provider } of allocations.sort((a, b) =>\n a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n )) {\n let publicToolName = nameByKey.get(key);\n if (!publicToolName) {\n publicToolName = candidate;\n for (let index = 2; usedNames.has(publicToolName); index += 1) {\n const suffix = `_${String(index)}`;\n publicToolName = `${candidate.slice(0, MAX_MCP_TOOL_NAME_LENGTH - suffix.length)}${suffix}`;\n }\n nameByKey.set(key, publicToolName);\n usedNames.add(publicToolName);\n }\n\n provider.publicToolName = publicToolName;\n\n const existing = this.providersByPublicToolName.get(publicToolName) ?? [];\n existing.push(provider);\n this.providersByPublicToolName.set(publicToolName, existing);\n }\n }\n\n /**\n * Returns providers sorted by source recency.\n */\n private sortProvidersByRecency(providers: ToolProvider[]): ToolProvider[] {\n return providers.slice().sort((a, b) => {\n const aSource = this.sourceByConnectionId.get(a.sourceId);\n const bSource = this.sourceByConnectionId.get(b.sourceId);\n const aLastSeen = aSource?.lastSeenAt ?? 0;\n const bLastSeen = bSource?.lastSeenAt ?? 0;\n\n return this.compareRecency(bLastSeen, aLastSeen, b.sourceId, a.sourceId);\n });\n }\n\n /**\n * Compares two source recency tuples and stabilizes order by source id.\n */\n private compareRecency(\n leftLastSeen: number,\n rightLastSeen: number,\n leftId: string,\n rightId: string\n ): number {\n const timeDiff = leftLastSeen - rightLastSeen;\n if (timeDiff !== 0) {\n return timeDiff;\n }\n return leftId.localeCompare(rightId);\n }\n}\n","import { z } from 'zod/v4';\nimport {\n CallToolResultSchema,\n InboundToolSchema,\n NormalizedToolSchema,\n normalizeInboundTool,\n RelayInvokeArgsSchema,\n type RelayTool,\n} from './protocol.js';\n\n/** Parses each tool on its own, so one malformed tool does not hide the page's other tools. */\nfunction parseBrowserTools(tools: unknown[]): RelayTool[] {\n return tools.flatMap((raw) => {\n const parsed = InboundToolSchema.safeParse(raw);\n if (!parsed.success) {\n process.stderr.write(\n `[webmcp-local-relay] warn: skipped a malformed page tool: ${parsed.error.message}\\n`\n );\n return [];\n }\n const tool = normalizeInboundTool(parsed.data);\n return tool.execution?.taskSupport === 'required' ? [] : [tool];\n });\n}\n\nconst BrowserHelloMessageSchema = z.object({\n type: z.literal('hello'),\n tabId: z.string().min(1),\n origin: z.string().optional(),\n url: z.string().optional(),\n title: z.string().optional(),\n iconUrl: z.string().optional(),\n});\n\nconst BrowserToolsListMessageSchema = z.object({\n type: z.literal('tools/list'),\n tools: z.array(z.unknown()).transform(parseBrowserTools),\n});\n\nconst BrowserToolsChangedMessageSchema = z.object({\n type: z.literal('tools/changed'),\n tools: z.array(z.unknown()).transform(parseBrowserTools),\n});\n\nconst BrowserToolResultMessageSchema = z.object({\n type: z.literal('result'),\n callId: z.string().min(1),\n result: z.unknown(),\n});\n\nconst BrowserPongMessageSchema = z.object({\n type: z.literal('pong'),\n});\n\n/**\n * Union schema for all browser-to-relay protocol messages.\n */\nexport const BrowserToRelayMessageSchema = z.discriminatedUnion('type', [\n BrowserHelloMessageSchema,\n BrowserToolsListMessageSchema,\n BrowserToolsChangedMessageSchema,\n BrowserToolResultMessageSchema,\n BrowserPongMessageSchema,\n]);\n\n/**\n * Browser-to-relay message payload.\n */\nexport type BrowserToRelayMessage = z.infer<typeof BrowserToRelayMessageSchema>;\n\n/**\n * Browser source bootstrap message.\n */\nexport type BrowserHelloMessage = z.infer<typeof BrowserHelloMessageSchema>;\n\n/**\n * Shared relay identity used for discovery and attach verification.\n */\nexport const RelayDescriptorSchema = z.object({\n host: z.string().min(1),\n instanceId: z.string().min(1),\n label: z.string().min(1).optional(),\n port: z.number().int().min(1).max(65535),\n relayId: z.string().min(1).optional(),\n workspace: z.string().min(1).optional(),\n});\n\n/**\n * Relay identity used for discovery and attach verification.\n */\nexport type RelayDescriptor = z.infer<typeof RelayDescriptorSchema>;\n\n/**\n * Schema for server hello messages sent immediately after WebSocket connect.\n */\nexport const ServerHelloMessageSchema = RelayDescriptorSchema.extend({\n type: z.literal('server-hello'),\n service: z.literal('webmcp-local-relay'),\n version: z.literal(1),\n});\n\n/**\n * Server hello payload.\n */\nexport type ServerHelloMessage = z.infer<typeof ServerHelloMessageSchema>;\n\n/**\n * Schema for acceptance of a browser source hello message.\n */\nexport const RelayHelloAcceptedMessageSchema = z.object({\n type: z.literal('hello/accepted'),\n});\n\n/**\n * Acceptance payload for a browser source hello message.\n */\nexport type RelayHelloAcceptedMessage = z.infer<typeof RelayHelloAcceptedMessageSchema>;\n\n/**\n * Schema for rejection of a browser source hello message.\n */\nexport const RelayHelloRejectedMessageSchema = z.object({\n type: z.literal('hello/rejected'),\n reason: z.string().min(1),\n message: z.string().min(1),\n});\n\n/**\n * Rejection payload for a browser source hello message.\n */\nexport type RelayHelloRejectedMessage = z.infer<typeof RelayHelloRejectedMessageSchema>;\n\nconst RelayInvokeMessageSchema = z.object({\n type: z.literal('invoke'),\n callId: z.string().min(1),\n toolName: z.string().min(1),\n args: RelayInvokeArgsSchema,\n});\n\nconst RelayCancelMessageSchema = z.object({\n type: z.literal('cancel'),\n callId: z.string().min(1),\n});\n\nconst RelayPingMessageSchema = z.object({\n type: z.literal('ping'),\n});\n\nconst RelayReloadMessageSchema = z.object({\n type: z.literal('reload'),\n});\n\n/**\n * Union schema for all relay-to-browser protocol messages.\n */\nexport const RelayToBrowserMessageSchema = z.discriminatedUnion('type', [\n ServerHelloMessageSchema,\n RelayHelloAcceptedMessageSchema,\n RelayHelloRejectedMessageSchema,\n RelayInvokeMessageSchema,\n RelayCancelMessageSchema,\n RelayPingMessageSchema,\n RelayReloadMessageSchema,\n]);\n\n/**\n * Relay-to-browser message payload.\n */\nexport type RelayToBrowserMessage = z.infer<typeof RelayToBrowserMessageSchema>;\n\n/**\n * Schema for source metadata transmitted in relay-to-relay messages.\n */\nexport const RelaySourceInfoSchema = z.object({\n sourceId: z.string(),\n tabId: z.string(),\n origin: z.string().optional(),\n url: z.string().optional(),\n title: z.string().optional(),\n iconUrl: z.string().optional(),\n connectedAt: z.number(),\n lastSeenAt: z.number(),\n toolCount: z.number(),\n});\n\n/**\n * Source metadata transmitted in relay-to-relay messages.\n */\nexport type RelaySourceInfo = z.infer<typeof RelaySourceInfoSchema>;\n\nconst RelayClientHelloSchema = z.object({\n type: z.literal('relay/hello'),\n});\n\nconst RelayClientListToolsSchema = z.object({\n type: z.literal('relay/list-tools'),\n});\n\nconst RelayClientInvokeSchema = z.object({\n type: z.literal('relay/invoke'),\n callId: z.string().min(1),\n toolName: z.string().min(1),\n args: RelayInvokeArgsSchema,\n});\n\nconst RelayClientCancelSchema = z.object({\n type: z.literal('relay/cancel'),\n callId: z.string().min(1),\n});\n\n/**\n * Union schema for all relay-client-to-server messages.\n */\nexport const RelayClientToServerMessageSchema = z.discriminatedUnion('type', [\n RelayClientHelloSchema,\n RelayClientListToolsSchema,\n RelayClientInvokeSchema,\n RelayClientCancelSchema,\n]);\n\n/**\n * Relay client to server message payload.\n */\nexport type RelayClientToServerMessage = z.infer<typeof RelayClientToServerMessageSchema>;\n\nconst RelayToolsPayloadFields = {\n tools: z.array(NormalizedToolSchema),\n sources: z.array(RelaySourceInfoSchema),\n toolSourceMap: z.record(z.string(), z.array(z.string())),\n};\n\nconst RelayServerToolsSchema = z.object({\n type: z.literal('relay/tools'),\n ...RelayToolsPayloadFields,\n});\n\nconst RelayServerResultSchema = z.object({\n type: z.literal('relay/result'),\n callId: z.string().min(1),\n result: CallToolResultSchema,\n});\n\nconst RelayServerToolsChangedSchema = z.object({\n type: z.literal('relay/tools-changed'),\n ...RelayToolsPayloadFields,\n});\n\n/**\n * Union schema for all relay-server-to-client messages.\n */\nexport const RelayServerToClientMessageSchema = z.discriminatedUnion('type', [\n ServerHelloMessageSchema,\n RelayServerToolsSchema,\n RelayServerResultSchema,\n RelayServerToolsChangedSchema,\n]);\n\n/**\n * Relay server to client message payload.\n */\nexport type RelayServerToClientMessage = z.infer<typeof RelayServerToClientMessageSchema>;\n","import { z } from 'zod/v4';\nimport { randomUUID } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\n\nimport WebSocket, { WebSocketServer } from 'ws';\nimport { DEFAULT_RELAY_PORT, DEFAULT_RELAY_PORT_RANGE_END, isTcpAddress } from './portStrategy.js';\nimport {\n CallToolResultSchema,\n type RelayCallToolResult,\n type RelayInvokeArgs,\n type RelayTool,\n} from './protocol.js';\nimport { RelayRegistry } from './registry.js';\nimport {\n type BrowserToRelayMessage,\n BrowserToRelayMessageSchema,\n type RelayClientToServerMessage,\n RelayClientToServerMessageSchema,\n type RelayServerToClientMessage,\n RelayServerToClientMessageSchema,\n type RelayHelloAcceptedMessage,\n type RelayHelloRejectedMessage,\n type RelaySourceInfo,\n type RelayToBrowserMessage,\n type ServerHelloMessage,\n} from './schemas.js';\n\nconst RELAY_BROWSER_PROTOCOL = 'webmcp.v1';\nconst RELAY_DISCOVERY_PROTOCOL = 'webmcp-discovery.v1';\nconst RELAY_INTERNAL_PROTOCOL = 'webmcp-relay.v1';\n/**\n * How long a joining relay waits for the relay on a port to greet it. Too short, and a busy\n * relay is mistaken for another service, so a second relay starts and splits pages from clients.\n */\nconst RELAY_SERVER_MESSAGE_TIMEOUT_MS = 5_000;\nconst HEARTBEAT_INTERVAL_MS = 15_000;\nconst HEARTBEAT_DEAD_THRESHOLD_MS = 25_000;\nconst RawMessageTypeSchema = z.object({ type: z.unknown().optional() });\nconst RelayProtocolTypeSchema = z.string().startsWith('relay/');\nconst SUPPORTED_SUBPROTOCOLS = new Set([\n RELAY_BROWSER_PROTOCOL,\n RELAY_DISCOVERY_PROTOCOL,\n RELAY_INTERNAL_PROTOCOL,\n]);\n\n/**\n * In-flight relay invocation waiting for a browser `result` message.\n */\ninterface PendingInvocation {\n connectionId: string;\n resolve: (result: RelayCallToolResult) => void;\n reject: (error: Error) => void;\n cancel: (reason: Error) => void;\n}\n\nfunction formatPayloadLimit(bytes: number): string {\n if (bytes >= 1_000_000 && bytes % 1_000_000 === 0) {\n return `${String(bytes / 1_000_000)}MB`;\n }\n return `${String(bytes)} bytes`;\n}\n\n/**\n * Runtime options for {@link RelayBridgeServer}.\n */\nexport interface RelayBridgeServerOptions {\n /**\n * Network interface used by the local WebSocket server.\n * @defaultValue `\"127.0.0.1\"`\n */\n host?: string;\n /**\n * Preferred WebSocket port for browser widget connections.\n * @defaultValue `9333`\n */\n port?: number;\n /**\n * Whether the preferred port came from an explicit CLI/API override.\n * Explicit ports fail if occupied by a non-relay process.\n * @defaultValue `false`\n */\n portExplicitlySet?: boolean;\n /**\n * Inclusive upper bound for automatic port discovery.\n * @defaultValue `9348`\n */\n portRangeEnd?: number;\n /**\n * @deprecated Ignored. The relay no longer caches its port, so it always joins a relay\n * that owns the preferred port instead of starting a second one elsewhere.\n */\n persistPath?: string;\n /**\n * Allowed host page origins verified from browser WebSocket requests.\n * Use `[\"*\"]` to allow all origins.\n *\n * Permissive by default for zero-config developer experience: any browser\n * page can connect and register tools without additional setup. For production\n * or shared machines, restrict to trusted host origins via the\n * `--widget-origin` CLI flag or by passing explicit origins here.\n *\n * @defaultValue `[\"*\"]`\n */\n allowedOrigins?: string[];\n /**\n * Maximum WebSocket payload size in bytes.\n * @defaultValue `10000000`\n */\n maxPayloadBytes?: number;\n /**\n * Timeout used for browser tool invocations.\n * @defaultValue `65000`\n */\n invokeTimeoutMs?: number;\n /**\n * Human-readable relay label reported in discovery handshakes.\n */\n label?: string;\n /**\n * Optional workspace name reported in discovery handshakes.\n */\n workspace?: string;\n /**\n * Stable relay identifier used to select between multiple relays.\n */\n relayId?: string;\n}\n\n/**\n * WebSocket relay between browser widget frames and MCP server calls.\n *\n * Operates in two modes:\n * - **server** (default): Runs a WebSocket server, accepts browser and relay\n * client connections.\n * - **client** (fallback on EADDRINUSE): Connects as a WebSocket client to an\n * existing server relay and proxies tool operations through it.\n */\nexport class RelayBridgeServer extends EventEmitter {\n /**\n * Registry for connected sources and aggregated tool definitions.\n * Only actively used in server mode; remains empty when operating as a client.\n */\n readonly registry: RelayRegistry;\n\n private readonly host: string;\n private readonly preferredPort: number;\n private desiredPort: number;\n private readonly portExplicitlySet: boolean;\n private readonly portRangeEnd: number;\n private readonly allowedOrigins: string[];\n private readonly maxPayloadBytes: number;\n private readonly invokeTimeoutMs: number;\n private readonly label: string | undefined;\n private readonly workspace: string | undefined;\n private readonly relayId: string | undefined;\n private readonly instanceId: string;\n\n private wss: WebSocketServer | null = null;\n private readonly socketByConnectionId = new Map<string, WebSocket>();\n private readonly requestOriginByConnectionId = new Map<string, string>();\n private readonly pendingInvocations = new Map<string, PendingInvocation>();\n private readonly browserClientConnectionIds = new Set<string>();\n private readonly relayClientConnectionIds = new Set<string>();\n private readonly relayClientInvocations = new Map<string, Map<string, AbortController>>();\n private readonly heartbeatIntervalByConnectionId = new Map<\n string,\n ReturnType<typeof setInterval>\n >();\n private readonly lastPongByConnectionId = new Map<string, number>();\n private readonly onStateChangedPushRelay = () => {\n this.pushToolsToRelayClients();\n };\n\n private _mode: 'server' | 'client' = 'server';\n private clientSocket: WebSocket | null = null;\n private clientReconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private clientReconnectDelay = 500;\n /**\n * Maximum delay for relay-to-relay reconnection backoff in client mode.\n */\n private readonly clientMaxReconnectDelay = 3_000;\n private readonly clientPendingInvocations = new Map<string, PendingInvocation>();\n private clientTools: RelayTool[] = [];\n private clientSources: RelaySourceInfo[] = [];\n private clientToolSourceMap: Record<string, string[]> = {};\n private stopping = false;\n\n /**\n * Creates a relay bridge server instance.\n */\n constructor(options: RelayBridgeServerOptions = {}, registry?: RelayRegistry) {\n super();\n this.registry = registry ?? new RelayRegistry();\n\n this.host = options.host ?? '127.0.0.1';\n this.preferredPort = options.port ?? DEFAULT_RELAY_PORT;\n this.desiredPort = this.preferredPort;\n this.portExplicitlySet = options.portExplicitlySet ?? false;\n this.portRangeEnd =\n options.portRangeEnd ?? Math.max(DEFAULT_RELAY_PORT_RANGE_END, this.preferredPort);\n this.allowedOrigins = options.allowedOrigins ?? ['*'];\n // 10MB default: large enough for typical JSON API responses while\n // still protecting the relay process from unbounded memory growth.\n this.maxPayloadBytes = options.maxPayloadBytes ?? 10_000_000;\n this.invokeTimeoutMs = options.invokeTimeoutMs ?? 65_000;\n this.label = options.label;\n this.workspace = options.workspace;\n this.relayId = options.relayId;\n this.instanceId = randomUUID();\n\n if (\n !Number.isInteger(this.preferredPort) ||\n this.preferredPort < 0 ||\n this.preferredPort > 65535\n ) {\n throw new Error(\n `Invalid port ${this.preferredPort}. Port must be 0 (auto-assign) or between 1 and 65535.`\n );\n }\n if (\n !Number.isInteger(this.portRangeEnd) ||\n this.portRangeEnd < 1 ||\n this.portRangeEnd > 65535 ||\n (this.portRangeEnd < this.preferredPort && this.preferredPort !== 0)\n ) {\n throw new Error(\n `Invalid port range ${this.preferredPort}-${this.portRangeEnd}. rangeEnd must be an integer between the preferred port and 65535.`\n );\n }\n if (!Number.isInteger(this.maxPayloadBytes) || this.maxPayloadBytes <= 0) {\n throw new Error(\n `Invalid maxPayloadBytes ${this.maxPayloadBytes}. Must be a positive integer.`\n );\n }\n if (!Number.isInteger(this.invokeTimeoutMs) || this.invokeTimeoutMs <= 0) {\n throw new Error(\n `Invalid invokeTimeoutMs ${this.invokeTimeoutMs}. Must be a positive integer.`\n );\n }\n }\n\n /**\n * Current operating mode.\n */\n get mode(): 'server' | 'client' {\n return this._mode;\n }\n\n /**\n * Resolved listening port. In client mode this is the port of the server\n * relay being proxied through.\n */\n get port(): number {\n return this.desiredPort;\n }\n\n /**\n * Tools received from the server relay (client mode only).\n * Returns an empty array in server mode.\n */\n listToolsFromRelay(): RelayTool[] {\n return this._mode === 'client' ? [...this.clientTools] : [];\n }\n\n /**\n * Source metadata received from the server relay (client mode only).\n * Returns an empty array in server mode.\n */\n listSourcesFromRelay(): RelaySourceInfo[] {\n return this._mode === 'client' ? [...this.clientSources] : [];\n }\n\n /**\n * Tool-to-source mapping received from the server relay (client mode only).\n * Maps public tool names to arrays of source IDs.\n * Returns an empty record in server mode.\n */\n getToolSourceMapFromRelay(): Record<string, string[]> {\n return this._mode === 'client' ? { ...this.clientToolSourceMap } : {};\n }\n\n /**\n * Starts the bridge. Attempts to bind a WebSocket server (server mode).\n * If a compatible relay already owns a candidate port, joins it in client mode.\n * If a non-relay process owns the port, continues searching the reserved range.\n */\n async start(): Promise<void> {\n if (this.wss || this.clientSocket) {\n return;\n }\n\n this.stopping = false;\n await this.startUsingPortStrategy();\n }\n\n private async startUsingPortStrategy(): Promise<void> {\n if (this.preferredPort === 0) {\n await this.startAsServer(0);\n return;\n }\n\n const lastPort = this.portExplicitlySet ? this.preferredPort : this.portRangeEnd;\n for (let port = this.preferredPort; port <= lastPort; port++) {\n try {\n await this.startAsServer(port);\n return;\n } catch (err) {\n if (!this.isAddressInUseError(err)) {\n throw err;\n }\n\n if (await this.tryAttachToExistingRelay(port)) {\n return;\n }\n\n if (this.portExplicitlySet) {\n throw new Error(\n `Port ${port} is already in use by a non-WebMCP service and cannot be shared.`\n );\n }\n\n process.stderr.write(\n `[webmcp-local-relay] info: port ${port} is occupied by a non-relay service, trying next port\\n`\n );\n }\n }\n\n throw new Error(\n `No compatible relay port was available in the range ${this.preferredPort}-${this.portRangeEnd}.`\n );\n }\n\n private isAddressInUseError(cause: unknown): boolean {\n return (\n cause instanceof Error &&\n ('code' in cause ? cause.code === 'EADDRINUSE' : cause.message.includes('EADDRINUSE'))\n );\n }\n\n private async tryAttachToExistingRelay(port: number): Promise<boolean> {\n try {\n await this.startAsClient(port);\n process.stderr.write(\n `[webmcp-local-relay] info: discovered compatible relay on port ${port}, switching to client mode\\n`\n );\n return true;\n } catch (error) {\n // The caller reports every false here as \"occupied by a non-relay service\". A relay\n // that is stuck past the hello timeout lands here too, so name the real reason rather\n // than leaving the operator with a wrong hypothesis.\n process.stderr.write(\n `[webmcp-local-relay] info: port ${port} did not complete a relay handshake: ${error instanceof Error ? error.message : String(error)}\\n`\n );\n return false;\n }\n }\n\n /**\n * Stops all relay resources and rejects any pending invocations.\n */\n async stop(): Promise<void> {\n this.stopping = true;\n\n if (this.clientReconnectTimer) {\n clearTimeout(this.clientReconnectTimer);\n this.clientReconnectTimer = null;\n }\n\n if (this._mode === 'client') {\n for (const pending of this.clientPendingInvocations.values()) {\n pending.cancel(new Error('Relay client stopped'));\n }\n this.clientTools = [];\n this.clientSources = [];\n this.clientToolSourceMap = {};\n\n if (this.clientSocket) {\n try {\n this.clientSocket.close(1000, 'Relay client shutting down');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: error closing client socket during shutdown: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n this.clientSocket = null;\n }\n return;\n }\n\n this.off('stateChanged', this.onStateChangedPushRelay);\n\n for (const pending of this.pendingInvocations.values()) {\n pending.cancel(new Error('Relay server stopped before tool invocation completed'));\n }\n for (const invocations of this.relayClientInvocations.values()) {\n for (const controller of invocations.values()) controller.abort();\n }\n this.relayClientInvocations.clear();\n\n for (const [connectionId, socket] of this.socketByConnectionId) {\n this.registry.removeConnection(connectionId);\n try {\n socket.close(1001, 'Relay shutting down');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: error closing socket during shutdown: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }\n\n for (const intervalId of this.heartbeatIntervalByConnectionId.values()) {\n clearInterval(intervalId);\n }\n this.heartbeatIntervalByConnectionId.clear();\n this.lastPongByConnectionId.clear();\n\n this.socketByConnectionId.clear();\n this.requestOriginByConnectionId.clear();\n this.browserClientConnectionIds.clear();\n this.relayClientConnectionIds.clear();\n\n const wss = this.wss;\n this.wss = null;\n\n if (!wss) {\n return;\n }\n\n // Peers that stopped responding never acknowledge the close, and wss.close waits for them.\n const lingering = setTimeout(() => {\n for (const client of wss.clients) client.terminate();\n }, 500);\n await new Promise<void>((resolve) => {\n wss.close((err?: Error) => {\n if (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: WebSocket server close error: ${err.message}\\n`\n );\n }\n resolve();\n });\n });\n clearTimeout(lingering);\n }\n\n /**\n * Sends a reload message to a connected browser source.\n * Only supported in server mode.\n */\n reloadSource(connectionId: string): void {\n if (this._mode !== 'server') {\n throw new Error('reloadSource is only supported in server mode');\n }\n const socket = this.socketByConnectionId.get(connectionId);\n if (!socket || socket.readyState !== WebSocket.OPEN) {\n throw new Error(`Source ${connectionId} is not connected`);\n }\n const message: RelayToBrowserMessage = { type: 'reload' };\n try {\n socket.send(JSON.stringify(message));\n } catch (err) {\n throw new Error(`Failed to send reload: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n /**\n * Invokes a tool either locally (server mode) or through the upstream relay\n * (client mode).\n */\n async invokeTool(\n toolName: string,\n args: RelayInvokeArgs,\n options: {\n sourceId?: string;\n requestTabId?: string;\n /** Cancels the invocation and forwards cancellation to the browser runtime. */\n signal?: AbortSignal;\n } = {}\n ): Promise<RelayCallToolResult> {\n options.signal?.throwIfAborted();\n if (this._mode === 'client') {\n return this.invokeToolViaRelay(toolName, args, options.signal);\n }\n\n return this.invokeToolLocally(toolName, args, options);\n }\n\n private async startAsServer(port = this.desiredPort): Promise<void> {\n const wss = await new Promise<WebSocketServer>((resolve, reject) => {\n const server = new WebSocketServer({\n handleProtocols: (protocols) => {\n for (const protocol of protocols) {\n if (SUPPORTED_SUBPROTOCOLS.has(protocol)) {\n return protocol;\n }\n }\n return false;\n },\n host: this.host,\n port,\n maxPayload: this.maxPayloadBytes,\n });\n\n const onListening = () => {\n server.off('error', onError);\n resolve(server);\n };\n const onError = (err: Error) => {\n server.off('listening', onListening);\n reject(err);\n };\n\n server.once('listening', onListening);\n server.once('error', onError);\n });\n\n wss.on('connection', (socket: WebSocket, request) => {\n const requestOrigin = request.headers.origin;\n if (requestOrigin && socket.protocol === RELAY_INTERNAL_PROTOCOL) {\n socket.close(1008, 'Browser connections cannot use the relay protocol');\n return;\n }\n\n const connectionId = randomUUID();\n this.socketByConnectionId.set(connectionId, socket);\n if (requestOrigin) {\n this.requestOriginByConnectionId.set(connectionId, requestOrigin);\n }\n\n socket.on('message', (raw: WebSocket.RawData) => {\n this.onSocketMessage(connectionId, raw);\n });\n\n socket.on('close', (code: number) => {\n this.onSocketClose(connectionId, code);\n });\n\n socket.on('error', (err: Error) => {\n process.stderr.write(\n `[webmcp-local-relay] warn: socket error for connection ${connectionId}: ${err.message}\\n`\n );\n const code =\n 'code' in err && err.code === 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH' ? 1009 : undefined;\n this.onSocketClose(connectionId, code);\n });\n\n try {\n socket.send(JSON.stringify(this.buildServerHello()));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send server hello to connection ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n socket.close(1011, 'Failed to send server hello');\n return;\n }\n\n this.startHeartbeat(connectionId);\n });\n\n wss.on('error', (err: Error) => {\n // Logged, not re-emitted: nothing listens for 'error' on this EventEmitter, and\n // emit('error') without a listener rethrows, killing the relay via uncaughtException.\n process.stderr.write(`[webmcp-local-relay] error: WebSocket server error: ${err.message}\\n`);\n });\n\n this.wss = wss;\n this._mode = 'server';\n\n const address = wss.address();\n if (isTcpAddress(address)) {\n this.desiredPort = address.port;\n }\n\n this.on('stateChanged', this.onStateChangedPushRelay);\n }\n\n private buildServerHello(): ServerHelloMessage {\n return {\n type: 'server-hello',\n service: 'webmcp-local-relay',\n version: 1,\n host: this.host,\n instanceId: this.instanceId,\n label: this.label,\n port: this.desiredPort,\n relayId: this.relayId ?? this.instanceId,\n workspace: this.workspace,\n };\n }\n\n /**\n * `async` is load-bearing: {@link onRelayClientMessage} consumes this through\n * `.then(onResult, onError)`, so a synchronous throw would escape the socket\n * message handler as an uncaught exception and leave the relay client waiting\n * for a `relay/result` that never arrives.\n */\n private async invokeToolLocally(\n toolName: string,\n args: RelayInvokeArgs,\n options: { sourceId?: string; requestTabId?: string; signal?: AbortSignal }\n ): Promise<RelayCallToolResult> {\n const invocation: Parameters<RelayRegistry['resolveInvocation']>[0] = { toolName };\n if (options.sourceId !== undefined) invocation.sourceId = options.sourceId;\n if (options.requestTabId !== undefined) invocation.requestTabId = options.requestTabId;\n const resolved = this.registry.resolveInvocation(invocation);\n\n if (!resolved) {\n throw new Error(`No active browser source provides tool \"${toolName}\"`);\n }\n\n const socket = this.socketByConnectionId.get(resolved.connectionId);\n if (!socket || socket.readyState !== WebSocket.OPEN) {\n throw new Error(\n `Tool source ${resolved.connectionId} disconnected before invocation of \"${toolName}\"`\n );\n }\n\n return this.requestTool(\n socket,\n this.pendingInvocations,\n resolved.connectionId,\n {\n type: 'invoke',\n callId: randomUUID(),\n toolName: resolved.tool.name,\n args,\n },\n `Invocation for tool \"${toolName}\" timed out after ${this.invokeTimeoutMs}ms`,\n options.signal\n );\n }\n\n /** Handles a raw message according to the negotiated WebSocket protocol. */\n private onSocketMessage(connectionId: string, raw: WebSocket.RawData): void {\n const text = this.rawDataToUtf8(raw);\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(text);\n } catch (err) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}...` : text;\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid JSON from connection ${connectionId} (${err instanceof Error ? err.message : 'parse error'}): ${preview}\\n`\n );\n return;\n }\n\n const typeField = RawMessageTypeSchema.safeParse(parsedJson).data?.type;\n const socket = this.socketByConnectionId.get(connectionId);\n if (!socket) {\n return;\n }\n\n if (socket.protocol === RELAY_INTERNAL_PROTOCOL) {\n const relayMsg = RelayClientToServerMessageSchema.safeParse(parsedJson);\n if (relayMsg.success) {\n this.onRelayClientMessage(connectionId, relayMsg.data);\n } else {\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid relay message from ${connectionId}: ${relayMsg.error.message}\\n`\n );\n }\n return;\n }\n\n if (RelayProtocolTypeSchema.safeParse(typeField).success) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} used the relay protocol without negotiating it\\n`\n );\n socket.close(1008, 'Relay protocol was not negotiated');\n return;\n }\n\n const parsedMessage = BrowserToRelayMessageSchema.safeParse(parsedJson);\n if (!parsedMessage.success) {\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid message from connection ${connectionId} (type=${typeField}): ${parsedMessage.error.message}\\n`\n );\n return;\n }\n\n this.registry.touchConnection(connectionId);\n this.onBrowserClientMessage(connectionId, socket, parsedMessage.data);\n }\n\n private onBrowserClientMessage(\n connectionId: string,\n socket: WebSocket,\n message: BrowserToRelayMessage\n ): void {\n if (message.type !== 'hello' && !this.browserClientConnectionIds.has(connectionId)) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} sent ${message.type} before hello, ignoring\\n`\n );\n return;\n }\n\n switch (message.type) {\n case 'hello':\n try {\n const origin = this.requestOriginByConnectionId.get(connectionId) ?? message.origin;\n if (!this.isHostOriginAllowed(origin)) {\n process.stderr.write(\n `[webmcp-local-relay] warn: rejecting source ${connectionId} with disallowed host origin: ${origin ?? 'missing'}\\n`\n );\n this.sendHelloRejected(\n socket,\n {\n type: 'hello/rejected',\n reason: 'host-origin-not-allowed',\n message: 'Host page origin is not allowed by this relay.',\n },\n 1008,\n 'Host origin not allowed'\n );\n break;\n }\n this.registry.upsertSource(connectionId, { ...message, origin });\n this.browserClientConnectionIds.add(connectionId);\n this.sendHelloAccepted(socket, { type: 'hello/accepted' });\n this.emit('stateChanged');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error: failed to process hello from connection ${connectionId}: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`\n );\n }\n break;\n\n case 'tools/list':\n case 'tools/changed':\n try {\n this.registry.registerTools(connectionId, message.tools);\n this.emit('stateChanged');\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error: failed to register tools for connection ${connectionId}: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`\n );\n socket.close(1008, 'Invalid tool list');\n }\n break;\n\n case 'result': {\n const pending = this.pendingInvocations.get(message.callId);\n if (!pending) {\n process.stderr.write(\n `[webmcp-local-relay] warn: received result for unknown callId ${message.callId}\\n`\n );\n break;\n }\n if (pending.connectionId !== connectionId) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} returned result for another source's callId ${message.callId}\\n`\n );\n break;\n }\n\n pending.resolve(this.normalizeCallToolResult(message.result));\n break;\n }\n\n case 'pong':\n this.lastPongByConnectionId.set(connectionId, Date.now());\n break;\n }\n }\n\n /**\n * Handles relay-protocol messages from relay client connections.\n */\n private onRelayClientMessage(connectionId: string, message: RelayClientToServerMessage): void {\n if (message.type !== 'relay/hello' && !this.relayClientConnectionIds.has(connectionId)) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} sent ${message.type} before relay/hello, ignoring\\n`\n );\n return;\n }\n\n switch (message.type) {\n case 'relay/hello':\n this.relayClientConnectionIds.add(connectionId);\n break;\n\n case 'relay/list-tools': {\n const response = this.buildRelayToolsPayload('relay/tools');\n const socket = this.socketByConnectionId.get(connectionId);\n if (socket?.readyState === WebSocket.OPEN) {\n try {\n socket.send(JSON.stringify(response));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send relay tools response to ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }\n break;\n }\n\n case 'relay/invoke': {\n const { callId, toolName, args } = message;\n let invocations = this.relayClientInvocations.get(connectionId);\n if (!invocations) {\n invocations = new Map();\n this.relayClientInvocations.set(connectionId, invocations);\n }\n if (invocations.has(callId)) break;\n const controller = new AbortController();\n invocations.set(callId, controller);\n void this.invokeToolLocally(toolName, args ?? {}, { signal: controller.signal })\n .then(\n (result) => {\n if (!controller.signal.aborted) this.sendRelayResult(connectionId, callId, result);\n },\n (error) => {\n if (!controller.signal.aborted) {\n this.sendRelayResult(connectionId, callId, {\n content: [\n {\n type: 'text',\n text: `Relay invocation failed: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n });\n }\n }\n )\n .finally(() => {\n invocations.delete(callId);\n if (invocations.size === 0) this.relayClientInvocations.delete(connectionId);\n });\n break;\n }\n\n case 'relay/cancel':\n this.relayClientInvocations.get(connectionId)?.get(message.callId)?.abort();\n break;\n }\n }\n\n private sendRelayResult(connectionId: string, callId: string, result: RelayCallToolResult): void {\n const socket = this.socketByConnectionId.get(connectionId);\n if (socket?.readyState !== WebSocket.OPEN) {\n process.stderr.write(\n `[webmcp-local-relay] warn: relay client ${connectionId} disconnected before result for callId ${callId} could be delivered\\n`\n );\n return;\n }\n\n try {\n socket.send(JSON.stringify({ type: 'relay/result', callId, result }));\n } catch (error) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send relay result to ${connectionId}: ${error instanceof Error ? error.message : String(error)}\\n`\n );\n }\n }\n\n /**\n * Pushes current tool state to all connected relay clients.\n */\n private pushToolsToRelayClients(): void {\n if (this.relayClientConnectionIds.size === 0) {\n return;\n }\n\n const message = this.buildRelayToolsPayload('relay/tools-changed');\n const payload = JSON.stringify(message);\n\n for (const connectionId of this.relayClientConnectionIds) {\n const socket = this.socketByConnectionId.get(connectionId);\n if (socket?.readyState === WebSocket.OPEN) {\n try {\n socket.send(payload);\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to push tool update to relay client ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }\n }\n }\n\n /**\n * Handles source disconnection and rejects in-flight calls owned by that source.\n *\n * Guarded against double-call: both 'error' and 'close' events fire on\n * socket failure, but cleanup (and the stateChanged emission) runs only once.\n */\n private onSocketClose(connectionId: string, code?: number): void {\n // Guard against double invocation: ws fires both 'error' and 'close' for\n // maxPayload violations. The second call is a no-op.\n if (!this.socketByConnectionId.has(connectionId)) return;\n\n this.stopHeartbeat(connectionId);\n this.requestOriginByConnectionId.delete(connectionId);\n this.browserClientConnectionIds.delete(connectionId);\n this.relayClientConnectionIds.delete(connectionId);\n for (const controller of this.relayClientInvocations.get(connectionId)?.values() ?? []) {\n controller.abort();\n }\n this.relayClientInvocations.delete(connectionId);\n this.registry.removeConnection(connectionId);\n this.socketByConnectionId.delete(connectionId);\n this.emit('stateChanged');\n\n // WS close code 1009 = \"Message Too Big\": the browser sent a response\n // that exceeded maxPayloadBytes. Surface a clear error instead of letting\n // the invocation time out silently after invokeTimeoutMs.\n const isPayloadExceeded = code === 1009;\n\n for (const pending of this.pendingInvocations.values()) {\n if (pending.connectionId !== connectionId) {\n continue;\n }\n\n if (isPayloadExceeded) {\n pending.reject(\n new Error(\n `Tool result exceeded maximum payload size (${formatPayloadLimit(this.maxPayloadBytes)}). Use --max-payload to increase the limit.`\n )\n );\n } else {\n pending.reject(new Error(`Tool source ${connectionId} disconnected during invocation`));\n }\n }\n }\n\n private startHeartbeat(connectionId: string): void {\n this.lastPongByConnectionId.set(connectionId, Date.now());\n\n const intervalId = setInterval(() => {\n const socket = this.socketByConnectionId.get(connectionId);\n if (!socket || socket.readyState !== WebSocket.OPEN) {\n this.stopHeartbeat(connectionId);\n return;\n }\n\n // Skip heartbeat for relay client connections; they have their own reconnect logic.\n if (this.relayClientConnectionIds.has(connectionId)) {\n return;\n }\n\n const lastPong = this.lastPongByConnectionId.get(connectionId) ?? 0;\n if (Date.now() - lastPong > HEARTBEAT_DEAD_THRESHOLD_MS) {\n process.stderr.write(\n `[webmcp-local-relay] warn: connection ${connectionId} missed heartbeat, closing\\n`\n );\n this.stopHeartbeat(connectionId);\n // The peer is gone, so a close handshake would only hold the source for 30 s.\n socket.terminate();\n return;\n }\n\n try {\n socket.send(JSON.stringify({ type: 'ping' }));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send heartbeat ping to ${connectionId}: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n this.heartbeatIntervalByConnectionId.set(connectionId, intervalId);\n }\n\n private stopHeartbeat(connectionId: string): void {\n const intervalId = this.heartbeatIntervalByConnectionId.get(connectionId);\n if (intervalId !== undefined) {\n clearInterval(intervalId);\n this.heartbeatIntervalByConnectionId.delete(connectionId);\n }\n this.lastPongByConnectionId.delete(connectionId);\n }\n\n private async startAsClient(port = this.desiredPort): Promise<void> {\n const previousMode = this._mode;\n const previousPort = this.desiredPort;\n this._mode = 'client';\n this.desiredPort = port;\n\n try {\n const { bufferedMessages, socket } = await this.connectToRelayServer(port);\n this.clientSocket = socket;\n this.clientReconnectDelay = 500;\n this.setupClientHandlers(socket, bufferedMessages);\n } catch (error) {\n this._mode = previousMode;\n this.desiredPort = previousPort;\n throw error;\n }\n }\n\n private async connectToRelayServer(\n port: number\n ): Promise<{ bufferedMessages: RelayServerToClientMessage[]; socket: WebSocket }> {\n return new Promise((resolve, reject) => {\n const wsUrl = `ws://${this.host}:${port}`;\n const ws = new WebSocket(wsUrl, RELAY_INTERNAL_PROTOCOL);\n const bufferedMessages: RelayServerToClientMessage[] = [];\n\n const cleanup = () => {\n clearTimeout(timeoutId);\n ws.off('close', onCloseBeforeReady);\n ws.off('error', onErrorBeforeReady);\n ws.off('message', onMessageBeforeReady);\n ws.off('open', onOpen);\n };\n\n const rejectWith = (error: Error) => {\n cleanup();\n ws.once('error', () => {\n // Ignore late socket errors from ports that failed relay verification.\n });\n ws.terminate();\n reject(error);\n };\n\n const finish = () => {\n cleanup();\n resolve({ bufferedMessages, socket: ws });\n };\n\n const onOpen = () => {\n try {\n this.sendRelayClientHandshake(ws);\n } catch (err) {\n rejectWith(\n new Error(\n `Failed to send handshake to relay server at ${wsUrl}: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n }\n };\n\n const onErrorBeforeReady = (err: Error) => {\n rejectWith(new Error(`Failed to connect to relay server at ${wsUrl}: ${err.message}`));\n };\n\n const onCloseBeforeReady = () => {\n rejectWith(new Error(`Connection to ${wsUrl} closed before relay verification completed`));\n };\n\n const onMessageBeforeReady = (raw: WebSocket.RawData) => {\n const message = this.parseRelayServerMessage(raw);\n if (!message) {\n rejectWith(new Error(`Received a non-relay response while probing ${wsUrl}`));\n return;\n }\n\n bufferedMessages.push(message);\n\n if (message.type === 'server-hello') {\n if (message.service !== 'webmcp-local-relay') {\n rejectWith(new Error(`Unexpected relay service \"${message.service}\" at ${wsUrl}`));\n return;\n }\n finish();\n return;\n }\n\n if (message.type === 'relay/tools' || message.type === 'relay/tools-changed') {\n finish();\n }\n };\n\n const timeoutId = setTimeout(() => {\n rejectWith(new Error(`Timed out waiting for relay hello from ${wsUrl}`));\n }, RELAY_SERVER_MESSAGE_TIMEOUT_MS);\n\n ws.once('open', onOpen);\n ws.once('error', onErrorBeforeReady);\n ws.once('close', onCloseBeforeReady);\n ws.on('message', onMessageBeforeReady);\n });\n }\n\n private setupClientHandlers(\n ws: WebSocket,\n bufferedMessages: RelayServerToClientMessage[] = []\n ): void {\n for (const message of bufferedMessages) {\n this.processRelayServerMessage(message);\n }\n\n ws.on('message', (raw: WebSocket.RawData) => {\n const message = this.parseRelayServerMessage(raw);\n if (!message) {\n return;\n }\n\n this.processRelayServerMessage(message);\n });\n\n ws.on('close', () => {\n this.clientSocket = null;\n for (const pending of this.clientPendingInvocations.values()) {\n pending.reject(new Error('Relay server connection lost during invocation'));\n }\n\n this.clientTools = [];\n this.clientSources = [];\n this.clientToolSourceMap = {};\n this.emit('stateChanged');\n\n if (!this.stopping) {\n this.scheduleReconnect();\n }\n });\n\n ws.on('error', (err: Error) => {\n process.stderr.write(\n `[webmcp-local-relay] warn: relay client socket error: ${err.message}\\n`\n );\n });\n }\n\n private sendRelayClientHandshake(ws: WebSocket): void {\n ws.send(JSON.stringify({ type: 'relay/hello' }));\n ws.send(JSON.stringify({ type: 'relay/list-tools' }));\n }\n\n private parseRelayServerMessage(raw: WebSocket.RawData): RelayServerToClientMessage | null {\n const text = this.rawDataToUtf8(raw);\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n const preview = text.length > 200 ? `${text.slice(0, 200)}...` : text;\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid JSON from relay server (${err instanceof Error ? err.message : 'parse error'}): ${preview}\\n`\n );\n return null;\n }\n\n const message = RelayServerToClientMessageSchema.safeParse(parsed);\n if (!message.success) {\n const typeField = RawMessageTypeSchema.safeParse(parsed).data?.type ?? 'unknown';\n process.stderr.write(\n `[webmcp-local-relay] warn: invalid relay server message (type=${typeField}): ${message.error.message}\\n`\n );\n return null;\n }\n\n return message.data;\n }\n\n private processRelayServerMessage(message: RelayServerToClientMessage): void {\n switch (message.type) {\n case 'server-hello':\n break;\n\n case 'relay/tools':\n case 'relay/tools-changed':\n this.clientTools = message.tools;\n this.clientSources = message.sources;\n this.clientToolSourceMap = message.toolSourceMap;\n this.emit('stateChanged');\n break;\n\n case 'relay/result': {\n const pending = this.clientPendingInvocations.get(message.callId);\n if (!pending) {\n process.stderr.write(\n `[webmcp-local-relay] warn: received relay result for unknown callId ${message.callId}\\n`\n );\n break;\n }\n pending.resolve(message.result);\n break;\n }\n }\n }\n\n private scheduleReconnect(): void {\n if (this.clientReconnectTimer || this.stopping) {\n return;\n }\n\n const delay = this.clientReconnectDelay;\n this.clientReconnectDelay = Math.min(\n this.clientReconnectDelay * 1.5,\n this.clientMaxReconnectDelay\n );\n\n this.clientReconnectTimer = setTimeout(() => {\n this.clientReconnectTimer = null;\n if (this.stopping) {\n return;\n }\n\n void this.reconnectWithModePromotion();\n }, delay);\n }\n\n /**\n * Attempts to promote from client to server mode when reconnecting.\n * Re-runs the same attach-or-bind strategy used during startup.\n */\n private async reconnectWithModePromotion(): Promise<void> {\n try {\n await this.startUsingPortStrategy();\n if (this.stopping) {\n // stop() ran while this attempt was connecting; undo what it just started.\n await this.stop();\n return;\n }\n this.clientReconnectDelay = 500;\n if (this._mode === 'server') {\n process.stderr.write('[webmcp-local-relay] info: promoted from client to server mode\\n');\n this.emit('stateChanged');\n }\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: reconnection failed: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n if (!this.stopping) {\n this.scheduleReconnect();\n }\n }\n }\n\n private async invokeToolViaRelay(\n toolName: string,\n args: RelayInvokeArgs,\n signal?: AbortSignal\n ): Promise<RelayCallToolResult> {\n if (!this.clientSocket || this.clientSocket.readyState !== WebSocket.OPEN) {\n throw new Error('Not connected to relay server');\n }\n\n return this.requestTool(\n this.clientSocket,\n this.clientPendingInvocations,\n 'relay-server',\n { type: 'relay/invoke', callId: randomUUID(), toolName, args },\n `Proxied invocation for tool \"${toolName}\" timed out after ${this.invokeTimeoutMs}ms`,\n signal\n );\n }\n\n private requestTool(\n socket: WebSocket,\n invocations: Map<string, PendingInvocation>,\n connectionId: string,\n message:\n | Extract<RelayToBrowserMessage, { type: 'invoke' }>\n | Extract<RelayClientToServerMessage, { type: 'relay/invoke' }>,\n timeoutMessage: string,\n signal?: AbortSignal\n ): Promise<RelayCallToolResult> {\n signal?.throwIfAborted();\n const { callId } = message;\n return new Promise<RelayCallToolResult>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n pending.cancel(new Error(timeoutMessage));\n }, this.invokeTimeoutMs);\n const onAbort = () => pending.cancel(signal?.reason);\n const cleanup = () => {\n if (!invocations.delete(callId)) return false;\n clearTimeout(timeoutId);\n signal?.removeEventListener('abort', onAbort);\n return true;\n };\n const pending: PendingInvocation = {\n connectionId,\n resolve: (result) => {\n if (cleanup()) resolve(result);\n },\n reject: (reason) => {\n if (cleanup()) reject(reason);\n },\n cancel: (reason) => {\n if (!cleanup()) return;\n try {\n if (socket.readyState === WebSocket.OPEN) {\n const cancellation: RelayToBrowserMessage | RelayClientToServerMessage = {\n type: message.type === 'invoke' ? 'cancel' : 'relay/cancel',\n callId,\n };\n socket.send(JSON.stringify(cancellation));\n }\n } catch (error) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to cancel call ${callId}: ${error instanceof Error ? error.message : String(error)}\\n`\n );\n }\n reject(reason);\n },\n };\n invocations.set(callId, pending);\n signal?.addEventListener('abort', onAbort, { once: true });\n\n try {\n socket.send(JSON.stringify(message));\n } catch (err) {\n pending.reject(\n new Error(\n `Failed to send invocation: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n }\n });\n }\n\n /**\n * Builds the complete relay response payload including tools and source metadata.\n */\n private buildRelayToolsPayload(\n type: 'relay/tools' | 'relay/tools-changed'\n ): RelayServerToClientMessage {\n const tools = this.registry.listTools();\n const sources = this.registry.listSources();\n const toolSourceMap: Record<string, string[]> = {};\n\n for (const tool of tools) {\n toolSourceMap[tool.name] = tool.sources.map((s) => s.sourceId);\n }\n\n return {\n type,\n tools: tools.map(({ originalName: _originalName, sources: _sources, ...tool }) => tool),\n sources,\n toolSourceMap,\n };\n }\n\n private isHostOriginAllowed(origin: string | undefined): boolean {\n if (this.allowedOrigins.includes('*')) {\n return true;\n }\n\n if (!origin) {\n return false;\n }\n\n return this.allowedOrigins.includes(origin);\n }\n\n private sendHelloAccepted(socket: WebSocket, message: RelayHelloAcceptedMessage): void {\n try {\n socket.send(JSON.stringify(message));\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send hello acceptance: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n socket.close(1011, 'Failed to send hello acceptance');\n }\n }\n\n private sendHelloRejected(\n socket: WebSocket,\n message: RelayHelloRejectedMessage,\n closeCode: number,\n closeReason: string\n ): void {\n try {\n socket.send(JSON.stringify(message), () => {\n try {\n socket.close(closeCode, closeReason);\n } catch {\n // Ignore close failures after a rejection send attempt.\n }\n });\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] warn: failed to send hello rejection: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n socket.close(closeCode, closeReason);\n }\n }\n\n /**\n * Validates browser tool results against CallToolResultSchema.\n * Non-conforming payloads are wrapped as error results with diagnostic text.\n */\n private normalizeCallToolResult(result: unknown): RelayCallToolResult {\n const parsed = CallToolResultSchema.safeParse(result);\n if (parsed.success) {\n return parsed.data;\n }\n\n const preview = JSON.stringify(result)?.slice(0, 500) ?? 'undefined';\n process.stderr.write(\n `[webmcp-local-relay] warn: tool returned invalid CallToolResult (${parsed.error.message}), wrapping as error: ${preview}\\n`\n );\n\n return {\n content: [\n {\n type: 'text',\n text: `Tool returned an invalid result (expected {content: [...]}): ${preview}`,\n },\n ],\n isError: true,\n };\n }\n\n /**\n * Converts WebSocket raw data variants to a UTF-8 string payload.\n */\n private rawDataToUtf8(raw: WebSocket.RawData): string {\n if (Array.isArray(raw)) return Buffer.concat(raw).toString('utf8');\n if (Buffer.isBuffer(raw)) return raw.toString('utf8');\n return Buffer.from(raw).toString('utf8');\n }\n}\n","import { parseArgs } from 'node:util';\n\nimport { DEFAULT_RELAY_PORT } from './portStrategy.js';\n\n/**\n * Parsed CLI options for relay startup.\n */\nexport interface CliOptions {\n help: boolean;\n host: string;\n port: number;\n portExplicitlySet: boolean;\n allowedOrigins: string[];\n label?: string;\n workspace?: string;\n relayId?: string;\n maxPayloadBytes?: number;\n invokeTimeoutMs?: number;\n}\n\nfunction parsePositiveInteger(name: string, value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid ${name} \"${value}\". Must be a positive integer.`);\n }\n return parsed;\n}\n\n/**\n * Parses supported CLI flags for relay startup.\n */\nexport function parseCliOptions(argv: string[]): CliOptions {\n const { values } = parseArgs({\n args: argv,\n options: {\n help: { type: 'boolean', short: 'h' },\n host: { type: 'string', short: 'H' },\n 'allowed-origin': { type: 'string' },\n 'invoke-timeout': { type: 'string' },\n label: { type: 'string' },\n 'max-payload': { type: 'string' },\n port: { type: 'string', short: 'p' },\n 'relay-id': { type: 'string' },\n 'widget-origin': { type: 'string' },\n workspace: { type: 'string' },\n 'ws-origin': { type: 'string' },\n },\n strict: true,\n });\n\n const port = values.port === undefined ? DEFAULT_RELAY_PORT : Number(values.port);\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new Error(`Invalid port \"${values.port}\". Port must be an integer between 1 and 65535.`);\n }\n\n const invokeTimeoutMs = parsePositiveInteger('invoke-timeout', values['invoke-timeout']);\n const maxPayloadBytes = parsePositiveInteger('max-payload', values['max-payload']);\n\n const allowedOrigins = (\n values['widget-origin'] ??\n values['allowed-origin'] ??\n values['ws-origin']\n )\n ?.split(',')\n .map((origin) => origin.trim())\n .filter(Boolean) ?? ['*'];\n if (allowedOrigins.length === 0) {\n throw new Error('--widget-origin must include at least one origin.');\n }\n\n const options: CliOptions = {\n allowedOrigins,\n help: values.help ?? false,\n host: values.host ?? '127.0.0.1',\n port,\n portExplicitlySet: values.port !== undefined,\n };\n if (values.label) options.label = values.label;\n if (invokeTimeoutMs !== undefined) options.invokeTimeoutMs = invokeTimeoutMs;\n if (maxPayloadBytes !== undefined) options.maxPayloadBytes = maxPayloadBytes;\n if (values['relay-id']) options.relayId = values['relay-id'];\n if (values.workspace) options.workspace = values.workspace;\n return options;\n}\n\n/**\n * Prints CLI usage to stderr.\n */\nexport function printHelp(): void {\n process.stderr.write(\n [\n 'webmcp-local-relay',\n '',\n 'Usage:',\n ' webmcp-local-relay [--host 127.0.0.1] [--port 9333] [--widget-origin https://myapp.example.com]',\n '',\n 'Options:',\n ' --host, -H Bind host for local websocket relay (default: 127.0.0.1)',\n ' --port, -p Preferred root port for the local relay cluster (default: 9333)',\n ' --widget-origin Allowed host page origin(s), comma-separated (default: *)',\n ' --allowed-origin Deprecated alias for --widget-origin',\n ' --ws-origin Deprecated alias for --widget-origin',\n ' --label Human-readable relay label reported during discovery',\n ' --workspace Optional workspace name reported during discovery',\n ' --relay-id Stable relay identifier reported during discovery',\n ' --invoke-timeout Browser tool invocation timeout in milliseconds (default: 65000)',\n ' --max-payload Maximum WebSocket payload size in bytes (default: 10000000)',\n ' --help, -h Show help',\n '',\n ].join('\\n')\n );\n}\n","import { execFile } from 'node:child_process';\n\nimport {\n type CallToolResult,\n fromJsonSchema,\n McpServer,\n type RegisteredTool,\n type Transport,\n} from '@modelcontextprotocol/server';\nimport { StdioServerTransport } from '@modelcontextprotocol/server/stdio';\nimport { z } from 'zod/v4';\n\nimport { RelayBridgeServer, type RelayBridgeServerOptions } from './bridgeServer.js';\nimport type { AggregatedTool, SourceInfo } from './registry.js';\nimport { RelayInvokeArgsSchema, type RelayInvokeArgs } from './protocol.js';\n\nconst JsonSchemaObjectSchema = z.record(z.string(), z.unknown());\n\n/** Tools the relay registers itself, matching `registerStaticTools`. */\nconst RELAY_TOOL_NAMES = new Set([\n 'webmcp_list_sources',\n 'webmcp_list_tools',\n 'webmcp_call_tool',\n 'webmcp_open_page',\n]);\n\nfunction textResult(text: string): CallToolResult {\n return { content: [{ type: 'text', text }] };\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { content: [{ type: 'text', text }], isError: true };\n}\n\ninterface RelaySourcesResult {\n mode?: 'client';\n count: number;\n sources: SourceInfo[];\n}\n\n/**\n * Base options shared by all {@link LocalRelayMcpServer} configurations.\n */\ntype BrowserLauncher = (\n command: string,\n args: readonly string[],\n callback: (cause: Error | null) => void\n) => void;\n\ninterface LocalRelayMcpServerBaseOptions {\n /** Platform launcher; override to run without opening a browser. */\n launchBrowser?: BrowserLauncher;\n /**\n * MCP server name reported during initialization.\n */\n serverName?: string;\n /**\n * MCP server version reported during initialization.\n */\n serverVersion?: string;\n}\n\n/**\n * Construction options for {@link LocalRelayMcpServer}.\n *\n * Provide either an existing `bridge` instance OR `bridgeOptions` to create\n * one internally — not both.\n */\nexport type LocalRelayMcpServerOptions = LocalRelayMcpServerBaseOptions &\n (\n | { bridge: RelayBridgeServer; bridgeOptions?: never }\n | { bridge?: never; bridgeOptions?: RelayBridgeServerOptions }\n );\n\n/**\n * MCP server facade that exposes browser-relayed tools over MCP transport.\n */\nexport class LocalRelayMcpServer {\n /**\n * Underlying WebSocket bridge used for browser communication.\n */\n readonly bridge: RelayBridgeServer;\n\n private readonly mcpServer: McpServer;\n private readonly launchBrowser: BrowserLauncher;\n private readonly dynamicTools = new Map<string, { handle: RegisteredTool; signature: string }>();\n /** Page tools that share a name with a relay tool, already reported once. */\n private readonly shadowedToolNames = new Set<string>();\n\n private syncDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n private connected = false;\n\n /**\n * Creates a local relay MCP server with static and dynamic tool registration.\n */\n constructor(options: LocalRelayMcpServerOptions = {}) {\n this.bridge = options.bridge ?? new RelayBridgeServer(options.bridgeOptions);\n this.launchBrowser = options.launchBrowser ?? execFile;\n\n this.mcpServer = new McpServer({\n name: options.serverName ?? 'webmcp-local-relay',\n version: options.serverVersion ?? '0.0.0',\n });\n\n this.bridge.on('stateChanged', () => {\n this.debouncedSyncDynamicTools();\n });\n\n this.registerStaticTools();\n }\n\n /**\n * Starts the browser bridge and synchronizes dynamic MCP tools.\n */\n async start(): Promise<void> {\n await this.bridge.start();\n this.syncDynamicTools();\n }\n\n /**\n * Connects the MCP server to a transport.\n *\n * This may be called exactly once per instance lifecycle.\n */\n async connect(transport: Transport): Promise<void> {\n if (this.connected) {\n throw new Error('MCP server transport already connected');\n }\n\n await this.mcpServer.connect(transport);\n this.connected = true;\n\n this.mcpServer.server.onerror = (error) => {\n process.stderr.write(\n `[webmcp-local-relay] error: MCP protocol error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\n`\n );\n };\n }\n\n /**\n * Convenience helper that connects the server over stdio transport.\n */\n async startStdio(): Promise<void> {\n await this.connect(new StdioServerTransport());\n }\n\n /**\n * Stops MCP transport and bridge resources.\n */\n async stop(): Promise<void> {\n this.connected = false;\n\n if (this.syncDebounceTimer) {\n clearTimeout(this.syncDebounceTimer);\n this.syncDebounceTimer = null;\n }\n\n let mcpCloseError: unknown;\n try {\n await this.mcpServer.close();\n } catch (err) {\n mcpCloseError = err;\n process.stderr.write(\n `[webmcp-local-relay] error: failed to close MCP server: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`\n );\n }\n await this.bridge.stop();\n if (mcpCloseError) {\n process.stderr.write(\n '[webmcp-local-relay] warn: shutdown completed with errors (MCP server close failed)\\n'\n );\n }\n }\n\n /**\n * Returns dynamic tool names currently registered in MCP.\n */\n listDynamicToolNames(): string[] {\n return Array.from(this.dynamicTools.keys()).sort();\n }\n\n /**\n * Registers built-in management tools exposed by the relay.\n */\n private registerStaticTools(): void {\n this.mcpServer.registerTool(\n 'webmcp_list_sources',\n {\n description: 'List connected browser tool sources and their metadata.',\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n },\n },\n async () => {\n const clientMode = this.bridge.mode === 'client';\n const sources = clientMode\n ? this.bridge.listSourcesFromRelay()\n : this.bridge.registry.listSources();\n const info: RelaySourcesResult = {\n count: sources.length,\n sources,\n };\n if (clientMode) info.mode = 'client';\n return {\n content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],\n structuredContent: info,\n };\n }\n );\n\n this.mcpServer.registerTool(\n 'webmcp_list_tools',\n {\n description:\n 'List WebMCP tools available from connected browser sources. Returns tool definitions including name, description, input schema, and source info. Use webmcp_call_tool to call one by name.',\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n },\n },\n async () => {\n const tools = this.listAggregatedTools();\n const info = { count: tools.length, tools };\n return {\n content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],\n structuredContent: info,\n };\n }\n );\n\n // Clients that ignore tools/list_changed can still call tools registered after they connect.\n this.mcpServer.registerTool(\n 'webmcp_call_tool',\n {\n description:\n 'Call a WebMCP tool from a connected browser page by name. Use webmcp_list_tools first to see available tools and their input schemas.',\n inputSchema: z.object({\n name: z.string().describe('Tool name as returned by webmcp_list_tools.'),\n arguments: RelayInvokeArgsSchema.describe('Arguments matching the tool input schema.'),\n }),\n annotations: { readOnlyHint: false },\n },\n async ({ name, arguments: args }, context) => {\n if (!this.listAggregatedTools().some((tool) => tool.name === name)) {\n return errorResult(\n `Tool \"${name}\" not found. Call webmcp_list_tools to see available tools.`\n );\n }\n return this.invokeRelayedTool(name, args ?? {}, context.mcpReq.signal);\n }\n );\n\n this.mcpServer.registerTool(\n 'webmcp_open_page',\n {\n description:\n \"Open a URL in the user's default browser, or refresh a connected source page. Use to launch WebMCP-enabled pages or reload stale connections.\",\n inputSchema: z.object({\n url: z.string().describe('URL to open or match for refresh.'),\n refresh: z\n .boolean()\n .optional()\n .describe(\n 'If true, refresh the connected source matching this URL instead of opening a new tab.'\n ),\n }),\n annotations: { readOnlyHint: false },\n },\n async ({ url, refresh }) => {\n const parsed = URL.parse(url);\n if (!parsed) {\n return errorResult(`Invalid URL: ${url}`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return errorResult(`Only http: and https: URLs are allowed. Got: ${parsed.protocol}`);\n }\n\n const existing =\n this.bridge.mode === 'server'\n ? this.bridge.registry\n .listSources()\n .find((source) => source.url && URL.parse(source.url)?.origin === parsed.origin)\n : undefined;\n\n if (refresh) {\n if (this.bridge.mode === 'client') {\n return errorResult(\n 'Refresh is not supported in client mode. Only the server relay can reload sources.'\n );\n }\n if (!existing) {\n return errorResult(\n `No connected source matches origin ${parsed.origin}. The page may not be open or connected.`\n );\n }\n try {\n this.bridge.reloadSource(existing.sourceId);\n } catch (err) {\n return errorResult(\n `Failed to reload source: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n return textResult(\n `Reload sent to source ${existing.sourceId} (${existing.url ?? existing.origin}).`\n );\n }\n\n try {\n await this.openInBrowser(url);\n } catch (err) {\n return errorResult(\n `Failed to open browser: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n return textResult(\n existing\n ? `Opened ${url} in the default browser. Note: a source from ${existing.url ?? existing.origin} is already connected.`\n : `Opened ${url} in the default browser.`\n );\n }\n );\n }\n\n /**\n * Opens a URL in the user's default browser using the platform open command.\n *\n * Passes the re-serialized `URL.href` directly to the platform launcher.\n * No shell or command-language string is involved.\n */\n private openInBrowser(url: string): Promise<void> {\n const safeUrl = new URL(url).href;\n const command =\n process.platform === 'darwin'\n ? 'open'\n : process.platform === 'win32'\n ? 'explorer.exe'\n : 'xdg-open';\n return new Promise((resolve, reject) => {\n this.launchBrowser(command, [safeUrl], (err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n }\n\n /**\n * Debounces rapid stateChanged events into a single sync pass.\n *\n * Browser reconnections emit multiple stateChanged events in quick\n * succession (hello, tools/list). Without debouncing, each triggers a\n * full sync that removes and re-registers tools — sending per-operation\n * notifications through the stdio transport and overwhelming the agent.\n */\n private debouncedSyncDynamicTools(): void {\n if (this.syncDebounceTimer) {\n return;\n }\n\n this.syncDebounceTimer = setTimeout(() => {\n this.syncDebounceTimer = null;\n try {\n this.syncDynamicTools();\n } catch (err) {\n const details = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(\n `[webmcp-local-relay] error: failed to sync dynamic tools: ${details}\\n`\n );\n }\n }, 16);\n }\n\n /**\n * Returns the current aggregated tool list, dispatching based on bridge mode.\n */\n private listAggregatedTools(): AggregatedTool[] {\n return this.bridge.mode === 'client'\n ? this.buildAggregatedToolsFromRelay()\n : this.bridge.registry.listTools();\n }\n\n /**\n * Converts relay client tool descriptors to aggregated tool shape.\n * Populates per-tool source metadata from the relay's tool-source mapping.\n */\n private buildAggregatedToolsFromRelay(): AggregatedTool[] {\n const toolSourceMap = this.bridge.getToolSourceMapFromRelay();\n const allSources = this.bridge.listSourcesFromRelay();\n const sourceById = new Map(allSources.map((s) => [s.sourceId, s]));\n\n return this.bridge\n .listToolsFromRelay()\n .map((tool) => {\n const sourceIds = toolSourceMap[tool.name] ?? [];\n const sources: SourceInfo[] = [];\n for (const id of sourceIds) {\n const source = sourceById.get(id);\n if (source) {\n sources.push(source);\n } else {\n process.stderr.write(\n `[webmcp-local-relay] warn: tool \"${tool.name}\" references unknown sourceId \"${id}\"\\n`\n );\n }\n }\n\n return {\n ...tool,\n originalName: tool.name,\n sources,\n };\n })\n .sort((left, right) => left.name.localeCompare(right.name));\n }\n\n /**\n * Applies current aggregated tool state to MCP dynamic registrations.\n */\n private syncDynamicTools(): void {\n const tools = this.listAggregatedTools();\n const nextNames = new Set(tools.map((tool) => tool.name));\n\n for (const [name, registration] of this.dynamicTools) {\n if (nextNames.has(name)) {\n continue;\n }\n\n registration.handle.remove();\n this.dynamicTools.delete(name);\n }\n\n for (const tool of tools) {\n if (RELAY_TOOL_NAMES.has(tool.name)) {\n if (!this.shadowedToolNames.has(tool.name)) {\n this.shadowedToolNames.add(tool.name);\n process.stderr.write(\n `[webmcp-local-relay] warn: skipped page tool \"${tool.name}\" because a built-in relay tool has that name\\n`\n );\n }\n continue;\n }\n\n const signature = this.toolSignature(tool);\n const current = this.dynamicTools.get(tool.name);\n\n if (current?.signature === signature) {\n continue;\n }\n\n if (current) {\n current.handle.remove();\n this.dynamicTools.delete(tool.name);\n }\n\n try {\n const handle = this.registerDynamicTool(tool);\n this.dynamicTools.set(tool.name, { handle, signature });\n } catch (err) {\n const details = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(\n `[webmcp-local-relay] warn: skipped dynamic tool \"${tool.name}\" because its schema could not be compiled: ${details}\\n`\n );\n }\n }\n }\n\n /**\n * Registers a single dynamic tool and returns a removal handle.\n */\n private registerDynamicTool(tool: AggregatedTool): RegisteredTool {\n // Validate the container before handing JSON Schema validation to the SDK compiler.\n const inputSchema = fromJsonSchema<RelayInvokeArgs>(\n JsonSchemaObjectSchema.parse(tool.inputSchema)\n );\n const outputSchema =\n tool.outputSchema !== undefined\n ? fromJsonSchema(JsonSchemaObjectSchema.parse(tool.outputSchema))\n : undefined;\n\n const registration: {\n [K in 'title' | 'annotations' | 'icons' | '_meta']?: Exclude<AggregatedTool[K], undefined>;\n } & {\n description: string;\n inputSchema: typeof inputSchema;\n outputSchema?: NonNullable<typeof outputSchema>;\n } = { description: this.dynamicToolDescription(tool), inputSchema };\n if (tool.title !== undefined) registration.title = tool.title;\n if (outputSchema) registration.outputSchema = outputSchema;\n if (tool.annotations) registration.annotations = tool.annotations;\n if (tool.icons) registration.icons = tool.icons;\n if (tool._meta) registration._meta = tool._meta;\n return this.mcpServer.registerTool(tool.name, registration, (args: RelayInvokeArgs, context) =>\n this.invokeRelayedTool(tool.name, args, context.mcpReq.signal)\n );\n }\n\n private async invokeRelayedTool(\n name: string,\n args: RelayInvokeArgs,\n signal: AbortSignal\n ): Promise<CallToolResult> {\n try {\n return await this.bridge.invokeTool(name, args, { signal });\n } catch (err) {\n if (signal.aborted) throw err;\n const message = err instanceof Error ? err.message : String(err);\n const details = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(\n `[webmcp-local-relay] error: relayed tool \"${name}\" invocation failed: ${details}\\n`\n );\n return errorResult(`Failed to invoke relayed tool \"${name}\": ${message}`);\n }\n }\n\n /**\n * Builds a display description for relayed tools including source context.\n * In client mode, source metadata is unavailable, so tools are labeled `[WebMCP relay]`.\n */\n private dynamicToolDescription(tool: AggregatedTool): string {\n const source = tool.sources[0];\n const sourceLabel = source\n ? `[WebMCP ${source.tabId}${source.title ? ` • ${source.title}` : ''}]`\n : '[WebMCP relay]';\n\n return `${sourceLabel} ${tool.description ?? `Relayed tool ${tool.originalName}`}`;\n }\n\n /**\n * Produces a stable signature for change detection of dynamic tool metadata.\n *\n * Deliberately excludes `sourceId` (which is a per-connection UUID that\n * changes on every browser reconnect) so that a reconnecting tab with the\n * same tools does not trigger a needless remove+re-register cycle.\n */\n private toolSignature(tool: AggregatedTool): string {\n return JSON.stringify({\n originalName: tool.originalName,\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n annotations: tool.annotations,\n _meta: tool._meta,\n icons: tool.icons,\n source: tool.sources[0]\n ? { tabId: tool.sources[0].tabId, title: tool.sources[0].title }\n : undefined,\n });\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/webmcp-local-relay",
3
- "version": "6.0.2",
3
+ "version": "6.1.0",
4
4
  "description": "Local MCP relay for browser WebMCP tools via embed iframe and localhost WebSocket",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -44,14 +44,14 @@
44
44
  "registry": "https://registry.npmjs.org/"
45
45
  },
46
46
  "dependencies": {
47
- "@modelcontextprotocol/core": "2.0.0",
48
- "@modelcontextprotocol/server": "2.0.0",
47
+ "@modelcontextprotocol/core": "2.4.0",
48
+ "@modelcontextprotocol/server": "2.4.0",
49
49
  "ws": "^8.22.0",
50
- "zod": "4.4.3"
50
+ "zod": "4.6.7"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@anthropic-ai/mcpb": "2.1.2",
54
- "@modelcontextprotocol/client": "2.2.0",
54
+ "@modelcontextprotocol/client": "2.4.0",
55
55
  "@types/node": "22.20.1",
56
56
  "@types/ws": "^8.5.14",
57
57
  "@vitest/coverage-v8": "4.1.8",
@@ -59,7 +59,7 @@
59
59
  "typescript": "^5.8.3",
60
60
  "vite-plus": "0.1.24",
61
61
  "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.24",
62
- "@mcp-b/webmcp-ts-sdk": "6.0.2"
62
+ "@mcp-b/webmcp-ts-sdk": "6.1.0"
63
63
  },
64
64
  "engines": {
65
65
  "node": ">=22"