js-bridge-mcp 0.1.0 → 0.1.6

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 CHANGED
@@ -30,6 +30,18 @@ to write one for a new page.
30
30
 
31
31
  ## Run it
32
32
 
33
+ ### Via npx
34
+
35
+ ```bash
36
+ npx js-bridge-mcp
37
+ ```
38
+
39
+ Starts the MCP + WS + `main.js` server on port 8766. Point an MCP client at
40
+ `http://localhost:8766/mcp`, then serve `legacy-page/hello-world.html` (or
41
+ your own page) with any static file server.
42
+
43
+ ### From this repo
44
+
33
45
  ```bash
34
46
  npm run build # bundle src/client/main.ts -> dist/client/main.js
35
47
  npm run start:mcp # MCP + WS + main.js server, port 8766
@@ -1,26 +1,26 @@
1
1
  import { z } from 'zod';
2
2
  const getEmbedSnippet = {
3
3
  name: 'get_embed_snippet',
4
- description: 'Returns a single line of executable JavaScript that connects the current page to this ' +
5
- 'MCP session\'s tenant. Primary use: paste it directly into the browser\'s DevTools console ' +
6
- '(Chrome/Firefox/etc.) on the target page and press enter no editing the page\'s source ' +
7
- 'required. It can also be wrapped in a <script type="module">...</script> tag if the user ' +
8
- 'wants to bake it into the page\'s HTML instead (e.g. right before </body>). Either way, once ' +
9
- 'it runs, the page pushes its own tool manifest (window.__mcpTools, plus an optional ' +
10
- 'window.__mcpSummary string with shared cross-tool context — see the describe_tools tool once ' +
11
- 'connected) to this session\'s tenant, and the tools it declares become available to call from ' +
12
- 'THIS conversation. By default this server shares one tenant across every MCP session that ' +
13
- 'doesn\'t explicitly pin one (this keeps a single browser connection stable across an agent ' +
14
- 'harness\'s own session churn) so unless the server was started with per-session tenants, ' +
15
- 'pages bridged from different conversations end up on the same tenant and see each other\'s ' +
16
- 'tools rather than staying isolated. Running the snippet immediately opens a browser prompt() ' +
17
- 'asking the user to name this connection (pre-filled with the page title) warn the user ' +
18
- 'about this pop-up before they paste it so it isn\'t a surprise, and know that ' +
19
- 'dismissing/cancelling it is safe (falls back to the page title, connection proceeds either ' +
20
- 'way). The chosen name becomes this connection\'s tool-name prefix once a second connection ' +
21
- 'joins the same tenant (see describe_tools). Share the returned snippet with the user and tell ' +
22
- 'them to open DevTools on the target page, go to the Console tab, paste it, and press enter; do ' +
23
- 'not construct this URL by hand.',
4
+ description: 'Returns a single line of executable JavaScript that connects the current page to this MCP session\'s ' +
5
+ 'channel. Paste it directly into the browser DevTools console on the target page and press enter — no ' +
6
+ 'source edits needed (or wrap in <script type="module">...</script> before </body> to bake it into the ' +
7
+ 'page). Once run, the page pushes its tool manifest (window.__mcpTools, optional window.__mcpSummary see ' +
8
+ 'describe_tools) to this session\'s channel, and its tools become callable from THIS conversation. Share ' +
9
+ 'the snippet and tell the user to paste it in DevTools\' Console tab and press enter; do not construct the ' +
10
+ 'URL by hand. ' +
11
+ '\n\n' +
12
+ 'DEFAULT TO calling join_channel with a topic-scoped name (e.g. "bulletino", from the page/app or task) ' +
13
+ 'BEFORE this the normal path, not something reserved for when it happens to matter. Skip only for a ' +
14
+ 'genuinely one-off bridge, or when nothing suggests a name worth giving it: staying on "default" means ' +
15
+ 'sharing state with every other unnamed session on this server, and blocks another agent from reconnecting ' +
16
+ 'to the SAME bridged page later by name. The snippet is generated fresh from whatever channel this session ' +
17
+ 'is on, so join first, then call this. Reusing an already-generated snippet in a second tab connects that ' +
18
+ 'tab to the SAME channel, not a new one join a different name and call this again for a distinct one. ' +
19
+ '\n\n' +
20
+ 'Running the snippet opens a browser prompt() asking the user to name this connection (pre-filled with the ' +
21
+ 'page title) warn them before they paste it; dismissing is safe (falls back to the page title). That ' +
22
+ 'name becomes this connection\'s tool-name prefix once a second connection joins the same channel (see ' +
23
+ 'describe_tools).',
24
24
  schema: {},
25
25
  handler: async (_args, tenant, port) => {
26
26
  const tenantId = tenant().id;
@@ -1,6 +1,7 @@
1
- import { createManifestToolRegistry } from 'mcp-tenant-lib';
1
+ import { createManifestToolRegistry, registerChannelTools } from 'mcp-tenant-lib';
2
+ import { initialHelloState } from '../types.js';
2
3
  import { helloTools } from './hello-tools.js';
3
- export function registerHelloTools(mcp, tenant, port) {
4
+ export function registerHelloTools(mcp, tenant, port, setChannel) {
4
5
  for (const tool of helloTools) {
5
6
  mcp.tool(tool.name, tool.description, tool.schema, (args) => tool.handler(args, tenant, port));
6
7
  }
@@ -11,6 +12,26 @@ export function registerHelloTools(mcp, tenant, port) {
11
12
  // multiple concurrent MCP sessions' McpServer/registry pairs can be
12
13
  // bound to the same tenant at once and all need to stay in sync, not
13
14
  // just whichever one connected last.
14
- tenant().addManifestToolRegistry(registry);
15
- mcp.server.onclose = () => tenant().removeManifestToolRegistry(registry);
15
+ //
16
+ // Track which Tenant object the registry is currently subscribed to so a
17
+ // later join_channel (see the wrapped setChannel below) can move the
18
+ // subscription — tenant() is a live accessor that starts pointing at a
19
+ // different Tenant once the session retargets to a named channel, but the
20
+ // subscription itself does NOT follow automatically: addManifestToolRegistry
21
+ // only runs once here, against whichever tenant was current at connect
22
+ // time. Without migrating it, a page that bridges into a channel AFTER
23
+ // join_channel was called would push its manifest to a tenant nothing is
24
+ // subscribed to — describe_tools would still see it (it reads
25
+ // tenant().toolManifest fresh every call), but no real MCP tool would ever
26
+ // get registered for it.
27
+ let subscribedTenant = tenant();
28
+ subscribedTenant.addManifestToolRegistry(registry);
29
+ mcp.server.onclose = () => subscribedTenant.removeManifestToolRegistry(registry);
30
+ const setChannelAndMigrateRegistry = (id) => {
31
+ setChannel(id);
32
+ subscribedTenant.removeManifestToolRegistry(registry);
33
+ subscribedTenant = tenant();
34
+ subscribedTenant.addManifestToolRegistry(registry);
35
+ };
36
+ registerChannelTools(mcp, tenant, port, setChannelAndMigrateRegistry, undefined, { ...initialHelloState });
16
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-bridge-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.6",
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": {
@@ -35,7 +35,7 @@
35
35
  "test": "node --import tsx --test test/**/*.test.ts"
36
36
  },
37
37
  "dependencies": {
38
- "mcp-tenant-lib": "^0.1.0",
38
+ "mcp-tenant-lib": "^0.3.3",
39
39
  "@modelcontextprotocol/sdk": "^1.12.0",
40
40
  "ws": "^8.18.0",
41
41
  "zod": "^3.23.8"