@mcp-b/webmcp-local-relay 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -16
- package/dist/browser/embed.js +1 -1
- package/dist/browser/widget.html +1 -1
- package/dist/browser/widget.js +1 -1
- package/dist/index.d.mts +4 -4
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -84,6 +84,16 @@ Custom relay port:
|
|
|
84
84
|
></script>
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
Increase the per-request timeout (default `60000` ms) for tools that chain
|
|
88
|
+
several slow API calls and might exceed one minute:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<script
|
|
92
|
+
src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@latest/dist/browser/embed.js"
|
|
93
|
+
data-request-timeout="120000"
|
|
94
|
+
></script>
|
|
95
|
+
```
|
|
96
|
+
|
|
87
97
|
Tool registration references:
|
|
88
98
|
|
|
89
99
|
- [`@mcp-b/global` quick start and `registerTool`](../global/README.md)
|
|
@@ -229,12 +239,13 @@ For Chromium/Chrome Canary native preview testing:
|
|
|
229
239
|
|
|
230
240
|
### Troubleshooting
|
|
231
241
|
|
|
232
|
-
| Problem
|
|
233
|
-
|
|
|
234
|
-
| `No sources connected`
|
|
235
|
-
| `No tools listed`
|
|
236
|
-
| `Tool not found`
|
|
237
|
-
| Connection blocked
|
|
242
|
+
| Problem | Fix |
|
|
243
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
244
|
+
| `No sources connected` | Ensure the page loaded `embed.js` and the relay process is running |
|
|
245
|
+
| `No tools listed` | Ensure tools are registered on the page's WebMCP runtime. If tools register after load, confirm your runtime emits tool-change notifications (`toolschanged` or `registerToolsChangedCallback`) |
|
|
246
|
+
| `Tool not found` | Tab reloaded or disconnected — call `webmcp_list_tools` again to refresh |
|
|
247
|
+
| Connection blocked | Verify `--widget-origin` matches your host page's origin (e.g., `https://myapp.com`), and relay port matches `data-relay-port` |
|
|
248
|
+
| `Host response timeout:` | The host page took longer than the per-request timeout (default 60s) to respond. Increase via `data-request-timeout="<ms>"` on the embed script tag |
|
|
238
249
|
|
|
239
250
|
---
|
|
240
251
|
|
|
@@ -244,18 +255,24 @@ For Chromium/Chrome Canary native preview testing:
|
|
|
244
255
|
|
|
245
256
|
```text
|
|
246
257
|
src/
|
|
247
|
-
├── cli.ts
|
|
248
|
-
├── cli-utils.ts
|
|
249
|
-
├── mcpRelayServer.ts
|
|
250
|
-
├── bridgeServer.ts
|
|
251
|
-
├── registry.ts
|
|
252
|
-
├── naming.ts
|
|
253
|
-
├── schemas.ts
|
|
254
|
-
├── browser/embed.
|
|
255
|
-
├── browser/widget.
|
|
256
|
-
|
|
258
|
+
├── cli.ts CLI entry point
|
|
259
|
+
├── cli-utils.ts CLI argument parsing
|
|
260
|
+
├── mcpRelayServer.ts MCP server (stdio + dynamic tool sync)
|
|
261
|
+
├── bridgeServer.ts WebSocket relay server
|
|
262
|
+
├── registry.ts Multi-source aggregation and deduplication
|
|
263
|
+
├── naming.ts Tool name sanitization and namespacing
|
|
264
|
+
├── schemas.ts Browser <-> relay protocol schemas
|
|
265
|
+
├── browser/embed.ts Script-tag loader for website owners
|
|
266
|
+
├── browser/widget.ts Widget IIFE entry point (calls startWidgetRuntime)
|
|
267
|
+
├── browser/widgetRuntime.ts Hidden iframe bridge runtime
|
|
268
|
+
├── browser/shared.ts Shared browser-side utilities
|
|
269
|
+
└── index.ts Public API exports
|
|
257
270
|
```
|
|
258
271
|
|
|
272
|
+
`dist/browser/widget.html` is generated at build time by
|
|
273
|
+
`scripts/build-widget-html.js`, which wraps the bundled `widget.js` (compiled
|
|
274
|
+
from `widget.ts` + `widgetRuntime.ts`) into a minimal HTML shell.
|
|
275
|
+
|
|
259
276
|
### Build and Test
|
|
260
277
|
|
|
261
278
|
From repository root:
|
package/dist/browser/embed.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){function e(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}let t=`[data-webmcp-relay]`,n=`__webmcp_relay_tab_id`,r=null,i;function a(){return document.currentScript instanceof HTMLScriptElement?document.currentScript:null}let o=a(),s=o?o.hasAttribute(`data-debug`):!1;function c(...e){s&&console.warn(`[webmcp-relay-embed]`,...e)}function l(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function u(){try{let e=sessionStorage.getItem(n);if(e)return e}catch(e){c(`sessionStorage read failed, tab ID will not persist:`,e)}let e=l();try{sessionStorage.setItem(n,e)}catch(e){c(`sessionStorage write failed:`,e)}return e}function d(e){if(e?.src)try{return new URL(`widget.html`,e.src).href}catch(e){c(`Failed to resolve widget URL from script src, falling back to CDN:`,e)}else c(`Script element has no src attribute, falling back to CDN widget URL.`);return`https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay/dist/browser/widget.html`}function f(e){let t=d(e),n=e?.getAttribute(`data-relay-id`)||void 0,r=e?.getAttribute(`data-relay-workspace`)||void 0;return{autoConnect:e?.getAttribute(`data-auto-connect`)!==`false`,relayHost:e?.getAttribute(`data-relay-host`)||`127.0.0.1`,relayPort:e?.getAttribute(`data-relay-port`)||`9333`,...n?{relayId:n}:{},...r?{relayWorkspace:r}:{},tabId:u(),widgetUrl:t,widgetOrigin:new URL(t).origin}}function p(t){if(typeof t!=`string`||t.length===0)return{type:`object`,properties:{}};try{let n=JSON.parse(t);return e(n)?n:{type:`object`,properties:{}}}catch(e){return c(`Tool inputSchema is not valid JSON:`,typeof t==`string`?t.slice(0,200):t,e),{type:`object`,properties:{}}}}function m(t){return e(t)?t:(t!=null&&c(`Tool invocation args must be an object, got`,typeof t),{})}function h(e){return{name:e.name,description:e.description,inputSchema:e.inputSchema}}function g(e){return{name:e.name,description:e.description,inputSchema:p(e.inputSchema)}}function _(){let e=navigator.modelContext;if(e&&typeof e.listTools==`function`&&typeof e.callTool==`function`)return e}function v(){let t=_();if(t)return{listTools(){return t.listTools().map(h)},invoke(e,n){return t.callTool({name:e,arguments:n})}};let n=navigator.modelContextTesting;return n&&typeof n.listTools==`function`&&typeof n.executeTool==`function`?{listTools(){return n.listTools().map(g)},async invoke(t,r){let i=await n.executeTool(t,JSON.stringify(r));if(i===null)return{isError:!0,content:[{type:`text`,text:`Tool execution interrupted by navigation`}]};let a;try{a=JSON.parse(i)}catch{throw Error(`Testing tool returned invalid JSON: ${String(i).slice(0,200)}`)}if(!e(a))throw Error(`Testing tool response was not an object`);return a}}:(c(`No WebMCP runtime found (navigator.modelContext or navigator.modelContextTesting).`),null)}let y=!1;function b(){y||!r||(y=!0,setTimeout(()=>{if(y=!1,!r)return;let e=v();(e?Promise.resolve(e.listTools()):Promise.resolve([])).then(e=>{r&&r.postMessage({type:`webmcp.tools.changed`,tools:Array.isArray(e)?e:[]},i.widgetOrigin)}).catch(e=>{c(`Failed to push tool changes:`,e)})},0))}function x(){let e=_();if(e)try{return e.addEventListener(`toolchange`,b),!0}catch(e){c(`addEventListener threw:`,e)}let t=navigator.modelContextTesting;if(t&&typeof t.registerToolsChangedCallback==`function`)try{return t.registerToolsChangedCallback(b),!0}catch(e){c(`Failed to subscribe via registerToolsChangedCallback:`,e)}return!1}function S(){if(x())return;let e=0,t=100,n=()=>{setTimeout(()=>{if(e++,!x()){if(e>=40){c(`Could not subscribe to tool changes after 40 retries. Dynamic tool updates will not be relayed.`);return}t=Math.min(Math.round(t*1.5),1e3),n()}},t)};n()}function C(e,t,n){!e||typeof e!=`object`||!(`postMessage`in e)||e.postMessage(n,t)}function w(t){return!e(t)||typeof t.requestId!=`string`||typeof t.type!=`string`?null:{requestId:t.requestId,type:t.type,toolName:t.toolName,args:t.args}}function T(e,t){let n=v();(n?Promise.resolve(n.listTools()):Promise.resolve([])).then(n=>{C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:Array.isArray(n)?n:[]})}).catch(n=>{c(`Failed to list tools:`,n),C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:[],error:`Failed to list tools: ${n instanceof Error?n.message:String(n)}`})})}let E=!1;function D(t,n){if(E)return;let r=_();if(!r||typeof r.elicitInput!=`function`){c(`Elicitation bridge not installed: elicitInput not available on modelContext`);return}r.elicitInput=(r,i)=>{let a=k();return new Promise(i=>{let o=()=>{window.removeEventListener(`message`,l),clearTimeout(s)},s=setTimeout(()=>{o(),c(`Elicitation request timed out after 60s`),i({action:`decline`,content:null})},6e4),l=t=>{if(t.origin!==n)return;let r=t.data;!e(r)||r.type!==`webmcp.elicitation.response`||r.callId!==a||(o(),i(e(r.result)?r.result:{action:`decline`,content:null}))};window.addEventListener(`message`,l),t.postMessage({type:`webmcp.elicitation.request`,callId:a,params:r},n)})},E=!0,c(`Elicitation bridge installed`)}let O=0;function k(){return O+=1,`elicit_${String(Date.now())}_${String(O)}`}function A(t,n){let r=v();if(!r){C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:`No WebMCP runtime found on this page`});return}n.source&&D(n.source,n.origin),Promise.resolve(r.invoke(String(t.toolName??``),m(t.args))).then(r=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.response`,requestId:t.requestId,result:e(r)?r:{}})}).catch(e=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:String(e instanceof Error?e.message:e)})})}async function j(e){if(document.querySelector(t))return;let n=new URLSearchParams;n.set(`tabId`,e.tabId),n.set(`hostOrigin`,window.location.origin);let a=new URL(window.location.href);a.search=``,a.hash=``,n.set(`hostUrl`,a.href),n.set(`hostTitle`,document.title||``),n.set(`relayHost`,e.relayHost),n.set(`relayPort`,e.relayPort),n.set(`autoConnect`,e.autoConnect?`true`:`false`),e.relayId&&n.set(`relayId`,e.relayId),e.relayWorkspace&&n.set(`relayWorkspace`,e.relayWorkspace);let o=null;try{let t=await fetch(e.widgetUrl);if(!t.ok)console.warn(`[webmcp-relay-embed] Widget HTML fetch returned ${String(t.status)}; falling back to direct iframe src.`);else if(t.ok){let e=await t.text(),r=`<script>window.__WEBMCP_RELAY_CONFIG=${JSON.stringify(Object.fromEntries(n))};<\/script>`,a=new Blob([e.replace(`</head>`,`${r}</head>`)],{type:`text/html`});o=URL.createObjectURL(a),i.widgetOrigin=window.location.origin}}catch(e){c(`Failed to fetch widget HTML for blob URL:`,e)}let s=document.createElement(`iframe`);s.src=o??`${e.widgetUrl}?${n.toString()}`,s.style.display=`none`,s.setAttribute(`aria-hidden`,`true`),s.setAttribute(`data-webmcp-relay`,`1`),s.setAttribute(`allow`,`loopback-network; local-network; local-network-access`),document.body.appendChild(s),r=s.contentWindow,s.addEventListener(`load`,()=>{r=s.contentWindow,o&&URL.revokeObjectURL(o)}),s.addEventListener(`error`,()=>{console.error(`[webmcp-relay-embed] Failed to load relay widget iframe from:`,s.src,`-- WebMCP tools will NOT be relayed. Check network connectivity and widget URL.`),o&&URL.revokeObjectURL(o)})}if(!document.querySelector(t)){try{i=f(o)}catch(e){throw console.error(`[webmcp-relay-embed] Failed to initialize relay configuration:`,e),e}window.addEventListener(`message`,t=>{if(t.origin!==i.widgetOrigin||!r||t.source!==r)return;let n=t.data;if(e(n)&&n.type===`webmcp.reload`){window.location.reload();return}let a=w(t.data);if(a){if(a.type===`webmcp.tools.list.request`){T(a,t);return}a.type===`webmcp.tools.invoke.request`&&A(a,t)}});let t=()=>{j(i).catch(e=>{console.error(`[webmcp-relay-embed] Failed to inject relay widget:`,e)})};document.body?t():document.addEventListener(`DOMContentLoaded`,t,{once:!0}),S()}})();
|
|
1
|
+
(function(){function e(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}let t=`[data-webmcp-relay]`,n=`__webmcp_relay_tab_id`,r=null,i;function a(){return document.currentScript instanceof HTMLScriptElement?document.currentScript:null}let o=a(),s=o?o.hasAttribute(`data-debug`):!1;function c(...e){s&&console.warn(`[webmcp-relay-embed]`,...e)}function l(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function u(){try{let e=sessionStorage.getItem(n);if(e)return e}catch(e){c(`sessionStorage read failed, tab ID will not persist:`,e)}let e=l();try{sessionStorage.setItem(n,e)}catch(e){c(`sessionStorage write failed:`,e)}return e}function d(e){if(e?.src)try{return new URL(`widget.html`,e.src).href}catch(e){c(`Failed to resolve widget URL from script src, falling back to CDN:`,e)}else c(`Script element has no src attribute, falling back to CDN widget URL.`);return`https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay/dist/browser/widget.html`}function f(e){let t=d(e),n=e?.getAttribute(`data-relay-id`)||void 0,r=e?.getAttribute(`data-relay-workspace`)||void 0,i=e?.getAttribute(`data-request-timeout`)||void 0;return{autoConnect:e?.getAttribute(`data-auto-connect`)!==`false`,relayHost:e?.getAttribute(`data-relay-host`)||`127.0.0.1`,relayPort:e?.getAttribute(`data-relay-port`)||`9333`,...n?{relayId:n}:{},...r?{relayWorkspace:r}:{},...i?{requestTimeout:i}:{},tabId:u(),widgetUrl:t,widgetOrigin:new URL(t).origin}}function p(t){if(typeof t!=`string`||t.length===0)return{type:`object`,properties:{}};try{let n=JSON.parse(t);return e(n)?n:{type:`object`,properties:{}}}catch(e){return c(`Tool inputSchema is not valid JSON:`,typeof t==`string`?t.slice(0,200):t,e),{type:`object`,properties:{}}}}function m(t){return e(t)?t:(t!=null&&c(`Tool invocation args must be an object, got`,typeof t),{})}function h(e){return{name:e.name,description:e.description,inputSchema:e.inputSchema}}function g(e){return{name:e.name,description:e.description,inputSchema:p(e.inputSchema)}}function _(){let e=navigator.modelContext;if(e&&typeof e.listTools==`function`&&typeof e.callTool==`function`)return e}function v(){let t=_();if(t)return{listTools(){return t.listTools().map(h)},invoke(e,n){return t.callTool({name:e,arguments:n})}};let n=navigator.modelContextTesting;return n&&typeof n.listTools==`function`&&typeof n.executeTool==`function`?{listTools(){return n.listTools().map(g)},async invoke(t,r){let i=await n.executeTool(t,JSON.stringify(r));if(i===null)return{isError:!0,content:[{type:`text`,text:`Tool execution interrupted by navigation`}]};let a;try{a=JSON.parse(i)}catch{throw Error(`Testing tool returned invalid JSON: ${String(i).slice(0,200)}`)}if(!e(a))throw Error(`Testing tool response was not an object`);return a}}:(c(`No WebMCP runtime found (navigator.modelContext or navigator.modelContextTesting).`),null)}let y=!1;function b(){y||!r||(y=!0,setTimeout(()=>{if(y=!1,!r)return;let e=v();(e?Promise.resolve(e.listTools()):Promise.resolve([])).then(e=>{r&&r.postMessage({type:`webmcp.tools.changed`,tools:Array.isArray(e)?e:[]},i.widgetOrigin)}).catch(e=>{c(`Failed to push tool changes:`,e)})},0))}function x(){let e=_();if(e)try{return e.addEventListener(`toolchange`,b),!0}catch(e){c(`addEventListener threw:`,e)}let t=navigator.modelContextTesting;if(t&&typeof t.registerToolsChangedCallback==`function`)try{return t.registerToolsChangedCallback(b),!0}catch(e){c(`Failed to subscribe via registerToolsChangedCallback:`,e)}return!1}function S(){if(x())return;let e=0,t=100,n=()=>{setTimeout(()=>{if(e++,!x()){if(e>=40){c(`Could not subscribe to tool changes after 40 retries. Dynamic tool updates will not be relayed.`);return}t=Math.min(Math.round(t*1.5),1e3),n()}},t)};n()}function C(e,t,n){!e||typeof e!=`object`||!(`postMessage`in e)||e.postMessage(n,t)}function w(t){return!e(t)||typeof t.requestId!=`string`||typeof t.type!=`string`?null:{requestId:t.requestId,type:t.type,toolName:t.toolName,args:t.args}}function T(e,t){let n=v();(n?Promise.resolve(n.listTools()):Promise.resolve([])).then(n=>{C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:Array.isArray(n)?n:[]})}).catch(n=>{c(`Failed to list tools:`,n),C(t.source,t.origin,{type:`webmcp.tools.list.response`,requestId:e.requestId,tools:[],error:`Failed to list tools: ${n instanceof Error?n.message:String(n)}`})})}let E=!1;function D(t,n){if(E)return;let r=_();if(!r||typeof r.elicitInput!=`function`){c(`Elicitation bridge not installed: elicitInput not available on modelContext`);return}r.elicitInput=(r,i)=>{let a=k();return new Promise(i=>{let o=()=>{window.removeEventListener(`message`,l),clearTimeout(s)},s=setTimeout(()=>{o(),c(`Elicitation request timed out after 60s`),i({action:`decline`,content:null})},6e4),l=t=>{if(t.origin!==n)return;let r=t.data;!e(r)||r.type!==`webmcp.elicitation.response`||r.callId!==a||(o(),i(e(r.result)?r.result:{action:`decline`,content:null}))};window.addEventListener(`message`,l),t.postMessage({type:`webmcp.elicitation.request`,callId:a,params:r},n)})},E=!0,c(`Elicitation bridge installed`)}let O=0;function k(){return O+=1,`elicit_${String(Date.now())}_${String(O)}`}function A(t,n){let r=v();if(!r){C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:`No WebMCP runtime found on this page`});return}n.source&&D(n.source,n.origin),Promise.resolve(r.invoke(String(t.toolName??``),m(t.args))).then(r=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.response`,requestId:t.requestId,result:e(r)?r:{}})}).catch(e=>{C(n.source,n.origin,{type:`webmcp.tools.invoke.error`,requestId:t.requestId,error:String(e instanceof Error?e.message:e)})})}async function j(e){if(document.querySelector(t))return;let n=new URLSearchParams;n.set(`tabId`,e.tabId),n.set(`hostOrigin`,window.location.origin);let a=new URL(window.location.href);a.search=``,a.hash=``,n.set(`hostUrl`,a.href),n.set(`hostTitle`,document.title||``),n.set(`relayHost`,e.relayHost),n.set(`relayPort`,e.relayPort),n.set(`autoConnect`,e.autoConnect?`true`:`false`),e.relayId&&n.set(`relayId`,e.relayId),e.relayWorkspace&&n.set(`relayWorkspace`,e.relayWorkspace),e.requestTimeout&&n.set(`requestTimeout`,e.requestTimeout);let o=null;try{let t=await fetch(e.widgetUrl);if(!t.ok)console.warn(`[webmcp-relay-embed] Widget HTML fetch returned ${String(t.status)}; falling back to direct iframe src.`);else if(t.ok){let e=await t.text(),r=`<script>window.__WEBMCP_RELAY_CONFIG=${JSON.stringify(Object.fromEntries(n))};<\/script>`,a=new Blob([e.replace(`</head>`,`${r}</head>`)],{type:`text/html`});o=URL.createObjectURL(a),i.widgetOrigin=window.location.origin}}catch(e){c(`Failed to fetch widget HTML for blob URL:`,e)}let s=document.createElement(`iframe`);s.src=o??`${e.widgetUrl}?${n.toString()}`,s.style.display=`none`,s.setAttribute(`aria-hidden`,`true`),s.setAttribute(`data-webmcp-relay`,`1`),s.setAttribute(`allow`,`loopback-network; local-network; local-network-access`),document.body.appendChild(s),r=s.contentWindow,s.addEventListener(`load`,()=>{r=s.contentWindow,o&&URL.revokeObjectURL(o)}),s.addEventListener(`error`,()=>{console.error(`[webmcp-relay-embed] Failed to load relay widget iframe from:`,s.src,`-- WebMCP tools will NOT be relayed. Check network connectivity and widget URL.`),o&&URL.revokeObjectURL(o)})}if(!document.querySelector(t)){try{i=f(o)}catch(e){throw console.error(`[webmcp-relay-embed] Failed to initialize relay configuration:`,e),e}window.addEventListener(`message`,t=>{if(t.origin!==i.widgetOrigin||!r||t.source!==r)return;let n=t.data;if(e(n)&&n.type===`webmcp.reload`){window.location.reload();return}let a=w(t.data);if(a){if(a.type===`webmcp.tools.list.request`){T(a,t);return}a.type===`webmcp.tools.invoke.request`&&A(a,t)}});let t=()=>{j(i).catch(e=>{console.error(`[webmcp-relay-embed] Failed to inject relay widget:`,e)})};document.body?t():document.addEventListener(`DOMContentLoaded`,t,{once:!0}),S()}})();
|
package/dist/browser/widget.html
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
<title>WebMCP Relay Widget</title>
|
|
6
6
|
</head>
|
|
7
7
|
<body>
|
|
8
|
-
<script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return t.get(e)??i?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`hostUrl`)||o,c=a(`hostTitle`)||``,l=a(`tabId`)||r(),u=a(`relayHost`)||`127.0.0.1`,d=a(`relayPort`),f=d&&d.length>0?Number.parseInt(d,10):9333,p=a(`autoConnect`)!==`false`,m=a(`relayId`)||void 0,h=a(`relayWorkspace`)||void 0;return n(u)?!Number.isInteger(f)||f<1||f>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,d),null):{autoConnect:p,hostOrigin:o,hostTitle:c,hostUrl:s,relayHostHint:u,relayPortHint:f,...m?{relayId:m}:{},...h?{relayWorkspace:h}:{},tabId:l}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,u),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}b(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function p(e){return!t(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function m(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function h(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(m(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function g(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(m(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function _(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(m(e))}catch{}}function v(e){let t=h(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function y(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function b(e){let n=new Map,o=null,c=null,u=0,m=!1,h=null,b=500,x=null,S=`idle`,C=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},w=e=>{S=e};function T(t,i){let a=r()
|
|
8
|
+
<script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return t.get(e)??i?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`hostUrl`)||o,c=a(`hostTitle`)||``,l=a(`tabId`)||r(),u=a(`relayHost`)||`127.0.0.1`,d=a(`relayPort`),f=d&&d.length>0?Number.parseInt(d,10):9333,p=a(`autoConnect`)!==`false`,m=a(`relayId`)||void 0,h=a(`relayWorkspace`)||void 0,g=a(`requestTimeout`),_=g&&g.length>0?Number.parseInt(g,10):6e4;return n(u)?!Number.isInteger(f)||f<1||f>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,d),null):!Number.isInteger(_)||_<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,g),null):{autoConnect:p,hostOrigin:o,hostTitle:c,hostUrl:s,relayHostHint:u,relayPortHint:f,...m?{relayId:m}:{},...h?{relayWorkspace:h}:{},requestTimeoutMs:_,tabId:l}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,u),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}b(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function p(e){return!t(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function m(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function h(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(m(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function g(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(m(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function _(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(m(e))}catch{}}function v(e){let t=h(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function y(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function b(e){let n=new Map,o=null,c=null,u=0,m=!1,h=null,b=500,x=null,S=`idle`,C=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},w=e=>{S=e};function T(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},e.requestTimeoutMs);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let E=(n,r)=>{let s=[],l=()=>{h&&(clearTimeout(h),h=null)},v=()=>{a(n,JSON.stringify({type:`tools/list`,tools:s}))};x&&(clearTimeout(x),x=null),o=r,c=n,b=500,m=!1,w(`connected`),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let u=d(c);if(u){o={hello:u,host:r.host,port:r.port};return}if(f(c)){l(),m=!0,g(e,r),v();return}let h=p(c);if(h){l(),m=!1,_(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:h.message,reason:h.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,h.reason,h.message);try{n.close(1008,h.message)}catch{}return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type===`elicitation-response`){window.parent.postMessage({type:`webmcp.elicitation.response`,callId:c.callId,result:t(c.result)?c.result:{action:`decline`,content:null}},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}T(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(m=!1,c=null,l(),C(`WebSocket connection lost`),u+=1,u>=5){u=0,A();return}k()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),T(`webmcp.tools.list`,{}).then(t=>{s=Array.isArray(t.tools)?t.tools:[],a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),h=setTimeout(()=>{h=null,!(c!==n||n.readyState!==WebSocket.OPEN||m)&&(m=!0,g(e,r),v())},250)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},D=async t=>{let n=await y({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(E(n.socket,r),!0)},O=async()=>{w(`discovering`);for(let t of v(e))if(await D(t))return u=0,!0;return!1},k=()=>{if(!o||x)return;w(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,D(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,A();return}k()})},t)},A=()=>{if(x)return;w(`rediscover`);let e=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,O().then(e=>{e||A()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&m&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&S!==`discovering`&&O();return}if(t(i)&&i.type===`webmcp.elicitation.request`){c&&c.readyState===WebSocket.OPEN&&a(c,JSON.stringify({type:`elicitation-request`,callId:i.callId,params:t(i.params)?i.params:{}}));return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?O().then(e=>{e||A()}):w(`idle`)}u()})();</script>
|
|
9
9
|
</body>
|
|
10
10
|
</html>
|
package/dist/browser/widget.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return t.get(e)??i?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`hostUrl`)||o,c=a(`hostTitle`)||``,l=a(`tabId`)||r(),u=a(`relayHost`)||`127.0.0.1`,d=a(`relayPort`),f=d&&d.length>0?Number.parseInt(d,10):9333,p=a(`autoConnect`)!==`false`,m=a(`relayId`)||void 0,h=a(`relayWorkspace`)||void 0;return n(u)?!Number.isInteger(f)||f<1||f>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,d),null):{autoConnect:p,hostOrigin:o,hostTitle:c,hostUrl:s,relayHostHint:u,relayPortHint:f,...m?{relayId:m}:{},...h?{relayWorkspace:h}:{},tabId:l}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,u),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}b(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function p(e){return!t(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function m(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function h(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(m(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function g(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(m(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function _(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(m(e))}catch{}}function v(e){let t=h(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function y(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function b(e){let n=new Map,o=null,c=null,u=0,m=!1,h=null,b=500,x=null,S=`idle`,C=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},w=e=>{S=e};function T(t,i){let a=r()
|
|
1
|
+
(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return t.get(e)??i?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`hostUrl`)||o,c=a(`hostTitle`)||``,l=a(`tabId`)||r(),u=a(`relayHost`)||`127.0.0.1`,d=a(`relayPort`),f=d&&d.length>0?Number.parseInt(d,10):9333,p=a(`autoConnect`)!==`false`,m=a(`relayId`)||void 0,h=a(`relayWorkspace`)||void 0,g=a(`requestTimeout`),_=g&&g.length>0?Number.parseInt(g,10):6e4;return n(u)?!Number.isInteger(f)||f<1||f>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,d),null):!Number.isInteger(_)||_<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,g),null):{autoConnect:p,hostOrigin:o,hostTitle:c,hostUrl:s,relayHostHint:u,relayPortHint:f,...m?{relayId:m}:{},...h?{relayWorkspace:h}:{},requestTimeoutMs:_,tabId:l}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,u),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}b(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return!t(e)||e.type!==`hello/accepted`?null:{type:`hello/accepted`}}function p(e){return!t(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function m(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function h(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(m(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function g(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(m(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function _(e){if(!(typeof sessionStorage>`u`))try{sessionStorage.removeItem(m(e))}catch{}}function v(e){let t=h(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function y(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function b(e){let n=new Map,o=null,c=null,u=0,m=!1,h=null,b=500,x=null,S=`idle`,C=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},w=e=>{S=e};function T(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},e.requestTimeoutMs);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let E=(n,r)=>{let s=[],l=()=>{h&&(clearTimeout(h),h=null)},v=()=>{a(n,JSON.stringify({type:`tools/list`,tools:s}))};x&&(clearTimeout(x),x=null),o=r,c=n,b=500,m=!1,w(`connected`),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let u=d(c);if(u){o={hello:u,host:r.host,port:r.port};return}if(f(c)){l(),m=!0,g(e,r),v();return}let h=p(c);if(h){l(),m=!1,_(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:h.message,reason:h.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,h.reason,h.message);try{n.close(1008,h.message)}catch{}return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type===`elicitation-response`){window.parent.postMessage({type:`webmcp.elicitation.response`,callId:c.callId,result:t(c.result)?c.result:{action:`decline`,content:null}},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}T(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(m=!1,c=null,l(),C(`WebSocket connection lost`),u+=1,u>=5){u=0,A();return}k()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),T(`webmcp.tools.list`,{}).then(t=>{s=Array.isArray(t.tools)?t.tools:[],a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),h=setTimeout(()=>{h=null,!(c!==n||n.readyState!==WebSocket.OPEN||m)&&(m=!0,g(e,r),v())},250)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},D=async t=>{let n=await y({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(E(n.socket,r),!0)},O=async()=>{w(`discovering`);for(let t of v(e))if(await D(t))return u=0,!0;return!1},k=()=>{if(!o||x)return;w(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,D(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,A();return}k()})},t)},A=()=>{if(x)return;w(`rediscover`);let e=Math.min(s,Math.round(b*(.85+Math.random()*.3)));b=Math.min(Math.round(b*1.5),s),x=setTimeout(()=>{x=null,O().then(e=>{e||A()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&m&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&S!==`discovering`&&O();return}if(t(i)&&i.type===`webmcp.elicitation.request`){c&&c.readyState===WebSocket.OPEN&&a(c,JSON.stringify({type:`elicitation-request`,callId:i.callId,params:t(i.params)?i.params:{}}));return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?O().then(e=>{e||A()}):w(`idle`)}u()})();
|
package/dist/index.d.mts
CHANGED
|
@@ -202,7 +202,7 @@ declare const BrowserToRelayMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
202
202
|
type: z.ZodLiteral<"elicitation-request">;
|
|
203
203
|
callId: z.ZodString;
|
|
204
204
|
params: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
205
|
-
}, z.core.$strip>]
|
|
205
|
+
}, z.core.$strip>]>;
|
|
206
206
|
/**
|
|
207
207
|
* Browser-to-relay message payload.
|
|
208
208
|
*/
|
|
@@ -298,7 +298,7 @@ declare const RelayToBrowserMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
298
298
|
type: z.ZodLiteral<"elicitation-response">;
|
|
299
299
|
callId: z.ZodString;
|
|
300
300
|
result: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
301
|
-
}, z.core.$strip>]
|
|
301
|
+
}, z.core.$strip>]>;
|
|
302
302
|
/**
|
|
303
303
|
* Relay-to-browser message payload.
|
|
304
304
|
*/
|
|
@@ -336,7 +336,7 @@ declare const RelayClientToServerMessageSchema: z.ZodDiscriminatedUnion<[z.ZodOb
|
|
|
336
336
|
callId: z.ZodString;
|
|
337
337
|
toolName: z.ZodString;
|
|
338
338
|
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
339
|
-
}, z.core.$strip>]
|
|
339
|
+
}, z.core.$strip>]>;
|
|
340
340
|
/**
|
|
341
341
|
* Relay client to server message payload.
|
|
342
342
|
*/
|
|
@@ -559,7 +559,7 @@ declare const RelayServerToClientMessageSchema: z.ZodDiscriminatedUnion<[z.ZodOb
|
|
|
559
559
|
}, z.core.$strip>>>>;
|
|
560
560
|
toolSourceMap: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
561
561
|
type: z.ZodLiteral<"relay/tools-changed">;
|
|
562
|
-
}, z.core.$strip>]
|
|
562
|
+
}, z.core.$strip>]>;
|
|
563
563
|
/**
|
|
564
564
|
* Relay server to client message payload.
|
|
565
565
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-b/webmcp-local-relay",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Local MCP relay for browser WebMCP tools via embed iframe and localhost WebSocket",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@modelcontextprotocol/sdk": "1.26.0",
|
|
51
51
|
"ws": "^8.18.0",
|
|
52
|
-
"zod": "
|
|
52
|
+
"zod": "3.25.76"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "22.17.2",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"msw": "2.12.10",
|
|
59
59
|
"playwright": "^1.58.0",
|
|
60
60
|
"typescript": "^5.8.3",
|
|
61
|
-
"vite-plus": "
|
|
61
|
+
"vite-plus": "latest",
|
|
62
62
|
"vitest": "npm:@voidzero-dev/vite-plus-test@latest",
|
|
63
|
-
"@mcp-b/webmcp-types": "2.
|
|
63
|
+
"@mcp-b/webmcp-types": "2.3.0"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=22"
|