@mcp-b/webmcp-local-relay 2.1.0 → 2.2.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.
@@ -5,6 +5,6 @@
5
5
  <title>WebMCP Relay Widget</title>
6
6
  </head>
7
7
  <body>
8
- <script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=t.get(`hostOrigin`);if(!i)return null;let a=t.get(`hostUrl`)||i,o=t.get(`hostTitle`)||``,s=t.get(`tabId`)||r(),c=t.get(`relayHost`)||`127.0.0.1`,l=t.get(`relayPort`),u=l&&l.length>0?Number.parseInt(l,10):9333,d=t.get(`autoConnect`)!==`false`,f=t.get(`relayId`)||void 0,p=t.get(`relayWorkspace`)||void 0;return n(c)?!Number.isInteger(u)||u<1||u>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,l),null):{autoConnect:d,hostOrigin:i,hostTitle:o,hostUrl:a,relayHostHint:c,relayPortHint:u,...f?{relayId:f}:{},...p?{relayWorkspace:p}:{},tabId:s}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,c),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}_(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function p(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(f(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function m(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(f(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function h(e){let t=p(e);[e.relayHostHint,`127.0.0.1`,`[::1]`].filter((e,t,n)=>n.indexOf(e)===t);let n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function g(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function _(e){let n=new Map,o=null,c=null,u=0,f=!1,p=500,_=null,v=`idle`,y=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},b=e=>{v=e};function x(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},1e4);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let S=(n,r)=>{_&&(clearTimeout(_),_=null),o=r,c=n,p=500,f=!1,b(`connected`),m(e,r),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let l=d(c);if(l){o={hello:l,host:r.host,port:r.port};return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}x(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(f=!1,c=null,y(`WebSocket connection lost`),u+=1,u>=5){u=0,E();return}T()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),x(`webmcp.tools.list`,{}).then(t=>{let r=Array.isArray(t.tools)?t.tools:[];a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),a(n,JSON.stringify({type:`tools/list`,tools:r})),f=!0}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},C=async t=>{let n=await g({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(S(n.socket,r),!0)},w=async()=>{b(`discovering`);for(let t of h(e))if(await C(t))return u=0,!0;return!1},T=()=>{if(!o||_)return;b(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,C(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,E();return}T()})},t)},E=()=>{if(_)return;b(`rediscover`);let e=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,w().then(e=>{e||E()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&f&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&v!==`discovering`&&w();return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?w().then(e=>{e||E()}):b(`idle`)}u()})();</script>
8
+ <script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=t.get(`hostOrigin`);if(!i)return null;let a=t.get(`hostUrl`)||i,o=t.get(`hostTitle`)||``,s=t.get(`tabId`)||r(),c=t.get(`relayHost`)||`127.0.0.1`,l=t.get(`relayPort`),u=l&&l.length>0?Number.parseInt(l,10):9333,d=t.get(`autoConnect`)!==`false`,f=t.get(`relayId`)||void 0,p=t.get(`relayWorkspace`)||void 0;return n(c)?!Number.isInteger(u)||u<1||u>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,l),null):{autoConnect:d,hostOrigin:i,hostTitle:o,hostUrl:a,relayHostHint:c,relayPortHint:u,...f?{relayId:f}:{},...p?{relayWorkspace:p}:{},tabId:s}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,c),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}_(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function p(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(f(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function m(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(f(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function h(e){let t=p(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function g(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function _(e){let n=new Map,o=null,c=null,u=0,f=!1,p=500,_=null,v=`idle`,y=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},b=e=>{v=e};function x(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},1e4);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let S=(n,r)=>{_&&(clearTimeout(_),_=null),o=r,c=n,p=500,f=!1,b(`connected`),m(e,r),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let l=d(c);if(l){o={hello:l,host:r.host,port:r.port};return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}x(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(f=!1,c=null,y(`WebSocket connection lost`),u+=1,u>=5){u=0,E();return}T()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),x(`webmcp.tools.list`,{}).then(t=>{let r=Array.isArray(t.tools)?t.tools:[];a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),a(n,JSON.stringify({type:`tools/list`,tools:r})),f=!0}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},C=async t=>{let n=await g({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(S(n.socket,r),!0)},w=async()=>{b(`discovering`);for(let t of h(e))if(await C(t))return u=0,!0;return!1},T=()=>{if(!o||_)return;b(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,C(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,E();return}T()})},t)},E=()=>{if(_)return;b(`rediscover`);let e=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,w().then(e=>{e||E()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&f&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&v!==`discovering`&&w();return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?w().then(e=>{e||E()}):b(`idle`)}u()})();</script>
9
9
  </body>
10
10
  </html>
@@ -1 +1 @@
1
- (function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=t.get(`hostOrigin`);if(!i)return null;let a=t.get(`hostUrl`)||i,o=t.get(`hostTitle`)||``,s=t.get(`tabId`)||r(),c=t.get(`relayHost`)||`127.0.0.1`,l=t.get(`relayPort`),u=l&&l.length>0?Number.parseInt(l,10):9333,d=t.get(`autoConnect`)!==`false`,f=t.get(`relayId`)||void 0,p=t.get(`relayWorkspace`)||void 0;return n(c)?!Number.isInteger(u)||u<1||u>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,l),null):{autoConnect:d,hostOrigin:i,hostTitle:o,hostUrl:a,relayHostHint:c,relayPortHint:u,...f?{relayId:f}:{},...p?{relayWorkspace:p}:{},tabId:s}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,c),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}_(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function p(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(f(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function m(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(f(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function h(e){let t=p(e);[e.relayHostHint,`127.0.0.1`,`[::1]`].filter((e,t,n)=>n.indexOf(e)===t);let n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function g(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function _(e){let n=new Map,o=null,c=null,u=0,f=!1,p=500,_=null,v=`idle`,y=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},b=e=>{v=e};function x(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},1e4);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let S=(n,r)=>{_&&(clearTimeout(_),_=null),o=r,c=n,p=500,f=!1,b(`connected`),m(e,r),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let l=d(c);if(l){o={hello:l,host:r.host,port:r.port};return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}x(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(f=!1,c=null,y(`WebSocket connection lost`),u+=1,u>=5){u=0,E();return}T()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),x(`webmcp.tools.list`,{}).then(t=>{let r=Array.isArray(t.tools)?t.tools:[];a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),a(n,JSON.stringify({type:`tools/list`,tools:r})),f=!0}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},C=async t=>{let n=await g({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(S(n.socket,r),!0)},w=async()=>{b(`discovering`);for(let t of h(e))if(await C(t))return u=0,!0;return!1},T=()=>{if(!o||_)return;b(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,C(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,E();return}T()})},t)},E=()=>{if(_)return;b(`rediscover`);let e=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,w().then(e=>{e||E()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&f&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&v!==`discovering`&&w();return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?w().then(e=>{e||E()}):b(`idle`)}u()})();
1
+ (function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]);function t(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function n(t){return e.has(t)}function r(){return typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`${String(Date.now())}_${String(Math.random()).slice(2,10)}`}function i(e){return String(e).replace(/[\r\n]/g,``)}function a(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function o(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let s=3e3;function c(e=window.location.search){let t=new URLSearchParams(e),i=t.get(`hostOrigin`);if(!i)return null;let a=t.get(`hostUrl`)||i,o=t.get(`hostTitle`)||``,s=t.get(`tabId`)||r(),c=t.get(`relayHost`)||`127.0.0.1`,l=t.get(`relayPort`),u=l&&l.length>0?Number.parseInt(l,10):9333,d=t.get(`autoConnect`)!==`false`,f=t.get(`relayId`)||void 0,p=t.get(`relayWorkspace`)||void 0;return n(c)?!Number.isInteger(u)||u<1||u>65535?(console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,l),null):{autoConnect:d,hostOrigin:i,hostTitle:o,hostUrl:a,relayHostHint:c,relayPortHint:u,...f?{relayId:f}:{},...p?{relayWorkspace:p}:{},tabId:s}:(console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,c),null)}function l(e){return!t(e)||typeof e.requestId!=`string`||typeof e.type!=`string`?null:{requestId:e.requestId,type:e.type,tools:e.tools,result:e.result,error:e.error}}function u(){let e=c();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}_(e)}function d(e){return!t(e)||e.type!==`server-hello`||e.service!==`webmcp-local-relay`||e.version!==1||typeof e.host!=`string`||typeof e.instanceId!=`string`||typeof e.port!=`number`?null:{type:`server-hello`,service:`webmcp-local-relay`,version:1,host:e.host,instanceId:e.instanceId,port:e.port,...typeof e.label==`string`?{label:e.label}:{},...typeof e.relayId==`string`?{relayId:e.relayId}:{},...typeof e.workspace==`string`?{workspace:e.workspace}:{}}}function f(e){return o({hostOrigin:e.hostOrigin,...e.relayId?{relayId:e.relayId}:{},...e.relayWorkspace?{workspace:e.relayWorkspace}:{}})}function p(e){if(typeof sessionStorage>`u`)return null;try{let t=sessionStorage.getItem(f(e));if(!t)return null;let n=JSON.parse(t);return typeof n.host!=`string`||n.host.length===0||typeof n.port!=`number`||!Number.isInteger(n.port)||n.port<1||n.port>65535?null:{host:n.host,port:n.port}}catch{return null}}function m(e,t){if(!(typeof sessionStorage>`u`))try{sessionStorage.setItem(f(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function h(e){let t=p(e),n=new Set,r=[],i=(e,t)=>{if(!Number.isInteger(t)||t<1||t>65535)return;let i=`${e}:${String(t)}`;n.has(i)||(n.add(i),r.push({host:e,port:t}))};e.relayPortHint!==void 0&&i(e.relayHostHint,e.relayPortHint),t&&i(t.host,t.port);for(let e of[`127.0.0.1`,`[::1]`])for(let t=9333;t<=9348;t+=1)i(e,t);return r}async function g(e){let t=`ws://${e.host}:${String(e.port)}`;return new Promise(n=>{let r=!1,i=new WebSocket(t,[`webmcp-discovery.v1`,`webmcp.v1`]),a=e=>{if(!r){if(r=!0,clearTimeout(l),i.removeEventListener(`close`,s),i.removeEventListener(`error`,o),i.removeEventListener(`message`,c),e===null)try{i.close()}catch{}n(e)}},o=()=>{a(null)},s=()=>{a(null)},c=t=>{let n;try{n=JSON.parse(String(t.data))}catch{return}let r=d(n);r&&a({endpoint:{hello:r,host:e.host,port:e.port},socket:i})},l=setTimeout(()=>{a(null)},1200);i.addEventListener(`close`,s,{once:!0}),i.addEventListener(`error`,o,{once:!0}),i.addEventListener(`message`,c)})}function _(e){let n=new Map,o=null,c=null,u=0,f=!1,p=500,_=null,v=`idle`,y=e=>{for(let[t,r]of n)clearTimeout(r.timeoutId),n.delete(t),r.reject(Error(e))},b=e=>{v=e};function x(t,i){let a=r();return new Promise((r,o)=>{let s=setTimeout(()=>{n.delete(a),o(Error(`Host response timeout: ${t}`))},1e4);n.set(a,{resolve:r,reject:o,timeoutId:s,responseType:`${t}.response`,errorType:`${t}.error`}),window.parent.postMessage({type:`${t}.request`,requestId:a,...i},e.hostOrigin)})}let S=(n,r)=>{_&&(clearTimeout(_),_=null),o=r,c=n,p=500,f=!1,b(`connected`),m(e,r),n.addEventListener(`message`,s=>{let c;try{c=JSON.parse(String(s.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let l=d(c);if(l){o={hello:l,host:r.host,port:r.port};return}if(!t(c)||typeof c.type!=`string`){console.debug(`[webmcp-relay-widget] Ignoring non-object or untyped relay message`);return}if(c.type===`ping`){a(n,JSON.stringify({type:`pong`}));return}if(c.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(c.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,i(c.type));return}x(`webmcp.tools.invoke`,{toolName:c.toolName,args:t(c.args)?c.args:{}}).then(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:e.result}))}).catch(e=>{a(n,JSON.stringify({type:`result`,callId:c.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),n.addEventListener(`close`,()=>{if(c===n){if(f=!1,c=null,y(`WebSocket connection lost`),u+=1,u>=5){u=0,E();return}T()}},{once:!0}),n.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{n.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),x(`webmcp.tools.list`,{}).then(t=>{let r=Array.isArray(t.tools)?t.tools:[];a(n,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),a(n,JSON.stringify({type:`tools/list`,tools:r})),f=!0}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{n.close()}catch{}})},C=async t=>{let n=await g({host:t.host,port:t.port});if(!n)return!1;let r=n.endpoint;return e.relayId&&r.hello.relayId!==e.relayId||e.relayWorkspace&&r.hello.workspace!==e.relayWorkspace?(n.socket.close(),!1):(S(n.socket,r),!0)},w=async()=>{b(`discovering`);for(let t of h(e))if(await C(t))return u=0,!0;return!1},T=()=>{if(!o||_)return;b(`retry-same-endpoint`);let e={host:o.host,port:o.port},t=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,C(e).then(e=>{if(e){u=0;return}if(u>=5){u=0,E();return}T()})},t)},E=()=>{if(_)return;b(`rediscover`);let e=Math.min(s,Math.round(p*(.85+Math.random()*.3)));p=Math.min(Math.round(p*1.5),s),_=setTimeout(()=>{_=null,w().then(e=>{e||E()})},e)};window.addEventListener(`message`,r=>{if(r.origin!==e.hostOrigin)return;let i=r.data;if(t(i)&&i.type===`webmcp.tools.changed`){c&&f&&a(c,JSON.stringify({type:`tools/changed`,tools:Array.isArray(i.tools)?i.tools:[]}));return}if(t(i)&&i.type===`webmcp.connect`){!c&&v!==`discovering`&&w();return}let o=l(i);if(!o)return;let s=n.get(o.requestId);if(s){if(o.type===s.responseType){clearTimeout(s.timeoutId),n.delete(o.requestId),s.resolve(o);return}o.type===s.errorType&&(clearTimeout(s.timeoutId),n.delete(o.requestId),s.reject(Error(String(o.error||`Unknown host error`))))}}),e.autoConnect?w().then(e=>{e||E()}):b(`idle`)}u()})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/webmcp-local-relay",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Local MCP relay for browser WebMCP tools via embed iframe and localhost WebSocket",
5
5
  "keywords": [
6
6
  "mcp",
@@ -56,7 +56,7 @@
56
56
  "tsdown": "^0.15.10",
57
57
  "typescript": "^5.8.3",
58
58
  "vitest": "^4.0.18",
59
- "@mcp-b/webmcp-types": "2.1.0"
59
+ "@mcp-b/webmcp-types": "2.2.0"
60
60
  },
61
61
  "engines": {
62
62
  "node": ">=22"