@mcp-b/webmcp-local-relay 5.1.0 → 6.0.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 +25 -15
- package/dist/browser/embed.js +2 -2
- package/dist/browser/widget.html +1 -1
- package/dist/browser/widget.js +1 -1
- package/dist/cli.mjs +5 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +1 -1
- package/dist/{mcpRelayServer-DfGkigG5.mjs → mcpRelayServer-DEgebuO2.mjs} +88 -120
- package/dist/mcpRelayServer-DEgebuO2.mjs.map +1 -0
- package/package.json +2 -3
- package/dist/mcpRelayServer-DfGkigG5.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -56,15 +56,18 @@ Tools that require MCP task execution are omitted, and multi-round `input_requir
|
|
|
56
56
|
Add one script tag to expose your page's WebMCP tools to the relay:
|
|
57
57
|
|
|
58
58
|
```html
|
|
59
|
-
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@
|
|
59
|
+
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@6/dist/browser/embed.js"></script>
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
That's it. If your page already registers tools on `document.modelContext`, they'll be picked up automatically.
|
|
63
63
|
|
|
64
|
+
Keep the major version in CDN URLs so the embed and the page runtime upgrade
|
|
65
|
+
together. Pages that still load a 5.x runtime pin `@5` for both scripts.
|
|
66
|
+
|
|
64
67
|
New to WebMCP? Here's the full setup:
|
|
65
68
|
|
|
66
69
|
```html
|
|
67
|
-
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/global@
|
|
70
|
+
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/global@6/dist/index.iife.js"></script>
|
|
68
71
|
<script>
|
|
69
72
|
void document.modelContext
|
|
70
73
|
.registerTool({
|
|
@@ -75,14 +78,14 @@ New to WebMCP? Here's the full setup:
|
|
|
75
78
|
})
|
|
76
79
|
.catch(console.error);
|
|
77
80
|
</script>
|
|
78
|
-
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@
|
|
81
|
+
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@6/dist/browser/embed.js"></script>
|
|
79
82
|
```
|
|
80
83
|
|
|
81
84
|
Custom relay port:
|
|
82
85
|
|
|
83
86
|
```html
|
|
84
87
|
<script
|
|
85
|
-
src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@
|
|
88
|
+
src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@6/dist/browser/embed.js"
|
|
86
89
|
data-relay-port="9444"
|
|
87
90
|
></script>
|
|
88
91
|
```
|
|
@@ -92,7 +95,7 @@ several slow API calls and might exceed one minute:
|
|
|
92
95
|
|
|
93
96
|
```html
|
|
94
97
|
<script
|
|
95
|
-
src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@
|
|
98
|
+
src="https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-local-relay@6/dist/browser/embed.js"
|
|
96
99
|
data-request-timeout="120000"
|
|
97
100
|
></script>
|
|
98
101
|
```
|
|
@@ -230,19 +233,26 @@ After a disconnect, the widget retries the last endpoint once after about `500ms
|
|
|
230
233
|
Supported page runtimes:
|
|
231
234
|
|
|
232
235
|
1. `@mcp-b/global` (recommended for the complete MCP-B runtime)
|
|
233
|
-
2. Current native Chrome with `document.modelContext.getTools()` and
|
|
236
|
+
2. Current native Chrome with `document.modelContext.getTools()` and object-input `executeTool()`, which this relay requires to invoke tools
|
|
234
237
|
3. `@mcp-b/webmcp-polyfill`
|
|
235
238
|
|
|
236
239
|
Runtime dispatch behavior in the browser embed/widget layer:
|
|
237
240
|
|
|
238
241
|
- Uses asynchronous `document.modelContext.getTools()` and the exact returned
|
|
239
|
-
descriptor with
|
|
242
|
+
descriptor with `executeTool()` and object input, as defined by the current
|
|
243
|
+
upstream API.
|
|
240
244
|
- Refreshes the descriptor before every invocation so Chrome never receives a
|
|
241
245
|
stale registration object.
|
|
246
|
+
- Relays the page's own tools plus tools registered by same-origin descendant
|
|
247
|
+
frames. When names collide, the page's own tool wins and the embed logs one
|
|
248
|
+
warning per name.
|
|
249
|
+
- Converts only results that parse to a JSON object into structured content.
|
|
250
|
+
A JSON string arrives as its content; any other result, such as `10.50`,
|
|
251
|
+
keeps its original text.
|
|
242
252
|
|
|
243
253
|
### WebMCP Standard Status
|
|
244
254
|
|
|
245
|
-
WebMCP is an emerging web platform proposal. This relay works with the current native Chrome preview and MCP-B runtimes, but
|
|
255
|
+
WebMCP is an emerging web platform proposal. This relay works with the current native Chrome preview and MCP-B runtimes, but the browser API can still change as implementations mature.
|
|
246
256
|
|
|
247
257
|
- [W3C WebML CG draft](https://webmachinelearning.github.io/webmcp/)
|
|
248
258
|
- [Proposal repository](https://github.com/webmachinelearning/webmcp)
|
|
@@ -256,13 +266,13 @@ For Chromium/Chrome Canary native preview testing:
|
|
|
256
266
|
|
|
257
267
|
### Troubleshooting
|
|
258
268
|
|
|
259
|
-
| Problem | Fix
|
|
260
|
-
| ------------------------ |
|
|
261
|
-
| `No sources connected` | Ensure the page loaded `embed.js` and the relay process is running
|
|
262
|
-
| `No tools listed` | Ensure tools are registered on the page
|
|
263
|
-
| `Tool not found` | Tab reloaded or disconnected — call `webmcp_list_tools` again to refresh
|
|
264
|
-
| Connection blocked | Verify `--widget-origin` matches your host page's origin (e.g., `https://myapp.com`), and relay port matches `data-relay-port`
|
|
265
|
-
| `Host response timeout:` | The host page exceeded its timeout (default 60s). Raise `data-request-timeout` and keep CLI `--invoke-timeout` slightly higher
|
|
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. The embed listens for changes and polls every two seconds, so load order does not matter. |
|
|
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
|
+
| `Host response timeout:` | The host page exceeded its timeout (default 60s). Raise `data-request-timeout` and keep CLI `--invoke-timeout` slightly higher |
|
|
266
276
|
|
|
267
277
|
---
|
|
268
278
|
|
package/dist/browser/embed.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(t){if(!e(t))return!1;let n=Object.getPrototypeOf(t);return n===Object.prototype||n===null}function n(e,r=new WeakSet){if(e===null||typeof e==`string`||typeof e==`boolean`)return!0;if(typeof e==`number`)return Number.isFinite(e);if(typeof e!=`object`||r.has(e))return!1;r.add(e);try{return(Array.isArray(e)?e:t(e)?Object.values(e):null)?.every(e=>n(e,r))??!1}catch{return!1}finally{r.delete(e)}}function r(e){return n(
|
|
2
|
-
`)}function
|
|
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;function c(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function l(e){return c(e)&&typeof e.requestId==`string`&&typeof e.type==`string`}function u(e){let t;try{t=JSON.parse(e)}catch{}return c(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 d(e){let t=new Map;for(let n of e){let e=t.get(n.name);e&&(s.has(n.name)||(s.add(n.name),console.warn(`[webmcp-relay-embed] More than one frame registered a tool named "${n.name}". Only one is relayed, preferring this page's own tool.`)),e.window===window||n.window!==window)||t.set(n.name,n)}return[...t.values()]}let f=`[data-webmcp-relay]`,p=`__webmcp_relay_tab_id`,m=null,h=document.currentScript instanceof HTMLScriptElement?document.currentScript:null,g=h?h.hasAttribute(`data-debug`):!1;function _(...e){g&&console.warn(`[webmcp-relay-embed]`,...e)}function v(){try{let e=sessionStorage.getItem(p);if(e)return e}catch(e){_(`sessionStorage read failed, tab ID will not persist:`,e)}let e=crypto.randomUUID();try{sessionStorage.setItem(p,e)}catch(e){_(`sessionStorage write failed:`,e)}return e}function y(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:v(),widgetUrl:new URL(`widget.html`,e.src).href}}function b(e){if(e.inputSchema!==void 0&&!c(e.inputSchema))return console.warn(`[webmcp-relay-embed] 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}async function x(){let e=document.modelContext;return e?d(await e.getTools()).map(b).filter(e=>e!==null):[]}async function S(e,t){let n=document.modelContext;if(!n)throw Error(`No executable WebMCP runtime found on this page`);let r=d(await n.getTools()).find(t=>t.name===e);if(!r)throw Error(`Tool not found: ${e}`);return u(await n.executeTool(r,t))}function C(e){return c(e)?e:(e!=null&&_(`Tool invocation args must be an object; using empty input`),{})}let w=!1,T=0,E=null,D=``;function O(e){return typeof e!=`object`||!e?JSON.stringify(e)??`undefined`:Array.isArray(e)?`[${e.map(O).join(`,`)}]`:`{${Object.entries(e).sort(([e],[t])=>e<t?-1:+(e>t)).map(([e,t])=>`${JSON.stringify(e)}:${O(t)}`).join(`,`)}}`}function k(e){return e.map(O).sort().join(`
|
|
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)})}})();
|
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]`]),t=9333;function n(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function r(
|
|
8
|
+
<script>(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),t=9333;function n(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function r(e){return n(e)&&typeof e.requestId==`string`&&typeof e.type==`string`}function i(t){return e.has(t)}function a(e){return e.replace(/[\r\n]/g,``)}function o(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function s(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let c=[1e4,2e4,3e4];function l(e=window.location.search){let n=new URLSearchParams(e),r=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return n.get(e)??r?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`relayHost`)||`127.0.0.1`;if(!i(s))return console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,s),null;let c=a(`relayPort`),l=c?Number(c):t;if(!Number.isInteger(l)||l<1||l>65535)return console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,c),null;let u=a(`requestTimeout`),d=u?Number(u):6e4;return!Number.isInteger(d)||d<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,u),null):{autoConnect:a(`autoConnect`)!==`false`,hostOrigin:o,hostTitle:a(`hostTitle`)||``,hostUrl:a(`hostUrl`)||o,relayHostHint:s,relayId:a(`relayId`)||void 0,relayPortHint:l,relayWorkspace:a(`relayWorkspace`)||void 0,requestTimeoutMs:d,tabId:a(`tabId`)||crypto.randomUUID()}}function u(){let e=l();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}y(e)}function d(e){if(!n(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 f(e){return!n(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function p(e){return s({hostOrigin:e.hostOrigin,relayId:e.relayId??null,workspace:e.relayWorkspace??null})}function m(e){try{let t=sessionStorage.getItem(p(e));if(!t)return null;let r=JSON.parse(t);return!n(r)||typeof r.host!=`string`||r.host.length===0||typeof r.port!=`number`||!Number.isInteger(r.port)||r.port<1||r.port>65535?null:{host:r.host,port:r.port}}catch{return null}}function h(e,t){try{sessionStorage.setItem(p(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function g(e){try{sessionStorage.removeItem(p(e))}catch{}}function _(e){let n=m(e),r=new Set,i=[],a=(e,t)=>{let n=`${e}:${String(t)}`;r.has(n)||(r.add(n),i.push({host:e,port:t}))};a(e.relayHostHint,e.relayPortHint),n&&a(n.host,n.port);for(let e of[`127.0.0.1`,`[::1]`])for(let n=t;n<=9348;n+=1)a(e,n);return i}async function v(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=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 y(e){let t=new Map,i=[],s=0,l=null,u=null,p=!1,y=null,b=null,x=`idle`,S=0,C=null,w=e=>{for(let[n,r]of t)clearTimeout(r.timeoutId),t.delete(n),r.reject(Error(e))};function T(n,r){let i=crypto.randomUUID();return new Promise((a,o)=>{let s=setTimeout(()=>{t.delete(i),o(Error(`Host response timeout: ${n}`))},e.requestTimeoutMs);t.set(i,{resolve:a,reject:o,timeoutId:s,responseType:`${n}.response`,errorType:`${n}.error`}),window.parent.postMessage({type:`${n}.request`,requestId:i,...r},e.hostOrigin)})}let E=(t,r)=>{let c=s,m=()=>{y&&(clearTimeout(y),y=null)},_=()=>{o(t,JSON.stringify({type:`tools/list`,tools:i}))};b&&(clearTimeout(b),b=null),l=r,u=t,S=0,p=!1,x=`idle`,t.addEventListener(`message`,i=>{let s;try{s=JSON.parse(String(i.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let c=d(s);if(c){l={hello:c,host:r.host,port:r.port};return}if(n(s)&&s.type===`hello/accepted`){m(),p=!0,h(e,r),_();return}let u=f(s);if(u){m(),p=!1,g(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:u.message,reason:u.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,u.reason,u.message);try{t.close(1008,u.message)}catch{}return}if(!n(s)||typeof s.type!=`string`)return;let v=s;if(v.type===`ping`){o(t,JSON.stringify({type:`pong`}));return}if(v.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(v.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,a(s.type));return}T(`webmcp.tools.invoke`,{toolName:v.toolName,args:n(v.args)?v.args:{}}).then(e=>{o(t,JSON.stringify({type:`result`,callId:v.callId,result:e.result}))}).catch(e=>{o(t,JSON.stringify({type:`result`,callId:v.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),t.addEventListener(`close`,()=>{u===t&&(p=!1,u=null,m(),w(`WebSocket connection lost`),k())},{once:!0}),t.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{t.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),T(`webmcp.tools.list`,{}).then(n=>{s===c&&(i=Array.isArray(n.tools)?n.tools:[]),o(t,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),y=setTimeout(()=>{y=null,!(u!==t||t.readyState!==WebSocket.OPEN||p)&&(console.warn(`[webmcp-relay-widget] Relay did not acknowledge browser hello`),t.close(4e3,`Browser hello was not acknowledged`))},1e3)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{t.close()}catch{}})},D=async t=>{let n=await v({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()=>{x=`discovering`;try{for(let t of _(e))if(await D(t))return!0;return!1}finally{x===`discovering`&&(x=`idle`)}},k=()=>{if(!l||b)return;let e={host:l.host,port:l.port},t=Math.round(500*(.85+Math.random()*.3));b=setTimeout(()=>{b=null,D(e).then(e=>{e||A()})},t)},A=()=>{if(b)return;let e=c[S];if(e===void 0){N();return}b=setTimeout(()=>{b=null,O().then(e=>{e||(S+=1,A())})},e)},j=()=>{document.visibilityState===`visible`&&x===`dormant`&&F()},M=()=>{document.removeEventListener(`visibilitychange`,j),C!==null&&(clearInterval(C),C=null)},N=()=>{x!==`dormant`&&(x=`dormant`,b&&(clearTimeout(b),b=null),document.addEventListener(`visibilitychange`,j),C=setInterval(()=>{P()},12e4))},P=async()=>{if(x!==`dormant`)return;let t=[{host:e.relayHostHint,port:e.relayPortHint}],n=m(e);n&&(n.host!==e.relayHostHint||n.port!==e.relayPortHint)&&t.push(n),x=`discovering`,M();for(let e of t)if(await D(e)){S=0;return}N()},F=()=>{M(),S=0,O().then(e=>{e||N()})};window.addEventListener(`message`,a=>{if(a.source!==window.parent||a.origin!==e.hostOrigin)return;let c=a.data;if(n(c)&&c.type===`webmcp.tools.changed`){i=Array.isArray(c.tools)?c.tools:[],s++,u&&p&&o(u,JSON.stringify({type:`tools/changed`,tools:i}));return}if(n(c)&&c.type===`webmcp.connect`){x===`dormant`?F():!u&&x!==`discovering`&&O();return}if(!r(c))return;let l=t.get(c.requestId);!l||c.type!==l.responseType&&c.type!==l.errorType||(clearTimeout(l.timeoutId),t.delete(c.requestId),c.type===l.responseType?l.resolve(c):l.reject(Error(String(c.error||`Unknown host error`))))}),e.autoConnect&&O().then(e=>{e||A()})}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]`]),t=9333;function n(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function r(
|
|
1
|
+
(function(){let e=new Set([`127.0.0.1`,`localhost`,`::1`,`[::1]`]),t=9333;function n(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function r(e){return n(e)&&typeof e.requestId==`string`&&typeof e.type==`string`}function i(t){return e.has(t)}function a(e){return e.replace(/[\r\n]/g,``)}function o(e,t){try{e.readyState===WebSocket.OPEN&&e.send(t)}catch(e){console.warn(`[webmcp-relay] Failed to send message:`,e)}}function s(e){return`__webmcp_relay_endpoint:${[e.hostOrigin,e.relayId??``,e.workspace??``].map(e=>encodeURIComponent(e)).join(`:`)}`}let c=[1e4,2e4,3e4];function l(e=window.location.search){let n=new URLSearchParams(e),r=globalThis.__WEBMCP_RELAY_CONFIG;function a(e){return n.get(e)??r?.[e]??null}let o=a(`hostOrigin`);if(!o)return null;let s=a(`relayHost`)||`127.0.0.1`;if(!i(s))return console.error(`[webmcp-relay-widget] relayHost must be a loopback address, got:`,s),null;let c=a(`relayPort`),l=c?Number(c):t;if(!Number.isInteger(l)||l<1||l>65535)return console.error(`[webmcp-relay-widget] relayPort must be an integer between 1 and 65535, got:`,c),null;let u=a(`requestTimeout`),d=u?Number(u):6e4;return!Number.isInteger(d)||d<1?(console.error(`[webmcp-relay-widget] requestTimeout must be a positive integer (ms), got:`,u),null):{autoConnect:a(`autoConnect`)!==`false`,hostOrigin:o,hostTitle:a(`hostTitle`)||``,hostUrl:a(`hostUrl`)||o,relayHostHint:s,relayId:a(`relayId`)||void 0,relayPortHint:l,relayWorkspace:a(`relayWorkspace`)||void 0,requestTimeoutMs:d,tabId:a(`tabId`)||crypto.randomUUID()}}function u(){let e=l();if(!e){console.warn(`[webmcp-relay-widget] Missing required hostOrigin parameter. Widget will not start.`);return}y(e)}function d(e){if(!n(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 f(e){return!n(e)||e.type!==`hello/rejected`||typeof e.message!=`string`||typeof e.reason!=`string`?null:{type:`hello/rejected`,message:e.message,reason:e.reason}}function p(e){return s({hostOrigin:e.hostOrigin,relayId:e.relayId??null,workspace:e.relayWorkspace??null})}function m(e){try{let t=sessionStorage.getItem(p(e));if(!t)return null;let r=JSON.parse(t);return!n(r)||typeof r.host!=`string`||r.host.length===0||typeof r.port!=`number`||!Number.isInteger(r.port)||r.port<1||r.port>65535?null:{host:r.host,port:r.port}}catch{return null}}function h(e,t){try{sessionStorage.setItem(p(e),JSON.stringify({host:t.host,port:t.port}))}catch{}}function g(e){try{sessionStorage.removeItem(p(e))}catch{}}function _(e){let n=m(e),r=new Set,i=[],a=(e,t)=>{let n=`${e}:${String(t)}`;r.has(n)||(r.add(n),i.push({host:e,port:t}))};a(e.relayHostHint,e.relayPortHint),n&&a(n.host,n.port);for(let e of[`127.0.0.1`,`[::1]`])for(let n=t;n<=9348;n+=1)a(e,n);return i}async function v(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=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 y(e){let t=new Map,i=[],s=0,l=null,u=null,p=!1,y=null,b=null,x=`idle`,S=0,C=null,w=e=>{for(let[n,r]of t)clearTimeout(r.timeoutId),t.delete(n),r.reject(Error(e))};function T(n,r){let i=crypto.randomUUID();return new Promise((a,o)=>{let s=setTimeout(()=>{t.delete(i),o(Error(`Host response timeout: ${n}`))},e.requestTimeoutMs);t.set(i,{resolve:a,reject:o,timeoutId:s,responseType:`${n}.response`,errorType:`${n}.error`}),window.parent.postMessage({type:`${n}.request`,requestId:i,...r},e.hostOrigin)})}let E=(t,r)=>{let c=s,m=()=>{y&&(clearTimeout(y),y=null)},_=()=>{o(t,JSON.stringify({type:`tools/list`,tools:i}))};b&&(clearTimeout(b),b=null),l=r,u=t,S=0,p=!1,x=`idle`,t.addEventListener(`message`,i=>{let s;try{s=JSON.parse(String(i.data))}catch(e){console.warn(`[webmcp-relay-widget] Failed to parse relay message:`,e);return}let c=d(s);if(c){l={hello:c,host:r.host,port:r.port};return}if(n(s)&&s.type===`hello/accepted`){m(),p=!0,h(e,r),_();return}let u=f(s);if(u){m(),p=!1,g(e),window.parent.postMessage({type:`webmcp.relay.rejected`,host:r.host,port:r.port,message:u.message,reason:u.reason},e.hostOrigin),console.error(`[webmcp-relay-widget] Relay rejected browser hello:`,u.reason,u.message);try{t.close(1008,u.message)}catch{}return}if(!n(s)||typeof s.type!=`string`)return;let v=s;if(v.type===`ping`){o(t,JSON.stringify({type:`pong`}));return}if(v.type===`reload`){window.parent.postMessage({type:`webmcp.reload`},e.hostOrigin);return}if(v.type!==`invoke`){console.debug(`[webmcp-relay-widget] Ignoring unrecognized message type:`,a(s.type));return}T(`webmcp.tools.invoke`,{toolName:v.toolName,args:n(v.args)?v.args:{}}).then(e=>{o(t,JSON.stringify({type:`result`,callId:v.callId,result:e.result}))}).catch(e=>{o(t,JSON.stringify({type:`result`,callId:v.callId,result:{isError:!0,content:[{type:`text`,text:String(e instanceof Error?e.message:e)}]}}))})}),t.addEventListener(`close`,()=>{u===t&&(p=!1,u=null,m(),w(`WebSocket connection lost`),k())},{once:!0}),t.addEventListener(`error`,e=>{console.warn(`[webmcp-relay-widget] WebSocket error:`,e);try{t.close()}catch(e){console.warn(`[webmcp-relay-widget] Error closing socket after error:`,e)}}),T(`webmcp.tools.list`,{}).then(n=>{s===c&&(i=Array.isArray(n.tools)?n.tools:[]),o(t,JSON.stringify({type:`hello`,tabId:e.tabId,origin:e.hostOrigin,title:e.hostTitle||document.referrer||`Unknown page`,url:e.hostUrl})),y=setTimeout(()=>{y=null,!(u!==t||t.readyState!==WebSocket.OPEN||p)&&(console.warn(`[webmcp-relay-widget] Relay did not acknowledge browser hello`),t.close(4e3,`Browser hello was not acknowledged`))},1e3)}).catch(e=>{console.warn(`[webmcp-relay-widget] Hello handshake failed:`,e);try{t.close()}catch{}})},D=async t=>{let n=await v({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()=>{x=`discovering`;try{for(let t of _(e))if(await D(t))return!0;return!1}finally{x===`discovering`&&(x=`idle`)}},k=()=>{if(!l||b)return;let e={host:l.host,port:l.port},t=Math.round(500*(.85+Math.random()*.3));b=setTimeout(()=>{b=null,D(e).then(e=>{e||A()})},t)},A=()=>{if(b)return;let e=c[S];if(e===void 0){N();return}b=setTimeout(()=>{b=null,O().then(e=>{e||(S+=1,A())})},e)},j=()=>{document.visibilityState===`visible`&&x===`dormant`&&F()},M=()=>{document.removeEventListener(`visibilitychange`,j),C!==null&&(clearInterval(C),C=null)},N=()=>{x!==`dormant`&&(x=`dormant`,b&&(clearTimeout(b),b=null),document.addEventListener(`visibilitychange`,j),C=setInterval(()=>{P()},12e4))},P=async()=>{if(x!==`dormant`)return;let t=[{host:e.relayHostHint,port:e.relayPortHint}],n=m(e);n&&(n.host!==e.relayHostHint||n.port!==e.relayPortHint)&&t.push(n),x=`discovering`,M();for(let e of t)if(await D(e)){S=0;return}N()},F=()=>{M(),S=0,O().then(e=>{e||N()})};window.addEventListener(`message`,a=>{if(a.source!==window.parent||a.origin!==e.hostOrigin)return;let c=a.data;if(n(c)&&c.type===`webmcp.tools.changed`){i=Array.isArray(c.tools)?c.tools:[],s++,u&&p&&o(u,JSON.stringify({type:`tools/changed`,tools:i}));return}if(n(c)&&c.type===`webmcp.connect`){x===`dormant`?F():!u&&x!==`discovering`&&O();return}if(!r(c))return;let l=t.get(c.requestId);!l||c.type!==l.responseType&&c.type!==l.errorType||(clearTimeout(l.timeoutId),t.delete(c.requestId),c.type===l.responseType?l.resolve(c):l.reject(Error(String(c.error||`Unknown host error`))))}),e.autoConnect&&O().then(e=>{e||A()})}u()})();
|
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-DEgebuO2.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
let options;
|
|
@@ -14,7 +14,10 @@ if (options.help) {
|
|
|
14
14
|
process.exit(0);
|
|
15
15
|
}
|
|
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
|
-
const relay = new LocalRelayMcpServer({
|
|
17
|
+
const relay = new LocalRelayMcpServer({
|
|
18
|
+
bridgeOptions: options,
|
|
19
|
+
serverVersion: "6.0.0"
|
|
20
|
+
});
|
|
18
21
|
try {
|
|
19
22
|
await relay.start();
|
|
20
23
|
} catch (err) {
|
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\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});\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(0);\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');\n});\nprocess.on('unhandledRejection', (reason) => {\n const message = reason instanceof Error ? (reason.stack ?? reason.message) : String(reason);\n process.stderr.write(`[webmcp-local-relay] error: unhandled rejection: ${message}\\n`);\n void shutdown('unhandledRejection');\n});\n\nprocess.on('SIGINT', () => {\n void shutdown('SIGINT');\n});\nprocess.on('SIGTERM', () => {\n void shutdown('SIGTERM');\n});\n"],"mappings":";;;;
|
|
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(0);\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');\n});\nprocess.on('unhandledRejection', (reason) => {\n const message = reason instanceof Error ? (reason.stack ?? reason.message) : String(reason);\n process.stderr.write(`[webmcp-local-relay] error: unhandled rejection: ${message}\\n`);\n void shutdown('unhandledRejection');\n});\n\nprocess.on('SIGINT', () => {\n void shutdown('SIGINT');\n});\nprocess.on('SIGTERM', () => {\n void shutdown('SIGTERM');\n});\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
1
2
|
import { EventEmitter } from "node:events";
|
|
2
3
|
import { CallToolRequestParamsSchema, CallToolResultSchema, ToolAnnotationsSchema, ToolSchema } from "@modelcontextprotocol/core";
|
|
3
|
-
import { z } from "zod/v4";
|
|
4
4
|
import { CallToolResult, Tool, ToolAnnotations, Transport } from "@modelcontextprotocol/server";
|
|
5
5
|
|
|
6
6
|
//#region src/protocol.d.ts
|
|
@@ -1086,7 +1086,10 @@ declare function printHelp(): void;
|
|
|
1086
1086
|
/**
|
|
1087
1087
|
* Base options shared by all {@link LocalRelayMcpServer} configurations.
|
|
1088
1088
|
*/
|
|
1089
|
+
type BrowserLauncher = (command: string, args: readonly string[], callback: (cause: Error | null) => void) => void;
|
|
1089
1090
|
interface LocalRelayMcpServerBaseOptions {
|
|
1091
|
+
/** Platform launcher; override to run without opening a browser. */
|
|
1092
|
+
launchBrowser?: BrowserLauncher;
|
|
1090
1093
|
/**
|
|
1091
1094
|
* MCP server name reported during initialization.
|
|
1092
1095
|
*/
|
|
@@ -1118,6 +1121,7 @@ declare class LocalRelayMcpServer {
|
|
|
1118
1121
|
*/
|
|
1119
1122
|
readonly bridge: RelayBridgeServer;
|
|
1120
1123
|
private readonly mcpServer;
|
|
1124
|
+
private readonly launchBrowser;
|
|
1121
1125
|
private readonly dynamicTools;
|
|
1122
1126
|
private syncDebounceTimer;
|
|
1123
1127
|
private connected;
|
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-DEgebuO2.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 };
|