js-bridge-mcp 0.1.7 → 0.1.9
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/dist/client/main.js +22 -23
- package/dist/client/tool-bus.js +60 -0
- package/dist/dashboard/assets/index-CNPdV1i6.js +90 -0
- package/dist/dashboard/index.html +26 -0
- package/dist/src/server.js +12 -5
- package/package.json +6 -3
package/dist/client/main.js
CHANGED
|
@@ -4,29 +4,29 @@ function _(e) {
|
|
|
4
4
|
n.push(s), o.set(s.name, c);
|
|
5
5
|
return { manifest: n, fnByName: o };
|
|
6
6
|
}
|
|
7
|
-
const
|
|
7
|
+
const w = 1e4, f = 3600 * 1e3;
|
|
8
8
|
function $(e, n = {}) {
|
|
9
9
|
let o, c = !1, s = 0, p;
|
|
10
10
|
const m = () => {
|
|
11
11
|
const a = n.tenant ?? (location.pathname.startsWith("/t/") ? location.pathname.slice(3).split("/")[0] : ""), y = a ? `/ws?tenant=${encodeURIComponent(a)}` : "/ws", d = `${n.serverUrl ? n.serverUrl.replace(/^http/, "ws") : `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}`}${y}`;
|
|
12
12
|
console.log(`[mcp-ws] connecting (attempt ${s + 1}): ${d}`), o = new WebSocket(d), o.onopen = () => {
|
|
13
13
|
console.log(`[mcp-ws] connected${s > 0 ? ` after ${s} reconnect attempt(s)` : ""}`), s = 0, p = void 0, e.onConnect?.();
|
|
14
|
-
}, o.onclose = (
|
|
15
|
-
if (console.log(`[mcp-ws] disconnected: code=${
|
|
16
|
-
if (
|
|
14
|
+
}, o.onclose = (r) => {
|
|
15
|
+
if (console.log(`[mcp-ws] disconnected: code=${r.code} reason=${r.reason || "(none)"} wasClean=${r.wasClean}`), e.onDisconnect?.(), !c) {
|
|
16
|
+
if (r.code === 4404) {
|
|
17
17
|
console.log("[mcp-ws] invalid tenant id (4404) — not retrying");
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
if (p ??= Date.now(), Date.now() - p >
|
|
21
|
-
console.log(`[mcp-ws] giving up after retrying for over ${
|
|
20
|
+
if (p ??= Date.now(), Date.now() - p > f) {
|
|
21
|
+
console.log(`[mcp-ws] giving up after retrying for over ${f / 6e4} minutes`);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
s++, console.log(`[mcp-ws] retrying in ${
|
|
24
|
+
s++, console.log(`[mcp-ws] retrying in ${w / 1e3}s (attempt ${s + 1})`), setTimeout(m, w);
|
|
25
25
|
}
|
|
26
26
|
}, o.onerror = () => {
|
|
27
27
|
console.log("[mcp-ws] socket error (see close event for details)");
|
|
28
|
-
}, o.onmessage = (
|
|
29
|
-
const t = JSON.parse(
|
|
28
|
+
}, o.onmessage = (r) => {
|
|
29
|
+
const t = JSON.parse(r.data);
|
|
30
30
|
t.type === "init" && e.onInit?.(t.schema, t.state), t.type === "reinit" && e.onReinit?.(t.schema, t.state), t.type === "update" && e.onUpdate?.(t.field, t.value), t.type === "call" && e.onCall?.(t.id, t.name, t.args), t.type === "identify" && (e.onIdentify ? e.onIdentify(t.label) : alert(`Identify: this is the "${t.label ?? "unlabeled"}" connection`));
|
|
31
31
|
};
|
|
32
32
|
};
|
|
@@ -39,23 +39,22 @@ function $(e, n = {}) {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
const h = window.__mcpTools ?? [], { manifest:
|
|
43
|
-
let
|
|
44
|
-
function
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return e && (r = e), r;
|
|
42
|
+
const h = window.__mcpTools ?? [], { manifest: b, fnByName: C } = _(h), N = window.__mcpSummary ?? void 0, v = typeof window.__mcpAppName == "string" && window.__mcpAppName;
|
|
43
|
+
let i = window.__mcpAppName ?? document.title ?? void 0, g = !1;
|
|
44
|
+
function S() {
|
|
45
|
+
if (g || (g = !0, v)) return i;
|
|
46
|
+
const e = prompt("Name this MCP connection (used to identify it to the agent):", i ?? "");
|
|
47
|
+
return e && (i = e), i;
|
|
49
48
|
}
|
|
50
|
-
const u = new URL(import.meta.url),
|
|
49
|
+
const u = new URL(import.meta.url), R = u.searchParams.get("server") ?? void 0, U = u.searchParams.get("tenant") ?? void 0, l = $(
|
|
51
50
|
{
|
|
52
51
|
onConnect() {
|
|
53
|
-
const e =
|
|
54
|
-
console.log(`[js-bridge-mcp] connected as "${e ?? "(unlabeled)"}"`), l.send({ type: "register_tools", tools:
|
|
52
|
+
const e = S();
|
|
53
|
+
console.log(`[js-bridge-mcp] connected as "${e ?? "(unlabeled)"}"`), l.send({ type: "register_tools", tools: b, summary: N, appLabel: e });
|
|
55
54
|
},
|
|
56
55
|
async onCall(e, n, o) {
|
|
57
56
|
try {
|
|
58
|
-
const c =
|
|
57
|
+
const c = C.get(n);
|
|
59
58
|
if (!c) throw new Error(`no page tool named "${n}" — was it in window.__mcpTools when this script loaded?`);
|
|
60
59
|
const s = await c(o);
|
|
61
60
|
l.send({ type: "call_result", id: e, result: s });
|
|
@@ -67,9 +66,9 @@ const u = new URL(import.meta.url), S = u.searchParams.get("server") ?? void 0,
|
|
|
67
66
|
console.log("[js-bridge-mcp] disconnected, retrying...");
|
|
68
67
|
}
|
|
69
68
|
},
|
|
70
|
-
{ serverUrl:
|
|
69
|
+
{ serverUrl: R, tenant: U }
|
|
71
70
|
);
|
|
72
71
|
window.__mcpRename = (e) => {
|
|
73
|
-
const n = e ?? prompt("Rename this MCP connection:",
|
|
74
|
-
n && (
|
|
72
|
+
const n = e ?? prompt("Rename this MCP connection:", i ?? "");
|
|
73
|
+
n && (i = n, l.send({ type: "rename_connection", appLabel: n }), console.log(`[js-bridge-mcp] renamed connection to "${n}"`));
|
|
75
74
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Generic, provider-agnostic tool registry shared by any page embedding
|
|
2
|
+
// js-bridge-mcp. Deliberately NOT part of main.ts's build (see vite.config.ts's
|
|
3
|
+
// copy-tool-bus plugin) - this is infrastructure any embeddable provider
|
|
4
|
+
// (folderfoo today, others later) and any host app (bulletino today) can
|
|
5
|
+
// import by URL, independent of js-bridge-mcp's own bundle.
|
|
6
|
+
//
|
|
7
|
+
// Solves the load-order problem plain `window.__mcpTools` has: main.ts reads
|
|
8
|
+
// that array once, synchronously, at import time, so a provider script that
|
|
9
|
+
// finishes loading even a tick later is invisible to it. A host page that
|
|
10
|
+
// wants late-registering providers to work should:
|
|
11
|
+
// 1. import this file first,
|
|
12
|
+
// 2. import every provider script (each one calls registerProvider()),
|
|
13
|
+
// 3. merge window.__mcpToolBus.getTools() into window.__mcpTools itself,
|
|
14
|
+
// 4. only then import main.js.
|
|
15
|
+
// Usage: <script type="module" src="http://<js-bridge-mcp host>/tool-bus.js"></script>
|
|
16
|
+
window.__mcpToolBus ??= (() => {
|
|
17
|
+
const providers = new Map(); // providerName -> tool[]
|
|
18
|
+
const listeners = new Set();
|
|
19
|
+
|
|
20
|
+
function notify() {
|
|
21
|
+
for (const cb of listeners) cb();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getTools() {
|
|
25
|
+
// Auto-prefix on name collision across providers, mirroring
|
|
26
|
+
// js-bridge-mcp's own slug-suffix-on-collision pattern for connections.
|
|
27
|
+
const claimedBy = new Map(); // tool name -> providerName that claimed it first
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const [providerName, tools] of providers) {
|
|
30
|
+
for (const tool of tools) {
|
|
31
|
+
let name = tool.name;
|
|
32
|
+
if (claimedBy.has(name)) {
|
|
33
|
+
name = `${providerName}__${tool.name}`;
|
|
34
|
+
} else {
|
|
35
|
+
claimedBy.set(name, providerName);
|
|
36
|
+
}
|
|
37
|
+
out.push({ ...tool, name });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
// Re-registering under the same providerName replaces its previous tool
|
|
45
|
+
// set (no manual unregister needed for e.g. a folder-switch refresh).
|
|
46
|
+
registerProvider(providerName, tools) {
|
|
47
|
+
providers.set(providerName, tools);
|
|
48
|
+
notify();
|
|
49
|
+
return () => {
|
|
50
|
+
providers.delete(providerName);
|
|
51
|
+
notify();
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
getTools,
|
|
55
|
+
onChange(cb) {
|
|
56
|
+
listeners.add(cb);
|
|
57
|
+
return () => listeners.delete(cb);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
})();
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&s(r)}).observe(document,{childList:!0,subtree:!0});function e(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function s(i){if(i.ep)return;i.ep=!0;const o=e(i);fetch(i.href,o)}})();/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2019 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/const M=globalThis,L=M.ShadowRoot&&(M.ShadyCSS===void 0||M.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,D=Symbol(),q=new WeakMap;let st=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==D)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(L&&t===void 0){const s=e!==void 0&&e.length===1;s&&(t=q.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&q.set(e,t))}return t}toString(){return this.cssText}};const lt=n=>new st(typeof n=="string"?n:n+"",void 0,D),dt=(n,...t)=>{const e=n.length===1?n[0]:t.reduce((s,i,o)=>s+(r=>{if(r._$cssResult$===!0)return r.cssText;if(typeof r=="number")return r;throw Error("Value passed to 'css' function must be a 'css' function result: "+r+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+n[o+1],n[0]);return new st(e,n,D)},pt=(n,t)=>{if(L)n.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const e of t){const s=document.createElement("style"),i=M.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=e.cssText,n.appendChild(s)}},V=L?n=>n:n=>n instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return lt(e)})(n):n;/**
|
|
6
|
+
* @license
|
|
7
|
+
* Copyright 2017 Google LLC
|
|
8
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
9
|
+
*/const{is:ut,defineProperty:ft,getOwnPropertyDescriptor:$t,getOwnPropertyNames:_t,getOwnPropertySymbols:mt,getPrototypeOf:gt}=Object,k=globalThis,J=k.trustedTypes,yt=J?J.emptyScript:"",bt=k.reactiveElementPolyfillSupport,E=(n,t)=>n,j={toAttribute(n,t){switch(t){case Boolean:n=n?yt:null;break;case Object:case Array:n=n==null?n:JSON.stringify(n)}return n},fromAttribute(n,t){let e=n;switch(t){case Boolean:e=n!==null;break;case Number:e=n===null?null:Number(n);break;case Object:case Array:try{e=JSON.parse(n)}catch{e=null}}return e}},it=(n,t)=>!ut(n,t),K={attribute:!0,type:String,converter:j,reflect:!1,useDefault:!1,hasChanged:it};Symbol.metadata??=Symbol("metadata"),k.litPropertyMetadata??=new WeakMap;let y=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=K){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const s=Symbol(),i=this.getPropertyDescriptor(t,s,e);i!==void 0&&ft(this.prototype,t,i)}}static getPropertyDescriptor(t,e,s){const{get:i,set:o}=$t(this.prototype,t)??{get(){return this[e]},set(r){this[e]=r}};return{get:i,set(r){const c=i?.call(this);o?.call(this,r),this.requestUpdate(t,c,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??K}static _$Ei(){if(this.hasOwnProperty(E("elementProperties")))return;const t=gt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(E("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(E("properties"))){const e=this.properties,s=[..._t(e),...mt(e)];for(const i of s)this.createProperty(i,e[i])}const t=this[Symbol.metadata];if(t!==null){const e=litPropertyMetadata.get(t);if(e!==void 0)for(const[s,i]of e)this.elementProperties.set(s,i)}this._$Eh=new Map;for(const[e,s]of this.elementProperties){const i=this._$Eu(e,s);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const s=new Set(t.flat(1/0).reverse());for(const i of s)e.unshift(V(i))}else t!==void 0&&e.push(V(t));return e}static _$Eu(t,e){const s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return pt(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){const s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){const o=(s.converter?.toAttribute!==void 0?s.converter:j).toAttribute(e,s.type);this._$Em=t,o==null?this.removeAttribute(i):this.setAttribute(i,o),this._$Em=null}}_$AK(t,e){const s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){const o=s.getPropertyOptions(i),r=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:j;this._$Em=i;const c=r.fromAttribute(e,o.type);this[i]=c??this._$Ej?.get(i)??c,this._$Em=null}}requestUpdate(t,e,s,i=!1,o){if(t!==void 0){const r=this.constructor;if(i===!1&&(o=this[t]),s??=r.getPropertyOptions(t),!((s.hasChanged??it)(o,e)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(r._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:i,wrapped:o},r){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??e??this[t]),o!==!0||r!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[i,o]of this._$Ep)this[i]=o;this._$Ep=void 0}const s=this.constructor.elementProperties;if(s.size>0)for(const[i,o]of s){const{wrapped:r}=o,c=this[i];r!==!0||this._$AL.has(i)||c===void 0||this.C(i,void 0,o,c)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};y.elementStyles=[],y.shadowRootOptions={mode:"open"},y[E("elementProperties")]=new Map,y[E("finalized")]=new Map,bt?.({ReactiveElement:y}),(k.reactiveElementVersions??=[]).push("2.1.2");/**
|
|
10
|
+
* @license
|
|
11
|
+
* Copyright 2017 Google LLC
|
|
12
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
13
|
+
*/const I=globalThis,Z=n=>n,H=I.trustedTypes,F=H?H.createPolicy("lit-html",{createHTML:n=>n}):void 0,nt="$lit$",$=`lit$${Math.random().toFixed(9).slice(2)}$`,ot="?"+$,vt=`<${ot}>`,g=document,C=()=>g.createComment(""),P=n=>n===null||typeof n!="object"&&typeof n!="function",B=Array.isArray,At=n=>B(n)||typeof n?.[Symbol.iterator]=="function",T=`[
|
|
14
|
+
\f\r]`,x=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Q=/-->/g,G=/>/g,_=RegExp(`>|${T}(?:([^\\s"'>=/]+)(${T}*=${T}*(?:[^
|
|
15
|
+
\f\r"'\`<>=]|("|')|))|$)`,"g"),X=/'/g,Y=/"/g,rt=/^(?:script|style|textarea|title)$/i,xt=n=>(t,...e)=>({_$litType$:n,strings:t,values:e}),S=xt(1),b=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),tt=new WeakMap,m=g.createTreeWalker(g,129);function at(n,t){if(!B(n)||!n.hasOwnProperty("raw"))throw Error("invalid template strings array");return F!==void 0?F.createHTML(t):t}const St=(n,t)=>{const e=n.length-1,s=[];let i,o=t===2?"<svg>":t===3?"<math>":"",r=x;for(let c=0;c<e;c++){const a=n[c];let l,p,h=-1,u=0;for(;u<a.length&&(r.lastIndex=u,p=r.exec(a),p!==null);)u=r.lastIndex,r===x?p[1]==="!--"?r=Q:p[1]!==void 0?r=G:p[2]!==void 0?(rt.test(p[2])&&(i=RegExp("</"+p[2],"g")),r=_):p[3]!==void 0&&(r=_):r===_?p[0]===">"?(r=i??x,h=-1):p[1]===void 0?h=-2:(h=r.lastIndex-p[2].length,l=p[1],r=p[3]===void 0?_:p[3]==='"'?Y:X):r===Y||r===X?r=_:r===Q||r===G?r=x:(r=_,i=void 0);const f=r===_&&n[c+1].startsWith("/>")?" ":"";o+=r===x?a+vt:h>=0?(s.push(l),a.slice(0,h)+nt+a.slice(h)+$+f):a+$+(h===-2?c:f)}return[at(n,o+(n[e]||"<?>")+(t===2?"</svg>":t===3?"</math>":"")),s]};class U{constructor({strings:t,_$litType$:e},s){let i;this.parts=[];let o=0,r=0;const c=t.length-1,a=this.parts,[l,p]=St(t,e);if(this.el=U.createElement(l,s),m.currentNode=this.el.content,e===2||e===3){const h=this.el.content.firstChild;h.replaceWith(...h.childNodes)}for(;(i=m.nextNode())!==null&&a.length<c;){if(i.nodeType===1){if(i.hasAttributes())for(const h of i.getAttributeNames())if(h.endsWith(nt)){const u=p[r++],f=i.getAttribute(h).split($),N=/([.?@])?(.*)/.exec(u);a.push({type:1,index:o,name:N[2],strings:f,ctor:N[1]==="."?wt:N[1]==="?"?Ct:N[1]==="@"?Pt:R}),i.removeAttribute(h)}else h.startsWith($)&&(a.push({type:6,index:o}),i.removeAttribute(h));if(rt.test(i.tagName)){const h=i.textContent.split($),u=h.length-1;if(u>0){i.textContent=H?H.emptyScript:"";for(let f=0;f<u;f++)i.append(h[f],C()),m.nextNode(),a.push({type:2,index:++o});i.append(h[u],C())}}}else if(i.nodeType===8)if(i.data===ot)a.push({type:2,index:o});else{let h=-1;for(;(h=i.data.indexOf($,h+1))!==-1;)a.push({type:7,index:o}),h+=$.length-1}o++}}static createElement(t,e){const s=g.createElement("template");return s.innerHTML=t,s}}function v(n,t,e=n,s){if(t===b)return t;let i=s!==void 0?e._$Co?.[s]:e._$Cl;const o=P(t)?void 0:t._$litDirective$;return i?.constructor!==o&&(i?._$AO?.(!1),o===void 0?i=void 0:(i=new o(n),i._$AT(n,e,s)),s!==void 0?(e._$Co??=[])[s]=i:e._$Cl=i),i!==void 0&&(t=v(n,i._$AS(n,t.values),i,s)),t}class Et{constructor(t,e){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:e},parts:s}=this._$AD,i=(t?.creationScope??g).importNode(e,!0);m.currentNode=i;let o=m.nextNode(),r=0,c=0,a=s[0];for(;a!==void 0;){if(r===a.index){let l;a.type===2?l=new O(o,o.nextSibling,this,t):a.type===1?l=new a.ctor(o,a.name,a.strings,this,t):a.type===6&&(l=new Ut(o,this,t)),this._$AV.push(l),a=s[++c]}r!==a?.index&&(o=m.nextNode(),r++)}return m.currentNode=g,i}p(t){let e=0;for(const s of this._$AV)s!==void 0&&(s.strings!==void 0?(s._$AI(t,s,e),e+=s.strings.length-2):s._$AI(t[e])),e++}}class O{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,e,s,i){this.type=2,this._$AH=d,this._$AN=void 0,this._$AA=t,this._$AB=e,this._$AM=s,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let t=this._$AA.parentNode;const e=this._$AM;return e!==void 0&&t?.nodeType===11&&(t=e.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,e=this){t=v(this,t,e),P(t)?t===d||t==null||t===""?(this._$AH!==d&&this._$AR(),this._$AH=d):t!==this._$AH&&t!==b&&this._(t):t._$litType$!==void 0?this.$(t):t.nodeType!==void 0?this.T(t):At(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==d&&P(this._$AH)?this._$AA.nextSibling.data=t:this.T(g.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:s}=t,i=typeof s=="number"?this._$AC(t):(s.el===void 0&&(s.el=U.createElement(at(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===i)this._$AH.p(e);else{const o=new Et(i,this),r=o.u(this.options);o.p(e),this.T(r),this._$AH=o}}_$AC(t){let e=tt.get(t.strings);return e===void 0&&tt.set(t.strings,e=new U(t)),e}k(t){B(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let s,i=0;for(const o of t)i===e.length?e.push(s=new O(this.O(C()),this.O(C()),this,this.options)):s=e[i],s._$AI(o),i++;i<e.length&&(this._$AR(s&&s._$AB.nextSibling,i),e.length=i)}_$AR(t=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);t!==this._$AB;){const s=Z(t).nextSibling;Z(t).remove(),t=s}}setConnected(t){this._$AM===void 0&&(this._$Cv=t,this._$AP?.(t))}}class R{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,e,s,i,o){this.type=1,this._$AH=d,this._$AN=void 0,this.element=t,this.name=e,this._$AM=i,this.options=o,s.length>2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d}_$AI(t,e=this,s,i){const o=this.strings;let r=!1;if(o===void 0)t=v(this,t,e,0),r=!P(t)||t!==this._$AH&&t!==b,r&&(this._$AH=t);else{const c=t;let a,l;for(t=o[0],a=0;a<o.length-1;a++)l=v(this,c[s+a],e,a),l===b&&(l=this._$AH[a]),r||=!P(l)||l!==this._$AH[a],l===d?t=d:t!==d&&(t+=(l??"")+o[a+1]),this._$AH[a]=l}r&&!i&&this.j(t)}j(t){t===d?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"")}}class wt extends R{constructor(){super(...arguments),this.type=3}j(t){this.element[this.name]=t===d?void 0:t}}class Ct extends R{constructor(){super(...arguments),this.type=4}j(t){this.element.toggleAttribute(this.name,!!t&&t!==d)}}class Pt extends R{constructor(t,e,s,i,o){super(t,e,s,i,o),this.type=5}_$AI(t,e=this){if((t=v(this,t,e,0)??d)===b)return;const s=this._$AH,i=t===d&&s!==d||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,o=t!==d&&(s===d||i);i&&this.element.removeEventListener(this.name,this,s),o&&this.element.addEventListener(this.name,this,t),this._$AH=t}handleEvent(t){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t)}}class Ut{constructor(t,e,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=e,this.options=s}get _$AU(){return this._$AM._$AU}_$AI(t){v(this,t)}}const Ot=I.litHtmlPolyfillSupport;Ot?.(U,O),(I.litHtmlVersions??=[]).push("3.3.3");const Nt=(n,t,e)=>{const s=e?.renderBefore??t;let i=s._$litPart$;if(i===void 0){const o=e?.renderBefore??null;s._$litPart$=i=new O(t.insertBefore(C(),o),o,void 0,e??{})}return i._$AI(n),i};/**
|
|
16
|
+
* @license
|
|
17
|
+
* Copyright 2017 Google LLC
|
|
18
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
19
|
+
*/const W=globalThis;class w extends y{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=Nt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return b}}w._$litElement$=!0,w.finalized=!0,W.litElementHydrateSupport?.({LitElement:w});const Mt=W.litElementPolyfillSupport;Mt?.({LitElement:w});(W.litElementVersions??=[]).push("4.2.2");var ht;let z=0;const et=new Set;function Ht(n){z++;try{n()}finally{z--,z===0&&kt()}}function kt(){const n=new Set(et);et.clear();for(const t of n)t()}function Rt(n,t){return n!=n?t==t:n!==t||n&&typeof n=="object"||typeof n=="function"}function Tt(n){return n&&typeof n.deref=="function"}class ct{static#e=0;#t;#s;#i=new Set;#n=ht.#e++;name;constructor(t,e){this.#t=t,this.#s=t,this.name=e}_get(){return A.activeConsumer&&A.activeConsumer.track(this),this.#t}_set(t){return Rt(t,this.#t)?(this.#s=this.#t,this.#t=t,this._notify(),!0):!1}subscribe(t,e=!1){let s=t;return e&&(s=new WeakRef(t)),this.#i.add(s),()=>this.#i.delete(s)}_notify(){const t=[...this.#i];for(const e of t){let s;if(Tt(e)){if(s=e.deref(),!s){this.#i.delete(e);continue}}else s=e;s()}}get id(){return this.#n}get previousValue(){return this.#s}toString(){return this.name?`${this.constructor.name}(${this.name})`:`${this.constructor.name}#${this.id}`}}ht=ct;class A extends ct{static#e=[];constructor(t,e){super(t,e)}static get activeConsumer(){return this.#e[this.#e.length-1]}static push(t){this.#e.push(t)}static pop(){this.#e.pop()}get(){return this._get()}set(t){Ht(()=>{this._set(t)})}get value(){return this.get()}set value(t){this.set(t)}update(t){this.set(t(this.get()))}}class zt{#e;#t=new Map;_debugParent;constructor(t){this.#e=t,t.addController(this);const e=t,s=e.update,i=this;e.update=function(o){A.push(i);try{s.call(this,o)}finally{A.pop()}}}track(t){this.#t.has(t)||this.#t.set(t,t.subscribe(this.#s,!0))}#s=()=>{this.#e.requestUpdate()};hostUpdate(){for(const t of this.#t.values())t();this.#t.clear()}hostDisconnected(){for(const t of this.#t.values())t();this.#t.clear()}toString(){const t=this.#e;return`SignalWatcher(${t?.localName??t?.tagName??t?.constructor?.name??"unknown"})`}}function jt(n){const t=Date.now()-n,e=Math.floor(t/6e4);if(e<1)return"just now";if(e<60)return`${e}m ago`;const s=Math.floor(e/60);return s<24?`${s}h ago`:`${Math.floor(s/24)}d ago`}class Lt extends w{static styles=dt`
|
|
20
|
+
:host { display: block; min-height: 100vh; padding: 24px; box-sizing: border-box; }
|
|
21
|
+
h1 { font-size: 16px; margin: 0 0 4px; }
|
|
22
|
+
.subtitle { font-size: 12px; opacity: 0.6; margin: 0 0 20px; }
|
|
23
|
+
.empty {
|
|
24
|
+
padding: 40px 20px; text-align: center; opacity: 0.6; font-size: 13px;
|
|
25
|
+
border: 1px dashed var(--border-strong); border-radius: 8px;
|
|
26
|
+
}
|
|
27
|
+
.channel {
|
|
28
|
+
border: 1px solid var(--border); border-radius: 8px; margin-bottom: 12px; overflow: hidden;
|
|
29
|
+
}
|
|
30
|
+
.channel-header {
|
|
31
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
|
|
32
|
+
padding: 10px 14px; background: var(--bg-subtle); border-bottom: 1px solid var(--border);
|
|
33
|
+
}
|
|
34
|
+
.channel-name { font-family: ui-monospace, monospace; font-size: 13px; font-weight: 700; }
|
|
35
|
+
.channel-meta { font-size: 11px; opacity: 0.6; }
|
|
36
|
+
.connections { display: flex; flex-direction: column; }
|
|
37
|
+
.connection-row {
|
|
38
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 14px;
|
|
39
|
+
border-bottom: 1px solid var(--border);
|
|
40
|
+
}
|
|
41
|
+
.connection-row:last-child { border-bottom: none; }
|
|
42
|
+
.connection-dot {
|
|
43
|
+
width: 8px; height: 8px; border-radius: 50%; background: #22c55e; flex-shrink: 0;
|
|
44
|
+
}
|
|
45
|
+
.connection-label { font-size: 13px; font-weight: 600; flex: 0 0 auto; }
|
|
46
|
+
.connection-summary {
|
|
47
|
+
font-size: 12px; opacity: 0.6; flex: 1 1 auto; min-width: 0;
|
|
48
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
49
|
+
}
|
|
50
|
+
.connection-tools { font-size: 11px; opacity: 0.6; flex: 0 0 auto; }
|
|
51
|
+
.no-connections { padding: 10px 14px; font-size: 12px; opacity: 0.5; font-style: italic; }
|
|
52
|
+
.identify-btn {
|
|
53
|
+
flex: 0 0 auto; font-size: 11px; padding: 4px 10px; border-radius: 6px;
|
|
54
|
+
border: 1px solid var(--border-strong); background: var(--bg); color: inherit; cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
.identify-btn:hover { background: var(--hover); border-color: var(--accent); }
|
|
57
|
+
.identify-btn:active { background: var(--accent-tint); }
|
|
58
|
+
.identify-btn.sent { border-color: var(--accent); color: var(--accent); }
|
|
59
|
+
.conn-count { font-size: 11px; padding: 1px 7px; border-radius: 999px; background: var(--hover); opacity: 0.75; }
|
|
60
|
+
`;#e=new A([]);#t;#s=new A(new Set);constructor(){super(),new zt(this)}connectedCallback(){super.connectedCallback(),this.#t=new EventSource("/api/dashboard/stream"),this.#t.onmessage=t=>{try{this.#e.set(JSON.parse(t.data))}catch{}}}disconnectedCallback(){super.disconnectedCallback(),this.#t?.close()}async#i(t,e){const s=`${t}::${e}`;try{await fetch(`/api/dashboard/channels/${encodeURIComponent(t)}/connections/${encodeURIComponent(e)}/identify`,{method:"POST"})}catch{return}const i=new Set(this.#s.value);i.add(s),this.#s.set(i),setTimeout(()=>{const o=new Set(this.#s.value);o.delete(s),this.#s.set(o)},1200)}render(){const t=this.#e.value;return S`
|
|
61
|
+
<h1>Connected apps</h1>
|
|
62
|
+
<p class="subtitle">Live channels and bridged browser tabs — updates automatically.</p>
|
|
63
|
+
${t.length===0?S`<div class="empty">No channels yet. A channel appears here once an agent calls join_channel, or a page connects and lands on the default channel.</div>`:t.map(e=>this.#n(e))}
|
|
64
|
+
`}#n(t){return S`
|
|
65
|
+
<div class="channel">
|
|
66
|
+
<div class="channel-header">
|
|
67
|
+
<span class="channel-name">${t.channel}</span>
|
|
68
|
+
<span class="channel-meta">
|
|
69
|
+
<span class="conn-count">${t.connections.length} connection${t.connections.length===1?"":"s"}</span>
|
|
70
|
+
· active ${jt(t.lastActivityAt)}
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="connections">
|
|
74
|
+
${t.connections.length===0?S`<div class="no-connections">No tabs currently bridged into this channel.</div>`:t.connections.map(e=>{const s=`${t.channel}::${e.id}`,i=this.#s.value.has(s);return S`
|
|
75
|
+
<div class="connection-row">
|
|
76
|
+
<span class="connection-dot"></span>
|
|
77
|
+
<span class="connection-label">${e.label??"(unlabeled)"}</span>
|
|
78
|
+
<span class="connection-summary">${e.summary??""}</span>
|
|
79
|
+
<span class="connection-tools">${e.toolCount} tool${e.toolCount===1?"":"s"}</span>
|
|
80
|
+
<button
|
|
81
|
+
class="identify-btn ${i?"sent":""}"
|
|
82
|
+
@click=${()=>this.#i(t.channel,e.id)}
|
|
83
|
+
>
|
|
84
|
+
${i?"Sent ✓":"Identify"}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
`})}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
`}}customElements.define("dashboard-app",Lt);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>js-bridge-mcp dashboard</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root {
|
|
8
|
+
color-scheme: light dark;
|
|
9
|
+
--border: light-dark(#0000001f, #ffffff2e);
|
|
10
|
+
--border-strong: light-dark(#00000038, #ffffff4a);
|
|
11
|
+
--hover: light-dark(#00000010, #ffffff14);
|
|
12
|
+
--bg: light-dark(#ffffff, #1a1a1a);
|
|
13
|
+
--bg-subtle: light-dark(#00000008, #ffffff0d);
|
|
14
|
+
--fg: light-dark(#111111, #f0f0f0);
|
|
15
|
+
--accent: light-dark(#2563eb, #3b82f6);
|
|
16
|
+
--accent-tint: light-dark(#2563eb11, #3b82f622);
|
|
17
|
+
}
|
|
18
|
+
body { font-family: system-ui, sans-serif; margin: 0; background: var(--bg); color: var(--fg); }
|
|
19
|
+
</style>
|
|
20
|
+
<script type="module" crossorigin src="/assets/index-CNPdV1i6.js"></script>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<dashboard-app></dashboard-app>
|
|
24
|
+
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
package/dist/src/server.js
CHANGED
|
@@ -14,17 +14,23 @@ const getOrCreateTenant = (id) => getOrCreateTenantFor(id, undefined, { ...initi
|
|
|
14
14
|
// The 'default' tenant backs direct access with no explicit ?tenant= param.
|
|
15
15
|
getOrCreateTenant('default');
|
|
16
16
|
startIdleSweep((id) => console.error(`[mcp] sweeping idle tenant: ${id}`));
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
17
|
+
// main.js is fetched from here by absolute path (get_embed_snippet always
|
|
18
|
+
// embeds "<server>/main.js", see hello-tools.ts) regardless of what's
|
|
19
|
+
// mounted at "/" — the legacy page it's injected into is hosted separately
|
|
20
|
+
// (see legacy-page/, run via `npm run start:static`), not by this server.
|
|
21
|
+
// "/" itself serves the connected-apps dashboard (dist/dashboard) instead,
|
|
22
|
+
// so visiting the server's own URL shows something useful rather than
|
|
23
|
+
// "Not found".
|
|
24
|
+
const CLIENT_DIR = path.join(packageRoot, 'dist', 'client');
|
|
25
|
+
const DASHBOARD_DIR = path.join(packageRoot, 'dist', 'dashboard');
|
|
21
26
|
const httpServer = createHttpServer({
|
|
22
27
|
port: PORT,
|
|
23
|
-
staticDir:
|
|
28
|
+
staticDir: DASHBOARD_DIR,
|
|
24
29
|
initialSchema: undefined,
|
|
25
30
|
initialValues: initialHelloState,
|
|
26
31
|
identity: { name: 'js-bridge-mcp', version: '0.1.0' },
|
|
27
32
|
registerFn: registerHelloTools,
|
|
33
|
+
extraStaticMounts: { '/main.js': CLIENT_DIR },
|
|
28
34
|
// js-bridge-mcp typically bridges a single browser page per server; MCP
|
|
29
35
|
// clients aren't expected to pin ?tenant= themselves (some, like VS Code
|
|
30
36
|
// Copilot, open a fresh MCP session with no ?tenant= on every
|
|
@@ -36,6 +42,7 @@ const httpServer = createHttpServer({
|
|
|
36
42
|
attachWebSocketServer(httpServer, PORT, undefined, initialHelloState);
|
|
37
43
|
httpServer.listen(PORT, () => {
|
|
38
44
|
console.error(`[js-bridge-mcp] MCP + bridge server listening on http://localhost:${PORT}`);
|
|
45
|
+
console.error(`[js-bridge-mcp] dashboard: http://localhost:${PORT}`);
|
|
39
46
|
console.error(`[js-bridge-mcp] serve legacy-page/hello-world.html separately: npm run start:static`);
|
|
40
47
|
});
|
|
41
48
|
export { getOrCreateTenant, tenants, httpServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js-bridge-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Generic bridge: exposes MCP tools discovered from a connected page's own JSON tool manifest, dispatched over a cross-origin WebSocket. Ships a hello-world example page under legacy-page/.",
|
|
6
6
|
"repository": {
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
|
-
"dev": "npm-run-all --parallel dev:client dev:mcp",
|
|
25
|
+
"dev": "npm-run-all --parallel dev:client dev:dashboard dev:mcp",
|
|
26
26
|
"dev:client": "vite build --watch",
|
|
27
|
+
"dev:dashboard": "vite build --watch --config vite.dashboard.config.ts",
|
|
27
28
|
"dev:mcp": "tsx watch src/server.ts",
|
|
28
|
-
"build": "tsc -p tsconfig.build.json && vite build",
|
|
29
|
+
"build": "tsc -p tsconfig.build.json && vite build && vite build --config vite.dashboard.config.ts",
|
|
29
30
|
"prestart:mcp": "npm run build",
|
|
30
31
|
"start:mcp": "tsx src/server.ts",
|
|
31
32
|
"start:static": "http-server legacy-page --cors -p 8080",
|
|
@@ -37,6 +38,8 @@
|
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"mcp-tenant-lib": "^0.3.3",
|
|
39
40
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
41
|
+
"avosignals": "^1.0.16",
|
|
42
|
+
"lit": "^3.3.3",
|
|
40
43
|
"ws": "^8.18.0",
|
|
41
44
|
"zod": "^3.23.8"
|
|
42
45
|
},
|