@oimdb/mcp 1.2.0 → 3.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 CHANGED
@@ -1,59 +1,19 @@
1
1
  # @oimdb/mcp
2
2
 
3
- Connects AI assistants to a live OIMDB store running in the browser. Provides MCP tools for Claude Code and an optional CDP inspector for any AI that can run a shell command.
3
+ The MCP server for OIMDB. It gives an AI assistant the tools `oimdb_inspect`, `oimdb_dump`,
4
+ `oimdb_collection` and `oimdb_computed` over a live browser tab or an in-process model. It is built on
5
+ `@oimdb/cli`, which answers the same four questions from a shell: an assistant that can run a command
6
+ can use `npx @oimdb/cli inspect` instead.
4
7
 
5
- 📖 **[Full documentation](https://abaikov.github.io/oimdb/)**
8
+ 📖 **[Full documentation](https://oimdb.org/)**
6
9
 
7
10
  ## For AI Assistants
8
11
 
9
- If you are an AI assistant and this package is present in the project, you can get live OIMDB state from the running browser tab.
12
+ **Check whether MCP is already configured.** Look for `mcpServers.oimdb` in `.claude/settings.json` or
13
+ `~/.claude/settings.json`. If it is there, call `oimdb_inspect` now to see the current state.
10
14
 
11
- **Check if MCP is already configured** — look for `mcpServers.oimdb` in `.claude/settings.json` or `~/.claude/settings.json`. If it is, the tools `oimdb_inspect`, `oimdb_dump`, `oimdb_collection`, and `oimdb_computed` are available. Call `oimdb_inspect` now to see the current state.
12
-
13
- **If MCP is not configured yet**, add this to the project's MCP config:
14
-
15
- - Claude Code: `.claude/settings.json`
16
- - Cursor: `.cursor/mcp.json`
17
-
18
- ```json
19
- {
20
- "mcpServers": {
21
- "oimdb": {
22
- "command": "npx",
23
- "args": ["-y", "@oimdb/mcp"]
24
- }
25
- }
26
- }
27
- ```
28
-
29
- Then ask the user to:
30
- 1. Reload Claude Code so the MCP server starts
31
- 2. Make sure `debug.ts` calls `registry.connect()` and the app is running in the browser
32
-
33
- **If the user just wants a one-shot snapshot** without MCP setup, run this via Bash (requires Chrome launched with `--remote-debugging-port=9222`):
34
-
35
- ```bash
36
- npx @oimdb/mcp cdp
37
- ```
38
-
39
- ## Human Setup
40
-
41
- ### Standalone DevTools UI (for humans)
42
-
43
- ```bash
44
- npx @oimdb/mcp devtools
45
- ```
46
-
47
- Opens `http://localhost:7433` automatically. Shows live collections and computeds from the running browser tab. No Chrome extension needed.
48
-
49
- ```bash
50
- OIMDB_WS_PORT=7433 OIMDB_UI_PORT=7434 npx @oimdb/mcp devtools # custom ports
51
- npx @oimdb/mcp devtools --no-open # don't open browser
52
- ```
53
-
54
- ### 1. Add MCP config (for AI tools)
55
-
56
- Claude Code — `.claude/settings.json`:
15
+ **If MCP is not configured yet**, add this to the project's MCP config (Claude Code: `.claude/settings.json`,
16
+ Cursor: `.cursor/mcp.json`):
57
17
 
58
18
  ```json
59
19
  {
@@ -66,111 +26,47 @@ Claude Code — `.claude/settings.json`:
66
26
  }
67
27
  ```
68
28
 
69
- Cursor — `.cursor/mcp.json`:
70
-
71
- ```json
72
- {
73
- "mcpServers": {
74
- "oimdb": {
75
- "command": "npx",
76
- "args": ["-y", "@oimdb/mcp"]
77
- }
78
- }
79
- }
80
- ```
81
-
82
- ### 2. Connect the browser
29
+ Then ask the user to reload the assistant so the server starts, and to make sure the page connects to the
30
+ bridge:
83
31
 
84
32
  ```ts
85
33
  // src/debug.ts
86
34
  import './store/users.debug';
87
35
  import './store/tasks.debug';
88
- import { registry } from '@oimdb/devtools';
36
+ import { oimDevRegistry } from '@oimdb/devtools';
89
37
 
90
38
  if (typeof window !== 'undefined') {
91
- (window as Record<string, unknown>).__OIMDB_DEV__ = registry;
92
- registry.connect(); // connects to ws://localhost:7432
39
+ (window as unknown as Record<string, unknown>).__OIMDB_DEV__ = oimDevRegistry;
40
+ oimDevRegistry.connect(); // connects to ws://localhost:7432
93
41
  }
94
42
  ```
95
43
 
96
- ### 3. Use from Claude
97
-
98
- ```
99
- Call oimdb_inspect to see what's in the store.
100
- ```
101
-
102
44
  ## Available MCP Tools
103
45
 
104
46
  | Tool | Description |
105
47
  |---|---|
106
- | `oimdb_inspect` | Full structured state — all collections, computeds, dep graph |
107
- | `oimdb_dump` | Human-readable text summary (same as `registry.dump()`) |
48
+ | `oimdb_inspect` | Full structured state — collections with their index keys and relations, computeds, flush history, errors |
49
+ | `oimdb_dump` | Human-readable text summary — what `oimDevRegistry.dumpString()` returns |
108
50
  | `oimdb_collection` | Details of one collection by name |
109
- | `oimdb_computed` | Status + deps of one computed by name |
51
+ | `oimdb_computed` | Current value of one registered computed, by name |
110
52
 
111
53
  ## Offline / static mode (no browser)
112
54
 
113
- By default the tools read a **live browser tab**. You can instead point the
114
- server at an **in-process model** so it answers offline — ideal for "what's the
115
- data model?" questions without running the app.
116
-
117
- **CLI — a model module.** Set `OIMDB_MODEL_MODULE` to a module that builds the
118
- model in Node and exports a `registry` (or a default export, or a zero-arg
119
- factory returning one):
120
-
121
- ```ts
122
- // oimdb-model.js (built JS, or run the server under a TS loader)
123
- import { OIMEventQueue, OIMReactiveCollection } from '@oimdb/core';
124
- import { OIMDevRegistry } from '@oimdb/devtools';
125
-
126
- const queue = new OIMEventQueue();
127
- const users = new OIMReactiveCollection(queue, { selectPk: u => u.id });
128
-
129
- export const registry = new OIMDevRegistry();
130
- registry.collection('users', users, { indexes: { /* ... */ } });
131
- ```
132
-
133
55
  ```bash
134
56
  OIMDB_MODEL_MODULE=./oimdb-model.js npx @oimdb/mcp
135
57
  ```
136
58
 
137
- No WebSocket port is opened in this mode — no browser required.
138
-
139
- **Embedded — inject a registry.** When running the server from your own Node
140
- process, pass the registry directly:
59
+ The module is the one `@oimdb/cli` reads with `--model`; see its README. No WebSocket port is opened in this
60
+ mode. From your own Node process, pass the registry directly:
141
61
 
142
62
  ```ts
143
63
  import { createOIMDevMcpServer } from '@oimdb/mcp';
144
- import { registry } from './oimdb-model';
145
-
146
- await createOIMDevMcpServer({ registry }).start();
147
- ```
148
-
149
- > **Fields offline:** structure (collections, indexes, relations) is available
150
- > as soon as the model is constructed. Entity **field names** appear only when
151
- > the model holds at least one sample entity (TypeScript types are erased at
152
- > runtime) — seed a representative row in the model module if you want them.
153
-
154
- ## CDP Inspector (no browser-side code needed)
155
-
156
- Launch Chrome with `--remote-debugging-port=9222`, then:
157
-
158
- ```bash
159
- npx @oimdb/mcp cdp
160
- npx @oimdb/mcp cdp | jq .collections
161
- OIMDB_CDP_URL=http://localhost:9222 npx @oimdb/mcp cdp
162
- ```
163
-
164
- ## Port
165
-
166
- The MCP bridge WebSocket runs on port `7432` by default. Change it:
64
+ import { oimDevRegistry } from '@oimdb/devtools';
167
65
 
168
- ```bash
169
- OIMDB_WS_PORT=7433 npx @oimdb/mcp
66
+ await createOIMDevMcpServer({ registry: oimDevRegistry }).start();
170
67
  ```
171
68
 
172
- Update `registry.connect()` accordingly:
173
-
174
- ```ts
175
- registry.connect('ws://localhost:7433');
176
- ```
69
+ `createOIMDevMcpServer(options?)` takes `wsPort?` (7432), `registry?`, `modelModulePath?`, `host?`
70
+ (`127.0.0.1`) and `allowedOrigins?` (`[]`). The bridge, ports, host and origin rules are `@oimdb/cli`'s and are
71
+ described in its README; `OIMDB_WS_PORT`, `OIMDB_HOST`, `OIMDB_ALLOWED_ORIGINS` and `OIMDB_MODEL_MODULE` set them
72
+ here too.
package/bin/oimdb-mcp.cjs CHANGED
@@ -1,24 +1,23 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Single entry point for @oimdb/mcp. Dispatches on the first argument so all of
4
- * these work via npx (one bin → npx can always resolve it):
5
- *
6
- * npx @oimdb/mcp # MCP server (default; for AI tools)
7
- * npx @oimdb/mcp devtools # standalone DevTools UI (for humans)
8
- * npx @oimdb/mcp cdp # one-shot CDP snapshot to stdout
9
- */
2
+ // The servers catch what they can reach; this is the net for what they cannot.
3
+ // It lives in the CLI and NOT in the library classes, because those also run
4
+ // embedded in somebody else's process, where deciding that the process survives
5
+ // its own uncaught errors is not ours to make. stderr only — MCP mode speaks
6
+ // protocol on stdout.
7
+ process.on('uncaughtException', err => {
8
+ process.stderr.write(`[OIMDB] Uncaught error (continuing): ${err && err.stack ? err.stack : String(err)}\n`);
9
+ });
10
+ process.on('unhandledRejection', reason => {
11
+ process.stderr.write(`[OIMDB] Unhandled rejection (continuing): ${String(reason)}\n`);
12
+ });
10
13
  const sub = process.argv[2];
11
14
 
12
- if (sub === 'devtools') {
13
- require('./devtools.cjs')();
14
- } else if (sub === 'cdp') {
15
- require('./cdp.cjs')();
16
- } else if (sub === undefined || sub === 'mcp' || sub === 'serve') {
15
+ if (sub === undefined || sub === 'mcp' || sub === 'serve') {
17
16
  require('./server.cjs')();
18
17
  } else {
19
18
  process.stderr.write(
20
19
  `[OIMDB] Unknown command: ${sub}\n` +
21
- 'Usage: oimdb-mcp [devtools|cdp] (no argument = MCP server)\n'
20
+ 'Usage: oimdb-mcp (the MCP server). The DevTools UI and the CDP snapshot are in @oimdb/cli: oimdb serve, oimdb inspect --cdp.\n'
22
21
  );
23
22
  process.exit(1);
24
23
  }
package/bin/server.cjs CHANGED
@@ -7,7 +7,16 @@ module.exports = function runMcpServer() {
7
7
  : 7432;
8
8
  // Offline/static mode: point at a module that builds the model in-process.
9
9
  const modelModulePath = process.env.OIMDB_MODEL_MODULE || undefined;
10
- const server = new OIMDevMcpServer({ wsPort, modelModulePath });
10
+ // Loopback unless told otherwise. This port hands out the application's
11
+ // whole data model, so binding every interface — which is what happens when
12
+ // no host is given — puts it on the café Wi-Fi.
13
+ const host = process.env.OIMDB_HOST || '127.0.0.1';
14
+ // Exact page origins, comma-separated. Never '*'.
15
+ const allowedOrigins = (process.env.OIMDB_ALLOWED_ORIGINS || '')
16
+ .split(',')
17
+ .map(s => s.trim())
18
+ .filter(Boolean);
19
+ const server = new OIMDevMcpServer({ wsPort, modelModulePath, host, allowedOrigins });
11
20
  server.start().catch(err => {
12
21
  process.stderr.write('[OIMDB MCP] Fatal: ' + err.message + '\n');
13
22
  process.exit(1);
package/dist/index.cjs CHANGED
@@ -21,42 +21,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  OIMDevMcpServer: () => OIMDevMcpServer,
24
- OIMDevUIServer: () => OIMDevUIServer,
25
- createOIMDevMcpServer: () => createOIMDevMcpServer,
26
- createOIMDevUIServer: () => createOIMDevUIServer
24
+ createOIMDevMcpServer: () => createOIMDevMcpServer
27
25
  });
28
26
  module.exports = __toCommonJS(index_exports);
29
27
 
30
28
  // src/core/OIMDevMcpServer.ts
31
29
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
32
30
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
33
- var import_ws = require("ws");
34
- var import_node_url = require("url");
35
- var import_node_path = require("path");
36
31
  var import_zod = require("zod");
37
- async function loadRegistryFromModule(path) {
38
- const url = (0, import_node_url.pathToFileURL)((0, import_node_path.resolve)(path)).href;
39
- const mod = await import(url);
40
- let candidate = mod.registry ?? mod.default ?? mod;
41
- if (typeof candidate === "function") {
42
- candidate = await candidate();
43
- }
44
- if (!candidate || typeof candidate.inspect !== "function" || typeof candidate.dumpString !== "function") {
45
- throw new Error(
46
- `[OIMDB MCP] Module "${path}" must export a registry exposing inspect() and dumpString() (as a "registry" or default export, or a zero-arg factory returning one).`
47
- );
48
- }
49
- return candidate;
50
- }
32
+ var import_cli = require("@oimdb/cli");
51
33
  var OIMDevMcpServer = class {
52
34
  constructor(options = {}) {
53
- this.wss = null;
54
- this.latestBrowser = null;
55
- this.pending = /* @__PURE__ */ new Map();
56
- this.modelModuleLoaded = false;
57
- this.wsPort = options.wsPort ?? 7432;
58
- this.inProcessRegistry = options.registry ?? null;
59
- this.modelModulePath = options.modelModulePath;
35
+ const wsPort = options.wsPort ?? 7432;
36
+ let devStateSource;
37
+ if (options.registry !== void 0) {
38
+ devStateSource = new import_cli.OIMDevRegistryStateSource(options.registry);
39
+ } else if (options.modelModulePath !== void 0) {
40
+ this.devModuleStateSource = new import_cli.OIMDevModuleStateSource(options.modelModulePath);
41
+ devStateSource = this.devModuleStateSource;
42
+ } else {
43
+ this.devBridge = new import_cli.OIMDevBridge({
44
+ wsPort,
45
+ host: options.host ?? "127.0.0.1",
46
+ allowedOrigins: new Set(options.allowedOrigins ?? []),
47
+ logPrefix: "[OIMDB MCP]",
48
+ portInUseHint: "npx @oimdb/mcp",
49
+ requestTimeoutMessage: "Browser response timeout (5s). Is the page active?"
50
+ });
51
+ devStateSource = new import_cli.OIMDevBridgeStateSource(this.devBridge, wsPort);
52
+ }
53
+ this.devStateReader = new import_cli.OIMDevStateReader(devStateSource);
60
54
  this.mcpServer = new import_mcp.McpServer(
61
55
  { name: "oimdb", version: "1.0.0" },
62
56
  {
@@ -65,292 +59,30 @@ var OIMDevMcpServer = class {
65
59
  );
66
60
  this.registerTools();
67
61
  }
68
- /**
69
- * Resolves the in-process registry, loading the model module on first use.
70
- * Returns null when no in-process source is configured (browser-bridge mode).
71
- */
72
- async getInProcessRegistry() {
73
- if (this.inProcessRegistry) return this.inProcessRegistry;
74
- if (this.modelModulePath && !this.modelModuleLoaded) {
75
- this.modelModuleLoaded = true;
76
- this.inProcessRegistry = await loadRegistryFromModule(
77
- this.modelModulePath
78
- );
79
- }
80
- return this.inProcessRegistry;
81
- }
82
- /** Reads the inspect snapshot from the in-process model or the browser. */
83
- async readInspect() {
84
- const registry = await this.getInProcessRegistry();
85
- if (registry) return registry.inspect();
86
- return this.queryBrowser("inspect");
87
- }
88
- /** Reads the human-readable dump from the in-process model or the browser. */
89
- async readDump() {
90
- const registry = await this.getInProcessRegistry();
91
- if (registry) return registry.dumpString();
92
- return await this.queryBrowser("dump");
93
- }
94
62
  registerTools() {
95
63
  const server = this.mcpServer;
96
64
  const nameSchema = { name: import_zod.z.string().describe("Registered name") };
65
+ const asText = (text) => ({ content: [{ type: "text", text }] });
97
66
  server.registerTool("oimdb_inspect", {
98
67
  description: "Get full state of all registered OIMDB collections and computeds. Returns structured JSON with entity counts, field names, index keys, relations, and computed statuses. Source is the connected browser tab, or an in-process model when one is configured (offline/static \u2014 field names appear only if the model holds sample entities)."
99
- }, async () => {
100
- const data = await this.readInspect();
101
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
102
- });
68
+ }, async () => asText(await this.devStateReader.readInspectText()));
103
69
  server.registerTool("oimdb_dump", {
104
- description: "Get a human-readable text summary of OIMDB state. Same output as registry.dump(). Reads the connected browser tab or an in-process model when configured."
105
- }, async () => {
106
- const text = await this.readDump();
107
- return { content: [{ type: "text", text }] };
108
- });
70
+ description: "Get a human-readable text summary of OIMDB state. Same output as oimDevRegistry.dump(). Reads the connected browser tab or an in-process model when configured."
71
+ }, async () => asText(await this.devStateReader.readDumpText()));
109
72
  server.registerTool("oimdb_collection", {
110
73
  description: "Get details of a specific registered collection: entity count, field names, index keys, and relations.",
111
74
  inputSchema: nameSchema
112
- }, async ({ name }) => {
113
- const data = await this.readInspect();
114
- const info = data.collections[name];
115
- if (!info) {
116
- const available = Object.keys(data.collections).join(", ") || "(none registered)";
117
- return { content: [{ type: "text", text: `Collection '${name}' not found. Registered: ${available}` }] };
118
- }
119
- return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
120
- });
75
+ }, async ({ name }) => asText(await this.devStateReader.readCollectionText(name)));
121
76
  server.registerTool("oimdb_computed", {
122
77
  description: "Get status and resolved dependency names of a specific registered computed value. Shows fresh/stale/pending status, current value, and which registered sources it depends on.",
123
78
  inputSchema: nameSchema
124
- }, async ({ name }) => {
125
- const data = await this.readInspect();
126
- const info = data.computeds[name];
127
- if (!info) {
128
- const available = Object.keys(data.computeds).join(", ") || "(none registered)";
129
- return { content: [{ type: "text", text: `Computed '${name}' not found. Registered: ${available}` }] };
130
- }
131
- return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
132
- });
133
- }
134
- async queryBrowser(action) {
135
- if (!this.latestBrowser) {
136
- throw new Error(
137
- `No browser tab connected to the OIMDB MCP bridge.
138
-
139
- Live mode \u2014 add to your debug entry point:
140
- import { registry } from '@oimdb/devtools';
141
- registry.connect(); // connects to ws://localhost:${this.wsPort}
142
- Then reload the page.
143
-
144
- Offline mode \u2014 point the server at a model module instead:
145
- OIMDB_MODEL_MODULE=./path/to/model.js npx @oimdb/mcp
146
- (the module exports a "registry" with inspect()/dumpString()).`
147
- );
148
- }
149
- return new Promise((resolve, reject) => {
150
- const id = Math.random().toString(36).slice(2);
151
- const timer = setTimeout(() => {
152
- this.pending.delete(id);
153
- reject(new Error("Browser response timeout (5s). Is the page active?"));
154
- }, 5e3);
155
- this.pending.set(id, { resolve, reject, timer });
156
- this.latestBrowser.ws.send(JSON.stringify({ type: "request", id, action }));
157
- });
158
- }
159
- setupWebSocketServer() {
160
- this.wss = new import_ws.WebSocketServer({ port: this.wsPort });
161
- this.wss.on("error", (err) => {
162
- if (err.code === "EADDRINUSE") {
163
- process.stderr.write(
164
- `[OIMDB MCP] Port ${this.wsPort} is already in use.
165
- Set a different port: OIMDB_WS_PORT=7433 npx @oimdb/mcp
166
- `
167
- );
168
- process.exit(1);
169
- }
170
- process.stderr.write(`[OIMDB MCP] WebSocket server error: ${err.message}
171
- `);
172
- process.exit(1);
173
- });
174
- this.wss.on("connection", (ws) => {
175
- ws.on("message", (raw) => {
176
- const msg = JSON.parse(raw.toString());
177
- if (msg.type === "hello") {
178
- this.latestBrowser = { ws, url: msg.url, title: msg.title };
179
- process.stderr.write(`[OIMDB MCP] Browser connected: ${msg.title} (${msg.url})
180
- `);
181
- return;
182
- }
183
- if (msg.type === "response") {
184
- const pending = this.pending.get(msg.id);
185
- if (!pending) return;
186
- this.pending.delete(msg.id);
187
- clearTimeout(pending.timer);
188
- if (msg.error) {
189
- pending.reject(new Error(msg.error));
190
- } else {
191
- pending.resolve(msg.data);
192
- }
193
- }
194
- });
195
- ws.on("close", () => {
196
- if (this.latestBrowser?.ws === ws) {
197
- this.latestBrowser = null;
198
- process.stderr.write("[OIMDB MCP] Browser disconnected\n");
199
- }
200
- });
201
- });
202
- process.stderr.write(`[OIMDB MCP] Bridge ready on ws://localhost:${this.wsPort}
203
- `);
79
+ }, async ({ name }) => asText(await this.devStateReader.readComputedText(name)));
204
80
  }
205
81
  async start() {
206
- const registry = await this.getInProcessRegistry();
207
- if (registry) {
208
- process.stderr.write(
209
- "[OIMDB MCP] In-process model loaded \u2014 serving offline (no browser bridge).\n"
210
- );
211
- } else {
212
- this.setupWebSocketServer();
213
- }
214
- const transport = new import_stdio.StdioServerTransport();
215
- await this.mcpServer.connect(transport);
216
- }
217
- };
218
-
219
- // src/core/OIMDevUIServer.ts
220
- var import_http = require("http");
221
- var import_fs = require("fs");
222
- var import_path = require("path");
223
- var import_child_process = require("child_process");
224
- var import_ws2 = require("ws");
225
- var MIME = {
226
- ".html": "text/html; charset=utf-8",
227
- ".js": "text/javascript; charset=utf-8",
228
- ".css": "text/css; charset=utf-8"
229
- };
230
- var OIMDevUIServer = class {
231
- constructor(options) {
232
- this.wss = null;
233
- this.latestBrowser = null;
234
- this.pending = /* @__PURE__ */ new Map();
235
- this.wsPort = options.wsPort ?? 7432;
236
- this.uiPort = options.uiPort ?? 7433;
237
- this.uiDir = options.uiDir;
238
- this.shouldOpen = options.open ?? true;
239
- }
240
- async queryBrowser(action) {
241
- if (!this.latestBrowser) return null;
242
- return new Promise((resolve, reject) => {
243
- const id = Math.random().toString(36).slice(2);
244
- const timer = setTimeout(() => {
245
- this.pending.delete(id);
246
- reject(new Error("timeout"));
247
- }, 5e3);
248
- this.pending.set(id, { resolve, reject, timer });
249
- this.latestBrowser.ws.send(JSON.stringify({ type: "request", id, action }));
250
- });
251
- }
252
- setupBridge() {
253
- this.wss = new import_ws2.WebSocketServer({ port: this.wsPort });
254
- this.wss.on("error", (err) => {
255
- if (err.code === "EADDRINUSE") {
256
- process.stderr.write(
257
- `[OIMDB DevTools] Port ${this.wsPort} is already in use.
258
- Set a different port: OIMDB_WS_PORT=7433 npx @oimdb/mcp devtools
259
- `
260
- );
261
- process.exit(1);
262
- }
263
- process.stderr.write(`[OIMDB DevTools] Bridge error: ${err.message}
264
- `);
265
- process.exit(1);
266
- });
267
- this.wss.on("connection", (ws) => {
268
- ws.on("message", (raw) => {
269
- const msg = JSON.parse(raw.toString());
270
- if (msg.type === "hello") {
271
- this.latestBrowser = { ws, url: msg.url, title: msg.title };
272
- process.stderr.write(`[OIMDB DevTools] Browser connected: ${msg.title} (${msg.url})
273
- `);
274
- return;
275
- }
276
- if (msg.type === "response") {
277
- const req = this.pending.get(msg.id);
278
- if (!req) return;
279
- this.pending.delete(msg.id);
280
- clearTimeout(req.timer);
281
- if (msg.error) req.reject(new Error(msg.error));
282
- else req.resolve(msg.data);
283
- }
284
- });
285
- ws.on("close", () => {
286
- if (this.latestBrowser?.ws === ws) {
287
- this.latestBrowser = null;
288
- process.stderr.write("[OIMDB DevTools] Browser disconnected\n");
289
- }
290
- });
291
- });
292
- process.stderr.write(`[OIMDB DevTools] Bridge on ws://localhost:${this.wsPort}
293
- `);
294
- }
295
- setupHTTP() {
296
- const server = (0, import_http.createServer)((req, res) => {
297
- const url = req.url ?? "/";
298
- res.setHeader("Access-Control-Allow-Origin", "*");
299
- if (url === "/api/inspect") {
300
- this.queryBrowser("inspect").then((data) => {
301
- res.writeHead(200, { "Content-Type": "application/json" });
302
- res.end(JSON.stringify({
303
- connected: !!this.latestBrowser,
304
- tab: this.latestBrowser ? { url: this.latestBrowser.url, title: this.latestBrowser.title } : null,
305
- data
306
- }));
307
- }).catch(() => {
308
- res.writeHead(200, { "Content-Type": "application/json" });
309
- res.end(JSON.stringify({ connected: false, tab: null, data: null }));
310
- });
311
- return;
312
- }
313
- const filePath = (0, import_path.join)(this.uiDir, url === "/" ? "index.html" : url);
314
- try {
315
- const content = (0, import_fs.readFileSync)(filePath);
316
- res.writeHead(200, { "Content-Type": MIME[(0, import_path.extname)(filePath)] ?? "text/plain" });
317
- res.end(content);
318
- } catch {
319
- res.writeHead(404);
320
- res.end("Not found");
321
- }
322
- });
323
- server.on("error", (err) => {
324
- if (err.code === "EADDRINUSE") {
325
- process.stderr.write(
326
- `[OIMDB DevTools] UI port ${this.uiPort} is already in use.
327
- Set a different port: OIMDB_UI_PORT=7434 npx @oimdb/mcp devtools
328
- `
329
- );
330
- process.exit(1);
331
- }
332
- });
333
- server.listen(this.uiPort, () => {
334
- const uiUrl = `http://localhost:${this.uiPort}`;
335
- process.stderr.write(`[OIMDB DevTools] UI ready \u2192 ${uiUrl}
336
- `);
337
- if (this.shouldOpen) this.openURL(uiUrl);
338
- });
339
- }
340
- openURL(url) {
341
- const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
342
- (0, import_child_process.exec)(cmd, (err) => {
343
- if (err) process.stderr.write(`[OIMDB DevTools] Could not open browser: ${err.message}
344
- `);
345
- });
346
- }
347
- start() {
348
- this.setupBridge();
349
- this.setupHTTP();
350
- process.stderr.write(
351
- `[OIMDB DevTools] Add to debug.ts: registry.connect();
352
- `
353
- );
82
+ if (this.devModuleStateSource !== void 0) await this.devModuleStateSource.loadDevRegistry();
83
+ if (this.devBridge !== void 0) this.devBridge.start();
84
+ else process.stderr.write("[OIMDB MCP] In-process model loaded \u2014 serving offline (no browser bridge).\n");
85
+ await this.mcpServer.connect(new import_stdio.StdioServerTransport());
354
86
  }
355
87
  };
356
88
 
@@ -358,13 +90,8 @@ Set a different port: OIMDB_UI_PORT=7434 npx @oimdb/mcp devtools
358
90
  function createOIMDevMcpServer(options) {
359
91
  return new OIMDevMcpServer(options);
360
92
  }
361
- function createOIMDevUIServer(options) {
362
- return new OIMDevUIServer(options);
363
- }
364
93
  // Annotate the CommonJS export names for ESM import in node:
365
94
  0 && (module.exports = {
366
95
  OIMDevMcpServer,
367
- OIMDevUIServer,
368
- createOIMDevMcpServer,
369
- createOIMDevUIServer
96
+ createOIMDevMcpServer
370
97
  });