@mcp-b/webmcp-local-relay 6.0.0 → 6.0.2
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 +15 -15
- package/dist/browser/embed.js +1 -2
- package/dist/browser/widget.html +2 -1
- package/dist/browser/widget.js +2 -1
- package/dist/cli.mjs +6 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +24 -147
- package/dist/index.mjs +1 -1
- package/dist/{mcpRelayServer-DEgebuO2.mjs → mcpRelayServer-DkidP4o7.mjs} +157 -182
- package/dist/mcpRelayServer-DkidP4o7.mjs.map +1 -0
- package/package.json +5 -5
- package/dist/mcpRelayServer-DEgebuO2.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -213,16 +213,16 @@ npx @mcp-b/webmcp-local-relay --widget-origin https://myapp.com
|
|
|
213
213
|
│ WebSocket (ws://127.0.0.1:9333)
|
|
214
214
|
┌──────────────────▼───────────────────┐
|
|
215
215
|
│ Widget iframe │
|
|
216
|
-
│
|
|
216
|
+
│ Reads page tools + relay socket │
|
|
217
217
|
└──────────────────┬───────────────────┘
|
|
218
|
-
│
|
|
218
|
+
│ same-origin access
|
|
219
219
|
┌──────────────────▼───────────────────┐
|
|
220
220
|
│ Host page │
|
|
221
221
|
│ WebMCP runtime + registered tools │
|
|
222
222
|
└──────────────────────────────────────┘
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
-
**How it connects:** The embed script fetches the sibling `widget.html`, injects configuration, and loads it as a hidden blob iframe that inherits the host page origin. The
|
|
225
|
+
**How it connects:** The embed script fetches the sibling `widget.html`, injects configuration, and loads it as a hidden blob iframe that inherits the host page origin. The widget reads the host page's `document.modelContext` directly, so native WebMCP, the polyfill, and `@mcp-b/global` all work. `postMessage` is limited to reconnect and reload controls. The widget keeps a WebSocket to the relay on `localhost` for MCP traffic. Self-hosted copies must serve both `embed.js` and `widget.html`; cross-origin hosts must allow the widget fetch with CORS. The relay fails closed if that fetch fails.
|
|
226
226
|
|
|
227
227
|
After a disconnect, the widget retries the last endpoint once after about `500ms`, then rescans the relay range after `10s`, `20s`, and `30s`. If no relay responds, it enters a dormant state and probes the configured or cached endpoint every two minutes; returning to the tab or sending `webmcp.connect` triggers immediate rediscovery.
|
|
228
228
|
|
|
@@ -233,7 +233,7 @@ After a disconnect, the widget retries the last endpoint once after about `500ms
|
|
|
233
233
|
Supported page runtimes:
|
|
234
234
|
|
|
235
235
|
1. `@mcp-b/global` (recommended for the complete MCP-B runtime)
|
|
236
|
-
2. Current native Chrome with `document.modelContext.getTools()` and object-input `executeTool()
|
|
236
|
+
2. Current native Chrome with `document.modelContext.getTools()` and object-input `executeTool()`
|
|
237
237
|
3. `@mcp-b/webmcp-polyfill`
|
|
238
238
|
|
|
239
239
|
Runtime dispatch behavior in the browser embed/widget layer:
|
|
@@ -243,9 +243,9 @@ Runtime dispatch behavior in the browser embed/widget layer:
|
|
|
243
243
|
upstream API.
|
|
244
244
|
- Refreshes the descriptor before every invocation so Chrome never receives a
|
|
245
245
|
stale registration object.
|
|
246
|
-
- Relays the page's
|
|
247
|
-
frames. When names collide, the page's own tool wins and the
|
|
248
|
-
warning per name.
|
|
246
|
+
- Relays the host page's tools plus tools registered by other same-origin
|
|
247
|
+
frames. When names collide, the host page's own tool wins and the widget logs
|
|
248
|
+
one warning per name.
|
|
249
249
|
- Converts only results that parse to a JSON object into structured content.
|
|
250
250
|
A JSON string arrives as its content; any other result, such as `10.50`,
|
|
251
251
|
keeps its original text.
|
|
@@ -266,13 +266,13 @@ For Chromium/Chrome Canary native preview testing:
|
|
|
266
266
|
|
|
267
267
|
### Troubleshooting
|
|
268
268
|
|
|
269
|
-
| Problem
|
|
270
|
-
|
|
|
271
|
-
| `No sources connected`
|
|
272
|
-
| `No tools listed`
|
|
273
|
-
| `Tool not found`
|
|
274
|
-
| Connection blocked
|
|
275
|
-
| `
|
|
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 |
|
|
276
276
|
|
|
277
277
|
---
|
|
278
278
|
|
|
@@ -291,7 +291,7 @@ src/
|
|
|
291
291
|
├── schemas.ts Browser <-> relay protocol schemas
|
|
292
292
|
├── browser/embed.ts Script-tag loader for website owners
|
|
293
293
|
├── browser/widget.ts Widget IIFE entry point (calls startWidgetRuntime)
|
|
294
|
-
├── browser/widgetRuntime.ts
|
|
294
|
+
├── browser/widgetRuntime.ts Iframe discovery, execution, and relay runtime
|
|
295
295
|
├── browser/shared.ts Shared browser-side utilities
|
|
296
296
|
└── index.ts Public API exports
|
|
297
297
|
```
|
package/dist/browser/embed.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
(function(){function e(e){return
|
|
2
|
-
`)}function A(){w=!1;let e=T;x().then(t=>{if(e!==T)return;let n=k(t);n===D||!m||(D=n,m.postMessage({type:`webmcp.tools.changed`,tools:t},window.location.origin))}).catch(e=>{_(`Failed to sync tool changes:`,e)})}function j(){T++,!w&&(w=!0,setTimeout(A,0))}function M(){E||(E=setInterval(j,2e3))}function N(){try{let e=document.modelContext;return e?(e.addEventListener(`toolchange`,j),!0):!1}catch(e){return _(`addEventListener on modelContext threw:`,e),!1}}function P(){if(M(),j(),N())return;let e=0,t=100,n=()=>{setTimeout(()=>{if(e++,!N()){if(e>=40){_(`Could not subscribe to tool changes after 40 retries. Dynamic tool updates will rely on polling.`);return}t=Math.min(Math.round(t*1.5),1e3),n()}},t)};n()}function F(e,t){x().then(n=>{e.postMessage({type:`webmcp.tools.list.response`,requestId:t,tools:n},window.location.origin)}).catch(n=>{_(`Failed to list tools:`,n),e.postMessage({type:`webmcp.tools.list.response`,requestId:t,tools:[],error:`Failed to list tools: ${n instanceof Error?n.message:String(n)}`},window.location.origin)})}function I(e,t){let{requestId:n}=t;S(String(t.toolName??``),C(t.args)).then(t=>{e.postMessage({type:`webmcp.tools.invoke.response`,requestId:n,result:t},window.location.origin)}).catch(t=>{e.postMessage({type:`webmcp.tools.invoke.error`,requestId:n,error:String(t instanceof Error?t.message:t)},window.location.origin)})}async function L({widgetUrl:e,...t}){if(document.querySelector(f))return;let n=new URL(window.location.href);n.search=``,n.hash=``;let r=JSON.stringify({...t,hostOrigin:window.location.origin,hostUrl:n.href,hostTitle:document.title}).replace(/</g,`\\u003c`),i=await fetch(e);if(!i.ok)throw Error(`Widget HTML request failed with status ${String(i.status)}`);let a=await i.text(),o=`<script>window.__WEBMCP_RELAY_CONFIG=${r};<\/script>`,s=URL.createObjectURL(new Blob([a.replace(`</head>`,()=>`${o}</head>`)],{type:`text/html`})),c=document.createElement(`iframe`);c.src=s,c.style.display=`none`,c.setAttribute(`aria-hidden`,`true`),c.setAttribute(`data-webmcp-relay`,`1`),c.setAttribute(`allow`,`loopback-network; local-network; local-network-access`),document.body.appendChild(c),m=c.contentWindow,c.addEventListener(`load`,()=>{m=c.contentWindow,URL.revokeObjectURL(s)}),c.addEventListener(`error`,()=>{console.error(`[webmcp-relay-embed] Failed to load relay widget iframe from:`,c.src,`-- WebMCP tools will NOT be relayed. Check network connectivity and widget URL.`),URL.revokeObjectURL(s)})}if(!document.querySelector(f)){let e;try{e=y(h)}catch(e){throw console.error(`[webmcp-relay-embed] Failed to initialize relay configuration:`,e),e}window.addEventListener(`message`,e=>{let t=m;if(e.origin!==window.location.origin||!t||e.source!==t)return;let n=e.data;if(c(n)&&n.type===`webmcp.reload`){window.location.reload();return}l(n)&&(n.type===`webmcp.tools.list.request`?F(t,n.requestId):n.type===`webmcp.tools.invoke.request`&&I(t,n))});let t=()=>{L(e).catch(e=>{console.error(`[webmcp-relay-embed] Failed to inject relay widget:`,e)})};document.body?t():document.addEventListener(`DOMContentLoaded`,t,{once:!0}),P(),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&m&&m.postMessage({type:`webmcp.connect`},window.location.origin)})}})();
|
|
1
|
+
(function(){function e(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}let t=`[data-webmcp-relay]`,n=`__webmcp_relay_tab_id`,r=null,i=document.currentScript instanceof HTMLScriptElement?document.currentScript:null,a=i?i.hasAttribute(`data-debug`):!1;function o(...e){a&&console.warn(`[webmcp-relay-embed]`,...e)}function s(){try{let e=sessionStorage.getItem(n);if(e)return e}catch(e){o(`sessionStorage read failed, tab ID will not persist:`,e)}let e=crypto.randomUUID();try{sessionStorage.setItem(n,e)}catch(e){o(`sessionStorage write failed:`,e)}return e}function c(e){if(!e?.src)throw Error(`The relay embed script must be loaded from a URL`);return{autoConnect:String(e.getAttribute(`data-auto-connect`)!==`false`),relayHost:e.getAttribute(`data-relay-host`)||`127.0.0.1`,relayPort:e.getAttribute(`data-relay-port`)||`9333`,relayId:e.getAttribute(`data-relay-id`)||void 0,relayWorkspace:e.getAttribute(`data-relay-workspace`)||void 0,requestTimeout:e.getAttribute(`data-request-timeout`)||void 0,tabId:s(),widgetUrl:new URL(`widget.html`,e.src).href}}async function l({widgetUrl:e,...n}){if(document.querySelector(t))return;let i=new URL(window.location.href);i.search=``,i.hash=``;let a=JSON.stringify({...n,hostOrigin:window.location.origin,hostUrl:i.href,hostTitle:document.title}).replace(/</g,`\\u003c`),o=await fetch(e);if(!o.ok)throw Error(`Widget HTML request failed with status ${String(o.status)}`);let s=await o.text(),c=`<script>window.__WEBMCP_RELAY_CONFIG=${a};<\/script>`,l=URL.createObjectURL(new Blob([s.replace(`</head>`,()=>`${c}</head>`)],{type:`text/html`})),u=document.createElement(`iframe`);u.src=l,u.style.display=`none`,u.setAttribute(`aria-hidden`,`true`),u.setAttribute(`data-webmcp-relay`,`1`),u.setAttribute(`allow`,`loopback-network; local-network; local-network-access`),document.body.appendChild(u),r=u.contentWindow,u.addEventListener(`load`,()=>{r=u.contentWindow,URL.revokeObjectURL(l)}),u.addEventListener(`error`,()=>{console.error(`[webmcp-relay-embed] Failed to load relay widget iframe from:`,u.src,`-- WebMCP tools will NOT be relayed. Check network connectivity and widget URL.`),URL.revokeObjectURL(l)})}if(!document.querySelector(t)){let t;try{t=c(i)}catch(e){throw console.error(`[webmcp-relay-embed] Failed to initialize relay configuration:`,e),e}window.addEventListener(`message`,t=>{t.origin!==window.location.origin||!r||t.source!==r||e(t.data)&&t.data.type===`webmcp.reload`&&window.location.reload()});let n=()=>{l(t).catch(e=>{console.error(`[webmcp-relay-embed] Failed to inject relay widget:`,e)})};document.body?n():document.addEventListener(`DOMContentLoaded`,n,{once:!0}),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&r&&r.postMessage({type:`webmcp.connect`},window.location.origin)})}})();
|
package/dist/browser/widget.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<title>WebMCP Relay Widget</title>
|
|
6
6
|
</head>
|
|
7
7
|
<body>
|
|
8
|
-
<script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),
|
|
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(`
|
|
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>
|
|
9
10
|
</body>
|
|
10
11
|
</html>
|
package/dist/browser/widget.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),
|
|
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(`
|
|
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-
|
|
2
|
+
import { n as parseCliOptions, r as printHelp, t as LocalRelayMcpServer } from "./mcpRelayServer-DkidP4o7.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.
|
|
19
|
+
serverVersion: "6.0.2"
|
|
20
20
|
});
|
|
21
21
|
try {
|
|
22
22
|
await relay.start();
|
|
@@ -39,7 +39,7 @@ let shuttingDown = false;
|
|
|
39
39
|
* Gracefully shuts down bridge and MCP server for process termination signals.
|
|
40
40
|
* Force-exits after 5 seconds if graceful shutdown does not complete, preventing hangs.
|
|
41
41
|
*/
|
|
42
|
-
const shutdown = async (signal) => {
|
|
42
|
+
const shutdown = async (signal, exitCode = 0) => {
|
|
43
43
|
if (shuttingDown) return;
|
|
44
44
|
shuttingDown = true;
|
|
45
45
|
if (parentCheckInterval) {
|
|
@@ -59,7 +59,7 @@ const shutdown = async (signal) => {
|
|
|
59
59
|
process.exit(1);
|
|
60
60
|
}
|
|
61
61
|
clearTimeout(forceExitTimer);
|
|
62
|
-
process.exit(
|
|
62
|
+
process.exit(exitCode);
|
|
63
63
|
};
|
|
64
64
|
process.stdin.on("end", () => {
|
|
65
65
|
shutdown("stdin-closed");
|
|
@@ -80,12 +80,12 @@ if (relay.bridge.mode === "server") process.stderr.write(`[webmcp-local-relay] s
|
|
|
80
80
|
else process.stderr.write(`[webmcp-local-relay] client mode: proxying through existing relay at ws://${options.host}:${relay.bridge.port}\n`);
|
|
81
81
|
process.on("uncaughtException", (err) => {
|
|
82
82
|
process.stderr.write(`[webmcp-local-relay] error: uncaught exception: ${err.stack ?? err.message}\n`);
|
|
83
|
-
shutdown("uncaughtException");
|
|
83
|
+
shutdown("uncaughtException", 1);
|
|
84
84
|
});
|
|
85
85
|
process.on("unhandledRejection", (reason) => {
|
|
86
86
|
const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
|
|
87
87
|
process.stderr.write(`[webmcp-local-relay] error: unhandled rejection: ${message}\n`);
|
|
88
|
-
shutdown("unhandledRejection");
|
|
88
|
+
shutdown("unhandledRejection", 1);
|
|
89
89
|
});
|
|
90
90
|
process.on("SIGINT", () => {
|
|
91
91
|
shutdown("SIGINT");
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { parseCliOptions, printHelp } from './cli-utils.js';\nimport { LocalRelayMcpServer } from './mcpRelayServer.js';\n\ndeclare const __RELAY_VERSION__: string;\n\nlet options: ReturnType<typeof parseCliOptions>;\ntry {\n options = parseCliOptions(process.argv.slice(2));\n} catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n process.exit(1);\n}\n\nif (options.help) {\n printHelp();\n process.exit(0);\n}\n\nif (options.allowedOrigins.includes('*')) {\n process.stderr.write(\n '[webmcp-local-relay] WARNING: accepting connections from ALL host page origins. Use --widget-origin to restrict.\\n'\n );\n}\n\nconst relay = new LocalRelayMcpServer({\n bridgeOptions: options,\n serverVersion: __RELAY_VERSION__,\n});\n\ntry {\n await relay.start();\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('EACCES')) {\n process.stderr.write(\n `[webmcp-local-relay] error: insufficient permissions to bind to ${options.host}:${options.port}.\\n`\n );\n } else {\n process.stderr.write(`[webmcp-local-relay] error: failed to start bridge server: ${message}\\n`);\n }\n process.exit(1);\n}\n\ntry {\n await relay.startStdio();\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[webmcp-local-relay] error: failed to start stdio transport: ${message}\\n`);\n process.exit(1);\n}\n\n// --- Auto-exit when the MCP client disconnects, preventing zombie processes ---\n\nlet parentCheckInterval: NodeJS.Timeout | undefined;\nlet shuttingDown = false;\n\n/**\n * Gracefully shuts down bridge and MCP server for process termination signals.\n * Force-exits after 5 seconds if graceful shutdown does not complete, preventing hangs.\n */\nconst shutdown = async (signal: string) => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n if (parentCheckInterval) {\n clearInterval(parentCheckInterval);\n parentCheckInterval = undefined;\n }\n\n // Safety net: force-exit if graceful shutdown hangs (e.g. SDK writing to a broken stdio pipe)\n const forceExitTimer = setTimeout(() => {\n process.stderr.write('[webmcp-local-relay] warn: graceful shutdown timed out, force exiting\\n');\n process.exit(1);\n }, 5_000);\n\n process.stderr.write(`[webmcp-local-relay] received ${signal}, shutting down\\n`);\n\n try {\n await relay.stop();\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error during shutdown: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n clearTimeout(forceExitTimer);\n process.exit(1);\n }\n clearTimeout(forceExitTimer);\n process.exit(
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { parseCliOptions, printHelp } from './cli-utils.js';\nimport { LocalRelayMcpServer } from './mcpRelayServer.js';\n\ndeclare const __RELAY_VERSION__: string;\n\nlet options: ReturnType<typeof parseCliOptions>;\ntry {\n options = parseCliOptions(process.argv.slice(2));\n} catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n process.exit(1);\n}\n\nif (options.help) {\n printHelp();\n process.exit(0);\n}\n\nif (options.allowedOrigins.includes('*')) {\n process.stderr.write(\n '[webmcp-local-relay] WARNING: accepting connections from ALL host page origins. Use --widget-origin to restrict.\\n'\n );\n}\n\nconst relay = new LocalRelayMcpServer({\n bridgeOptions: options,\n serverVersion: __RELAY_VERSION__,\n});\n\ntry {\n await relay.start();\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('EACCES')) {\n process.stderr.write(\n `[webmcp-local-relay] error: insufficient permissions to bind to ${options.host}:${options.port}.\\n`\n );\n } else {\n process.stderr.write(`[webmcp-local-relay] error: failed to start bridge server: ${message}\\n`);\n }\n process.exit(1);\n}\n\ntry {\n await relay.startStdio();\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[webmcp-local-relay] error: failed to start stdio transport: ${message}\\n`);\n process.exit(1);\n}\n\n// --- Auto-exit when the MCP client disconnects, preventing zombie processes ---\n\nlet parentCheckInterval: NodeJS.Timeout | undefined;\nlet shuttingDown = false;\n\n/**\n * Gracefully shuts down bridge and MCP server for process termination signals.\n * Force-exits after 5 seconds if graceful shutdown does not complete, preventing hangs.\n */\nconst shutdown = async (signal: string, exitCode = 0) => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n if (parentCheckInterval) {\n clearInterval(parentCheckInterval);\n parentCheckInterval = undefined;\n }\n\n // Safety net: force-exit if graceful shutdown hangs (e.g. SDK writing to a broken stdio pipe)\n const forceExitTimer = setTimeout(() => {\n process.stderr.write('[webmcp-local-relay] warn: graceful shutdown timed out, force exiting\\n');\n process.exit(1);\n }, 5_000);\n\n process.stderr.write(`[webmcp-local-relay] received ${signal}, shutting down\\n`);\n\n try {\n await relay.stop();\n } catch (err) {\n process.stderr.write(\n `[webmcp-local-relay] error during shutdown: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n clearTimeout(forceExitTimer);\n process.exit(1);\n }\n clearTimeout(forceExitTimer);\n process.exit(exitCode);\n};\n\nprocess.stdin.on('end', () => {\n void shutdown('stdin-closed');\n});\nprocess.stdin.on('error', () => {\n void shutdown('stdin-error');\n});\nprocess.stdout.on('error', () => {\n void shutdown('stdout-error');\n});\n\nif (process.platform !== 'win32') {\n parentCheckInterval = setInterval(() => {\n if (process.ppid === 1) {\n void shutdown('parent-exited');\n }\n }, 30_000);\n parentCheckInterval.unref();\n}\n\nif (relay.bridge.mode === 'server') {\n process.stderr.write(\n `[webmcp-local-relay] server mode: listening on ws://${options.host}:${relay.bridge.port} (allowed origins: ${options.allowedOrigins.join(', ')})\\n`\n );\n} else {\n process.stderr.write(\n `[webmcp-local-relay] client mode: proxying through existing relay at ws://${options.host}:${relay.bridge.port}\\n`\n );\n}\n\nprocess.on('uncaughtException', (err) => {\n process.stderr.write(\n `[webmcp-local-relay] error: uncaught exception: ${err.stack ?? err.message}\\n`\n );\n void shutdown('uncaughtException', 1);\n});\nprocess.on('unhandledRejection', (reason) => {\n const message = reason instanceof Error ? (reason.stack ?? reason.message) : String(reason);\n process.stderr.write(`[webmcp-local-relay] error: unhandled rejection: ${message}\\n`);\n void shutdown('unhandledRejection', 1);\n});\n\nprocess.on('SIGINT', () => {\n void shutdown('SIGINT');\n});\nprocess.on('SIGTERM', () => {\n void shutdown('SIGTERM');\n});\n"],"mappings":";;;;AAOA,IAAI;AACJ,IAAI;CACF,UAAU,gBAAgB,QAAQ,KAAK,MAAM,CAAC,CAAC;AACjD,SAAS,KAAK;CACZ,QAAQ,OAAO,MACb,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,GAClF;CACA,QAAQ,KAAK,CAAC;AAChB;AAEA,IAAI,QAAQ,MAAM;CAChB,UAAU;CACV,QAAQ,KAAK,CAAC;AAChB;AAEA,IAAI,QAAQ,eAAe,SAAS,GAAG,GACrC,QAAQ,OAAO,MACb,oHACF;AAGF,MAAM,QAAQ,IAAI,oBAAoB;CACpC,eAAe;CACf;AACF,CAAC;AAED,IAAI;CACF,MAAM,MAAM,MAAM;AACpB,SAAS,KAAK;CACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC/D,IAAI,QAAQ,SAAS,QAAQ,GAC3B,QAAQ,OAAO,MACb,mEAAmE,QAAQ,KAAK,GAAG,QAAQ,KAAK,IAClG;MAEA,QAAQ,OAAO,MAAM,8DAA8D,QAAQ,GAAG;CAEhG,QAAQ,KAAK,CAAC;AAChB;AAEA,IAAI;CACF,MAAM,MAAM,WAAW;AACzB,SAAS,KAAK;CACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC/D,QAAQ,OAAO,MAAM,gEAAgE,QAAQ,GAAG;CAChG,QAAQ,KAAK,CAAC;AAChB;AAIA,IAAI;AACJ,IAAI,eAAe;;;;;AAMnB,MAAM,WAAW,OAAO,QAAgB,WAAW,MAAM;CACvD,IAAI,cACF;CAEF,eAAe;CACf,IAAI,qBAAqB;EACvB,cAAc,mBAAmB;EACjC,sBAAsB;CACxB;CAGA,MAAM,iBAAiB,iBAAiB;EACtC,QAAQ,OAAO,MAAM,yEAAyE;EAC9F,QAAQ,KAAK,CAAC;CAChB,GAAG,GAAK;CAER,QAAQ,OAAO,MAAM,iCAAiC,OAAO,kBAAkB;CAE/E,IAAI;EACF,MAAM,MAAM,KAAK;CACnB,SAAS,KAAK;EACZ,QAAQ,OAAO,MACb,+CAA+C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,GAClG;EACA,aAAa,cAAc;EAC3B,QAAQ,KAAK,CAAC;CAChB;CACA,aAAa,cAAc;CAC3B,QAAQ,KAAK,QAAQ;AACvB;AAEA,QAAQ,MAAM,GAAG,aAAa;CAC5B,AAAK,SAAS,cAAc;AAC9B,CAAC;AACD,QAAQ,MAAM,GAAG,eAAe;CAC9B,AAAK,SAAS,aAAa;AAC7B,CAAC;AACD,QAAQ,OAAO,GAAG,eAAe;CAC/B,AAAK,SAAS,cAAc;AAC9B,CAAC;AAED,IAAI,QAAQ,aAAa,SAAS;CAChC,sBAAsB,kBAAkB;EACtC,IAAI,QAAQ,SAAS,GACnB,AAAK,SAAS,eAAe;CAEjC,GAAG,GAAM;CACT,oBAAoB,MAAM;AAC5B;AAEA,IAAI,MAAM,OAAO,SAAS,UACxB,QAAQ,OAAO,MACb,uDAAuD,QAAQ,KAAK,GAAG,MAAM,OAAO,KAAK,qBAAqB,QAAQ,eAAe,KAAK,IAAI,EAAE,IAClJ;KAEA,QAAQ,OAAO,MACb,6EAA6E,QAAQ,KAAK,GAAG,MAAM,OAAO,KAAK,GACjH;AAGF,QAAQ,GAAG,sBAAsB,QAAQ;CACvC,QAAQ,OAAO,MACb,mDAAmD,IAAI,SAAS,IAAI,QAAQ,GAC9E;CACA,AAAK,SAAS,qBAAqB,CAAC;AACtC,CAAC;AACD,QAAQ,GAAG,uBAAuB,WAAW;CAC3C,MAAM,UAAU,kBAAkB,QAAS,OAAO,SAAS,OAAO,UAAW,OAAO,MAAM;CAC1F,QAAQ,OAAO,MAAM,oDAAoD,QAAQ,GAAG;CACpF,AAAK,SAAS,sBAAsB,CAAC;AACvC,CAAC;AAED,QAAQ,GAAG,gBAAgB;CACzB,AAAK,SAAS,QAAQ;AACxB,CAAC;AACD,QAAQ,GAAG,iBAAiB;CAC1B,AAAK,SAAS,SAAS;AACzB,CAAC"}
|
package/dist/index.d.mts
CHANGED
|
@@ -85,11 +85,15 @@ declare const InboundToolSchema: z.ZodObject<{
|
|
|
85
85
|
}, z.core.$strip>>>;
|
|
86
86
|
title: z.ZodOptional<z.ZodString>;
|
|
87
87
|
name: z.ZodString;
|
|
88
|
-
inputSchema: z.ZodOptional<z.ZodObject<{
|
|
88
|
+
inputSchema: z.ZodOptional<z.ZodPipe<z.ZodPipe<z.ZodObject<{}, z.core.$loose>, z.ZodTransform<{
|
|
89
|
+
type: string;
|
|
90
|
+
}, {
|
|
91
|
+
[x: string]: unknown;
|
|
92
|
+
}>>, z.ZodObject<{
|
|
89
93
|
type: z.ZodLiteral<"object">;
|
|
90
94
|
properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<import("@modelcontextprotocol/server").JSONValue, import("@modelcontextprotocol/server").JSONValue, z.core.$ZodTypeInternals<import("@modelcontextprotocol/server").JSONValue, import("@modelcontextprotocol/server").JSONValue>>>>;
|
|
91
95
|
required: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
92
|
-
}, z.core.$catchall<z.ZodUnknown
|
|
96
|
+
}, z.core.$catchall<z.ZodUnknown>>>>;
|
|
93
97
|
}, z.core.$strip>;
|
|
94
98
|
/**
|
|
95
99
|
* Canonical normalized relay tool shape.
|
|
@@ -133,43 +137,7 @@ declare const BrowserToRelayMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
133
137
|
iconUrl: z.ZodOptional<z.ZodString>;
|
|
134
138
|
}, z.core.$strip>, z.ZodObject<{
|
|
135
139
|
type: z.ZodLiteral<"tools/list">;
|
|
136
|
-
tools: z.ZodPipe<z.ZodArray<z.
|
|
137
|
-
description: z.ZodOptional<z.ZodString>;
|
|
138
|
-
outputSchema: z.ZodOptional<z.ZodObject<{
|
|
139
|
-
$schema: z.ZodOptional<z.ZodString>;
|
|
140
|
-
}, z.core.$loose>>;
|
|
141
|
-
annotations: z.ZodOptional<z.ZodObject<{
|
|
142
|
-
title: z.ZodOptional<z.ZodString>;
|
|
143
|
-
readOnlyHint: z.ZodOptional<z.ZodBoolean>;
|
|
144
|
-
destructiveHint: z.ZodOptional<z.ZodBoolean>;
|
|
145
|
-
idempotentHint: z.ZodOptional<z.ZodBoolean>;
|
|
146
|
-
openWorldHint: z.ZodOptional<z.ZodBoolean>;
|
|
147
|
-
}, z.core.$strip>>;
|
|
148
|
-
execution: z.ZodOptional<z.ZodObject<{
|
|
149
|
-
taskSupport: z.ZodOptional<z.ZodEnum<{
|
|
150
|
-
optional: "optional";
|
|
151
|
-
required: "required";
|
|
152
|
-
forbidden: "forbidden";
|
|
153
|
-
}>>;
|
|
154
|
-
}, z.core.$strip>>;
|
|
155
|
-
_meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
156
|
-
icons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
157
|
-
src: z.ZodString;
|
|
158
|
-
mimeType: z.ZodOptional<z.ZodString>;
|
|
159
|
-
sizes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
160
|
-
theme: z.ZodOptional<z.ZodEnum<{
|
|
161
|
-
light: "light";
|
|
162
|
-
dark: "dark";
|
|
163
|
-
}>>;
|
|
164
|
-
}, z.core.$strip>>>;
|
|
165
|
-
title: z.ZodOptional<z.ZodString>;
|
|
166
|
-
name: z.ZodString;
|
|
167
|
-
inputSchema: z.ZodOptional<z.ZodObject<{
|
|
168
|
-
type: z.ZodLiteral<"object">;
|
|
169
|
-
properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<import("@modelcontextprotocol/server").JSONValue, import("@modelcontextprotocol/server").JSONValue, z.core.$ZodTypeInternals<import("@modelcontextprotocol/server").JSONValue, import("@modelcontextprotocol/server").JSONValue>>>>;
|
|
170
|
-
required: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
171
|
-
}, z.core.$catchall<z.ZodUnknown>>>;
|
|
172
|
-
}, z.core.$strip>>, z.ZodTransform<{
|
|
140
|
+
tools: z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<{
|
|
173
141
|
inputSchema: {
|
|
174
142
|
[x: string]: unknown;
|
|
175
143
|
type: "object";
|
|
@@ -200,77 +168,10 @@ declare const BrowserToRelayMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
200
168
|
theme?: "light" | "dark" | undefined;
|
|
201
169
|
}[] | undefined;
|
|
202
170
|
title?: string | undefined;
|
|
203
|
-
}[],
|
|
204
|
-
name: string;
|
|
205
|
-
description?: string | undefined;
|
|
206
|
-
outputSchema?: {
|
|
207
|
-
[x: string]: unknown;
|
|
208
|
-
$schema?: string | undefined;
|
|
209
|
-
} | undefined;
|
|
210
|
-
annotations?: {
|
|
211
|
-
title?: string | undefined;
|
|
212
|
-
readOnlyHint?: boolean | undefined;
|
|
213
|
-
destructiveHint?: boolean | undefined;
|
|
214
|
-
idempotentHint?: boolean | undefined;
|
|
215
|
-
openWorldHint?: boolean | undefined;
|
|
216
|
-
} | undefined;
|
|
217
|
-
execution?: {
|
|
218
|
-
taskSupport?: "optional" | "required" | "forbidden" | undefined;
|
|
219
|
-
} | undefined;
|
|
220
|
-
_meta?: Record<string, unknown> | undefined;
|
|
221
|
-
icons?: {
|
|
222
|
-
src: string;
|
|
223
|
-
mimeType?: string | undefined;
|
|
224
|
-
sizes?: string[] | undefined;
|
|
225
|
-
theme?: "light" | "dark" | undefined;
|
|
226
|
-
}[] | undefined;
|
|
227
|
-
title?: string | undefined;
|
|
228
|
-
inputSchema?: {
|
|
229
|
-
[x: string]: unknown;
|
|
230
|
-
type: "object";
|
|
231
|
-
properties?: Record<string, import("@modelcontextprotocol/server").JSONValue> | undefined;
|
|
232
|
-
required?: string[] | undefined;
|
|
233
|
-
} | undefined;
|
|
234
|
-
}[]>>;
|
|
171
|
+
}[], unknown[]>>;
|
|
235
172
|
}, z.core.$strip>, z.ZodObject<{
|
|
236
173
|
type: z.ZodLiteral<"tools/changed">;
|
|
237
|
-
tools: z.ZodPipe<z.ZodArray<z.
|
|
238
|
-
description: z.ZodOptional<z.ZodString>;
|
|
239
|
-
outputSchema: z.ZodOptional<z.ZodObject<{
|
|
240
|
-
$schema: z.ZodOptional<z.ZodString>;
|
|
241
|
-
}, z.core.$loose>>;
|
|
242
|
-
annotations: z.ZodOptional<z.ZodObject<{
|
|
243
|
-
title: z.ZodOptional<z.ZodString>;
|
|
244
|
-
readOnlyHint: z.ZodOptional<z.ZodBoolean>;
|
|
245
|
-
destructiveHint: z.ZodOptional<z.ZodBoolean>;
|
|
246
|
-
idempotentHint: z.ZodOptional<z.ZodBoolean>;
|
|
247
|
-
openWorldHint: z.ZodOptional<z.ZodBoolean>;
|
|
248
|
-
}, z.core.$strip>>;
|
|
249
|
-
execution: z.ZodOptional<z.ZodObject<{
|
|
250
|
-
taskSupport: z.ZodOptional<z.ZodEnum<{
|
|
251
|
-
optional: "optional";
|
|
252
|
-
required: "required";
|
|
253
|
-
forbidden: "forbidden";
|
|
254
|
-
}>>;
|
|
255
|
-
}, z.core.$strip>>;
|
|
256
|
-
_meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
257
|
-
icons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
258
|
-
src: z.ZodString;
|
|
259
|
-
mimeType: z.ZodOptional<z.ZodString>;
|
|
260
|
-
sizes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
261
|
-
theme: z.ZodOptional<z.ZodEnum<{
|
|
262
|
-
light: "light";
|
|
263
|
-
dark: "dark";
|
|
264
|
-
}>>;
|
|
265
|
-
}, z.core.$strip>>>;
|
|
266
|
-
title: z.ZodOptional<z.ZodString>;
|
|
267
|
-
name: z.ZodString;
|
|
268
|
-
inputSchema: z.ZodOptional<z.ZodObject<{
|
|
269
|
-
type: z.ZodLiteral<"object">;
|
|
270
|
-
properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<import("@modelcontextprotocol/server").JSONValue, import("@modelcontextprotocol/server").JSONValue, z.core.$ZodTypeInternals<import("@modelcontextprotocol/server").JSONValue, import("@modelcontextprotocol/server").JSONValue>>>>;
|
|
271
|
-
required: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
272
|
-
}, z.core.$catchall<z.ZodUnknown>>>;
|
|
273
|
-
}, z.core.$strip>>, z.ZodTransform<{
|
|
174
|
+
tools: z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<{
|
|
274
175
|
inputSchema: {
|
|
275
176
|
[x: string]: unknown;
|
|
276
177
|
type: "object";
|
|
@@ -301,38 +202,7 @@ declare const BrowserToRelayMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
301
202
|
theme?: "light" | "dark" | undefined;
|
|
302
203
|
}[] | undefined;
|
|
303
204
|
title?: string | undefined;
|
|
304
|
-
}[],
|
|
305
|
-
name: string;
|
|
306
|
-
description?: string | undefined;
|
|
307
|
-
outputSchema?: {
|
|
308
|
-
[x: string]: unknown;
|
|
309
|
-
$schema?: string | undefined;
|
|
310
|
-
} | undefined;
|
|
311
|
-
annotations?: {
|
|
312
|
-
title?: string | undefined;
|
|
313
|
-
readOnlyHint?: boolean | undefined;
|
|
314
|
-
destructiveHint?: boolean | undefined;
|
|
315
|
-
idempotentHint?: boolean | undefined;
|
|
316
|
-
openWorldHint?: boolean | undefined;
|
|
317
|
-
} | undefined;
|
|
318
|
-
execution?: {
|
|
319
|
-
taskSupport?: "optional" | "required" | "forbidden" | undefined;
|
|
320
|
-
} | undefined;
|
|
321
|
-
_meta?: Record<string, unknown> | undefined;
|
|
322
|
-
icons?: {
|
|
323
|
-
src: string;
|
|
324
|
-
mimeType?: string | undefined;
|
|
325
|
-
sizes?: string[] | undefined;
|
|
326
|
-
theme?: "light" | "dark" | undefined;
|
|
327
|
-
}[] | undefined;
|
|
328
|
-
title?: string | undefined;
|
|
329
|
-
inputSchema?: {
|
|
330
|
-
[x: string]: unknown;
|
|
331
|
-
type: "object";
|
|
332
|
-
properties?: Record<string, import("@modelcontextprotocol/server").JSONValue> | undefined;
|
|
333
|
-
required?: string[] | undefined;
|
|
334
|
-
} | undefined;
|
|
335
|
-
}[]>>;
|
|
205
|
+
}[], unknown[]>>;
|
|
336
206
|
}, z.core.$strip>, z.ZodObject<{
|
|
337
207
|
type: z.ZodLiteral<"result">;
|
|
338
208
|
callId: z.ZodString;
|
|
@@ -427,6 +297,9 @@ declare const RelayToBrowserMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
427
297
|
callId: z.ZodString;
|
|
428
298
|
toolName: z.ZodString;
|
|
429
299
|
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
300
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
301
|
+
type: z.ZodLiteral<"cancel">;
|
|
302
|
+
callId: z.ZodString;
|
|
430
303
|
}, z.core.$strip>, z.ZodObject<{
|
|
431
304
|
type: z.ZodLiteral<"ping">;
|
|
432
305
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -466,6 +339,9 @@ declare const RelayClientToServerMessageSchema: z.ZodDiscriminatedUnion<[z.ZodOb
|
|
|
466
339
|
callId: z.ZodString;
|
|
467
340
|
toolName: z.ZodString;
|
|
468
341
|
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
342
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
343
|
+
type: z.ZodLiteral<"relay/cancel">;
|
|
344
|
+
callId: z.ZodString;
|
|
469
345
|
}, z.core.$strip>], "type">;
|
|
470
346
|
/**
|
|
471
347
|
* Relay client to server message payload.
|
|
@@ -849,8 +725,8 @@ interface RelayBridgeServerOptions {
|
|
|
849
725
|
*/
|
|
850
726
|
portRangeEnd?: number;
|
|
851
727
|
/**
|
|
852
|
-
*
|
|
853
|
-
*
|
|
728
|
+
* @deprecated Ignored. The relay no longer caches its port, so it always joins a relay
|
|
729
|
+
* that owns the preferred port instead of starting a second one elsewhere.
|
|
854
730
|
*/
|
|
855
731
|
persistPath?: string;
|
|
856
732
|
/**
|
|
@@ -908,7 +784,6 @@ declare class RelayBridgeServer extends EventEmitter {
|
|
|
908
784
|
private desiredPort;
|
|
909
785
|
private readonly portExplicitlySet;
|
|
910
786
|
private readonly portRangeEnd;
|
|
911
|
-
private readonly persistPath;
|
|
912
787
|
private readonly allowedOrigins;
|
|
913
788
|
private readonly maxPayloadBytes;
|
|
914
789
|
private readonly invokeTimeoutMs;
|
|
@@ -922,6 +797,7 @@ declare class RelayBridgeServer extends EventEmitter {
|
|
|
922
797
|
private readonly pendingInvocations;
|
|
923
798
|
private readonly browserClientConnectionIds;
|
|
924
799
|
private readonly relayClientConnectionIds;
|
|
800
|
+
private readonly relayClientInvocations;
|
|
925
801
|
private readonly heartbeatIntervalByConnectionId;
|
|
926
802
|
private readonly lastPongByConnectionId;
|
|
927
803
|
private readonly onStateChangedPushRelay;
|
|
@@ -933,8 +809,6 @@ declare class RelayBridgeServer extends EventEmitter {
|
|
|
933
809
|
* Maximum delay for relay-to-relay reconnection backoff in client mode.
|
|
934
810
|
*/
|
|
935
811
|
private readonly clientMaxReconnectDelay;
|
|
936
|
-
private readonly clientMaxReconnectAttempts;
|
|
937
|
-
private clientReconnectAttempts;
|
|
938
812
|
private readonly clientPendingInvocations;
|
|
939
813
|
private clientTools;
|
|
940
814
|
private clientSources;
|
|
@@ -977,7 +851,6 @@ declare class RelayBridgeServer extends EventEmitter {
|
|
|
977
851
|
start(): Promise<void>;
|
|
978
852
|
private startUsingPortStrategy;
|
|
979
853
|
private isAddressInUseError;
|
|
980
|
-
private persistSelectedPort;
|
|
981
854
|
private tryAttachToExistingRelay;
|
|
982
855
|
/**
|
|
983
856
|
* Stops all relay resources and rejects any pending invocations.
|
|
@@ -994,7 +867,8 @@ declare class RelayBridgeServer extends EventEmitter {
|
|
|
994
867
|
*/
|
|
995
868
|
invokeTool(toolName: string, args: RelayInvokeArgs, options?: {
|
|
996
869
|
sourceId?: string;
|
|
997
|
-
requestTabId?: string;
|
|
870
|
+
requestTabId?: string; /** Cancels the invocation and forwards cancellation to the browser runtime. */
|
|
871
|
+
signal?: AbortSignal;
|
|
998
872
|
}): Promise<RelayCallToolResult>;
|
|
999
873
|
private startAsServer;
|
|
1000
874
|
private buildServerHello;
|
|
@@ -1039,6 +913,7 @@ declare class RelayBridgeServer extends EventEmitter {
|
|
|
1039
913
|
*/
|
|
1040
914
|
private reconnectWithModePromotion;
|
|
1041
915
|
private invokeToolViaRelay;
|
|
916
|
+
private requestTool;
|
|
1042
917
|
/**
|
|
1043
918
|
* Builds the complete relay response payload including tools and source metadata.
|
|
1044
919
|
*/
|
|
@@ -1123,6 +998,8 @@ declare class LocalRelayMcpServer {
|
|
|
1123
998
|
private readonly mcpServer;
|
|
1124
999
|
private readonly launchBrowser;
|
|
1125
1000
|
private readonly dynamicTools;
|
|
1001
|
+
/** Page tools that share a name with a relay tool, already reported once. */
|
|
1002
|
+
private readonly shadowedToolNames;
|
|
1126
1003
|
private syncDebounceTimer;
|
|
1127
1004
|
private connected;
|
|
1128
1005
|
/**
|
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-
|
|
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";
|
|
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 };
|