@oimdb/mcp 1.2.0 → 2.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 +132 -32
- package/bin/cdp.cjs +1 -1
- package/bin/devtools.cjs +10 -1
- package/bin/oimdb-mcp.cjs +11 -0
- package/bin/server.cjs +10 -1
- package/dist/index.cjs +290 -148
- package/dist/index.d.cts +47 -33
- package/dist/index.d.ts +47 -33
- package/dist/index.js +291 -149
- package/package.json +16 -6
- package/ui/app.js +1 -1
- package/ui/index.html +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# @oimdb/mcp
|
|
2
2
|
|
|
3
|
-
Connects AI assistants to a live OIMDB store running in the browser.
|
|
3
|
+
Connects AI assistants to a live OIMDB store running in the browser. It provides MCP tools for Claude Code, plus an optional CDP inspector for any AI that can run a shell command.
|
|
4
4
|
|
|
5
|
-
📖 **[Full documentation](https://
|
|
5
|
+
📖 **[Full documentation](https://oimdb.org/)**
|
|
6
6
|
|
|
7
7
|
## For AI Assistants
|
|
8
8
|
|
|
9
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.
|
|
10
10
|
|
|
11
|
-
**Check
|
|
11
|
+
**Check whether MCP is already configured.** Look for `mcpServers.oimdb` in `.claude/settings.json` or `~/.claude/settings.json`. If it is there, the tools `oimdb_inspect`, `oimdb_dump`, `oimdb_collection` and `oimdb_computed` are available. Call `oimdb_inspect` now to see the current state.
|
|
12
12
|
|
|
13
13
|
**If MCP is not configured yet**, add this to the project's MCP config:
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ If you are an AI assistant and this package is present in the project, you can g
|
|
|
28
28
|
|
|
29
29
|
Then ask the user to:
|
|
30
30
|
1. Reload Claude Code so the MCP server starts
|
|
31
|
-
2. Make sure `debug.ts` calls `
|
|
31
|
+
2. Make sure `debug.ts` calls `oimDevRegistry.connect()` and the app is running in the browser
|
|
32
32
|
|
|
33
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
34
|
|
|
@@ -44,7 +44,7 @@ npx @oimdb/mcp cdp
|
|
|
44
44
|
npx @oimdb/mcp devtools
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Opens `http://localhost:7433` automatically
|
|
47
|
+
Opens `http://localhost:7433` automatically and shows live collections and computeds from the running browser tab. No Chrome extension is needed.
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
OIMDB_WS_PORT=7433 OIMDB_UI_PORT=7434 npx @oimdb/mcp devtools # custom ports
|
|
@@ -81,15 +81,19 @@ Cursor — `.cursor/mcp.json`:
|
|
|
81
81
|
|
|
82
82
|
### 2. Connect the browser
|
|
83
83
|
|
|
84
|
+
The app calls `oimInitDevtools()` once at startup, before it builds its first
|
|
85
|
+
service, where it builds the database (see the `@oimdb/devtools` README, Setup).
|
|
86
|
+
The debug entry point then only connects:
|
|
87
|
+
|
|
84
88
|
```ts
|
|
85
89
|
// src/debug.ts
|
|
86
90
|
import './store/users.debug';
|
|
87
91
|
import './store/tasks.debug';
|
|
88
|
-
import {
|
|
92
|
+
import { oimDevRegistry } from '@oimdb/devtools';
|
|
89
93
|
|
|
90
94
|
if (typeof window !== 'undefined') {
|
|
91
|
-
(window as Record<string, unknown>).__OIMDB_DEV__ =
|
|
92
|
-
|
|
95
|
+
(window as unknown as Record<string, unknown>).__OIMDB_DEV__ = oimDevRegistry;
|
|
96
|
+
oimDevRegistry.connect(); // connects to ws://localhost:7432
|
|
93
97
|
}
|
|
94
98
|
```
|
|
95
99
|
|
|
@@ -103,47 +107,68 @@ Call oimdb_inspect to see what's in the store.
|
|
|
103
107
|
|
|
104
108
|
| Tool | Description |
|
|
105
109
|
|---|---|
|
|
106
|
-
| `oimdb_inspect` | Full structured state —
|
|
107
|
-
| `oimdb_dump` | Human-readable text summary (
|
|
110
|
+
| `oimdb_inspect` | Full structured state — collections with their index keys and relations, computeds, flush history, errors |
|
|
111
|
+
| `oimdb_dump` | Human-readable text summary — what `oimDevRegistry.dumpString()` returns (`oimDevRegistry.dump()` is the sibling that console-logs it) |
|
|
108
112
|
| `oimdb_collection` | Details of one collection by name |
|
|
109
|
-
| `oimdb_computed` |
|
|
113
|
+
| `oimdb_computed` | Current value of one registered computed, by name |
|
|
110
114
|
|
|
111
115
|
## Offline / static mode (no browser)
|
|
112
116
|
|
|
113
117
|
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
|
|
115
|
-
data model?" questions
|
|
118
|
+
server at an **in-process model** so it answers offline. This suits "what's the
|
|
119
|
+
data model?" questions when you don't want to run the app.
|
|
116
120
|
|
|
117
|
-
**CLI
|
|
118
|
-
model in Node and exports
|
|
119
|
-
factory
|
|
121
|
+
**CLI: a model module.** Set `OIMDB_MODEL_MODULE` to a module that builds the
|
|
122
|
+
model in Node and exports an `oimDevRegistry` (or a default export, or a zero-arg
|
|
123
|
+
factory that returns one). It calls `oimInitDevtools()` before it builds the first
|
|
124
|
+
service, as every app using `@oimdb/devtools` does:
|
|
120
125
|
|
|
121
126
|
```ts
|
|
122
127
|
// oimdb-model.js (built JS, or run the server under a TS loader)
|
|
123
|
-
import {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
import {
|
|
129
|
+
OIMDB, createOIMBucketService, createOIMEntityService, type TOIMEntitySlot,
|
|
130
|
+
} from '@oimdb/core';
|
|
131
|
+
import { OIMDevRegistry, oimInitDevtools } from '@oimdb/devtools';
|
|
132
|
+
|
|
133
|
+
oimInitDevtools();
|
|
134
|
+
|
|
135
|
+
type TUser = { id: string; name: string; role: string };
|
|
136
|
+
|
|
137
|
+
const db = new OIMDB();
|
|
138
|
+
const usersService = createOIMEntityService<TUser>(db, { keyOf: (user) => user.id });
|
|
139
|
+
const users = usersService.createStoreSlot(db);
|
|
140
|
+
// A bucket's slots are the caller's — a write to a user moves the value and
|
|
141
|
+
// files nothing, so whoever writes the user also calls
|
|
142
|
+
// `usersByRoleService.slotContentService.writeBucketMemberSlots(bucketSlot, entitySlots)`.
|
|
143
|
+
const usersByRoleService = createOIMBucketService<TOIMEntitySlot<TUser>>(db);
|
|
144
|
+
const usersByRole = usersByRoleService.createStoreSlot(db);
|
|
145
|
+
|
|
146
|
+
export const oimDevRegistry = new OIMDevRegistry();
|
|
147
|
+
// The bucket collection and its store slot — you constructed them, so you hand them over.
|
|
148
|
+
oimDevRegistry.collection('users', usersService, users, {
|
|
149
|
+
indexes: { byRole: { bucketCollectionService: usersByRoleService, bucketStoreSlot: usersByRole } },
|
|
150
|
+
});
|
|
131
151
|
```
|
|
132
152
|
|
|
153
|
+
The server asks a registry for only two methods, `inspect()` and `dumpString()`,
|
|
154
|
+
declared here as `IOIMDevRegistry`, so this package depends on no `@oimdb`
|
|
155
|
+
package at all. `OIMDevRegistry` from `@oimdb/devtools` satisfies it as it
|
|
156
|
+
stands, and so does anything else that answers those two.
|
|
157
|
+
|
|
133
158
|
```bash
|
|
134
159
|
OIMDB_MODEL_MODULE=./oimdb-model.js npx @oimdb/mcp
|
|
135
160
|
```
|
|
136
161
|
|
|
137
|
-
No WebSocket port is opened in this mode
|
|
162
|
+
No WebSocket port is opened in this mode, and no browser is required.
|
|
138
163
|
|
|
139
|
-
**Embedded
|
|
164
|
+
**Embedded: inject a registry.** When you run the server from your own Node
|
|
140
165
|
process, pass the registry directly:
|
|
141
166
|
|
|
142
167
|
```ts
|
|
143
168
|
import { createOIMDevMcpServer } from '@oimdb/mcp';
|
|
144
|
-
import {
|
|
169
|
+
import { oimDevRegistry } from '@oimdb/devtools';
|
|
145
170
|
|
|
146
|
-
await createOIMDevMcpServer({ registry }).start();
|
|
171
|
+
await createOIMDevMcpServer({ registry: oimDevRegistry }).start();
|
|
147
172
|
```
|
|
148
173
|
|
|
149
174
|
> **Fields offline:** structure (collections, indexes, relations) is available
|
|
@@ -161,16 +186,91 @@ npx @oimdb/mcp cdp | jq .collections
|
|
|
161
186
|
OIMDB_CDP_URL=http://localhost:9222 npx @oimdb/mcp cdp
|
|
162
187
|
```
|
|
163
188
|
|
|
164
|
-
##
|
|
189
|
+
## Ports, host and origins
|
|
190
|
+
|
|
191
|
+
Everything here is configured through **environment variables**. The only CLI
|
|
192
|
+
flag in the package is `devtools --no-open`.
|
|
193
|
+
|
|
194
|
+
| Variable | Default | What it sets |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `OIMDB_WS_PORT` | `7432` | The bridge WebSocket port |
|
|
197
|
+
| `OIMDB_UI_PORT` | `7433` | The `devtools` UI HTTP port |
|
|
198
|
+
| `OIMDB_HOST` | `127.0.0.1` | The interface both servers bind to |
|
|
199
|
+
| `OIMDB_ALLOWED_ORIGINS` | *(empty)* | Extra page origins allowed onto the bridge, comma-separated |
|
|
200
|
+
| `OIMDB_MODEL_MODULE` | — | Offline model module (see above) |
|
|
201
|
+
| `OIMDB_CDP_URL` | `http://localhost:9222` | The CDP endpoint the `cdp` subcommand reads |
|
|
202
|
+
|
|
203
|
+
The same two settings are `host` and `allowedOrigins` on `TOIMDevMcpOptions` and
|
|
204
|
+
`TOIMDevUIServerOptions` when you construct a server yourself.
|
|
165
205
|
|
|
166
|
-
|
|
206
|
+
**It binds to loopback by default, on purpose.** This port hands out the
|
|
207
|
+
application's entire data model, and a `WebSocketServer` built with no host
|
|
208
|
+
binds every interface, which would put the model on the café Wi-Fi.
|
|
209
|
+
Set `OIMDB_HOST=0.0.0.0` when the browser really runs
|
|
210
|
+
somewhere else (a container, a VM, a phone), and accept that everyone who can
|
|
211
|
+
route there can talk to it. Both servers print the bound host at startup.
|
|
212
|
+
|
|
213
|
+
**Origins are checked, and `'*'` is never allowed.** A request with no `Origin` header is
|
|
214
|
+
allowed (that is a non-browser client). A loopback origin is allowed
|
|
215
|
+
(`localhost`, `127.0.0.1`, `::1`, `[::1]`, `*.localhost`). Anything else is
|
|
216
|
+
allowed only if you listed it exactly in `OIMDB_ALLOWED_ORIGINS`. The literal
|
|
217
|
+
string `null` is always rejected, because a sandboxed iframe on any site in the world
|
|
218
|
+
sends exactly that. Only an allow-listed origin gets
|
|
219
|
+
`Access-Control-Allow-Origin` echoed back.
|
|
220
|
+
|
|
221
|
+
While bound to a loopback host, the UI server also rejects a request whose
|
|
222
|
+
`Host` header is not a loopback authority, with **421 Misdirected request**.
|
|
223
|
+
This is the DNS-rebinding guard, and it is switched off when you bind elsewhere.
|
|
224
|
+
|
|
225
|
+
Change the bridge port and update `oimDevRegistry.connect()` accordingly:
|
|
167
226
|
|
|
168
227
|
```bash
|
|
169
228
|
OIMDB_WS_PORT=7433 npx @oimdb/mcp
|
|
170
229
|
```
|
|
171
230
|
|
|
172
|
-
|
|
231
|
+
```ts
|
|
232
|
+
import { oimDevRegistry } from '@oimdb/devtools';
|
|
233
|
+
|
|
234
|
+
oimDevRegistry.connect('ws://localhost:7433');
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Running a server from your own process
|
|
173
238
|
|
|
174
239
|
```ts
|
|
175
|
-
|
|
240
|
+
import { createOIMDevMcpServer, createOIMDevUIServer } from '@oimdb/mcp';
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- **`createOIMDevMcpServer(options?)`** → `OIMDevMcpServer`. Options: `wsPort?`
|
|
244
|
+
(7432), `registry?`, `modelModulePath?`, `host?` (`127.0.0.1`),
|
|
245
|
+
`allowedOrigins?` (`[]`).
|
|
246
|
+
- **`createOIMDevUIServer(options)`** → `OIMDevUIServer`. Options: `wsPort?`
|
|
247
|
+
(7432), `uiPort?` (7433), **`uiDir` — required, no default**, `open?` (true),
|
|
248
|
+
`host?` (`127.0.0.1`), `allowedOrigins?` (`[]`).
|
|
249
|
+
|
|
250
|
+
Both classes are exported too, along with `TOIMDevMcpOptions`,
|
|
251
|
+
`TOIMDevUIServerOptions` and `IOIMDevRegistry`.
|
|
252
|
+
|
|
253
|
+
Know these three behaviours before you wire this into anything:
|
|
254
|
+
|
|
255
|
+
- **The newest browser tab takes the bridge**, and the previous socket is closed
|
|
256
|
+
with "Replaced by a newer OIMDB DevTools tab". Only the socket that currently
|
|
257
|
+
holds the bridge may answer a request; anything else is ignored.
|
|
258
|
+
- **A query times out after 5 seconds**, reported as "Browser response timeout
|
|
259
|
+
(5s). Is the page active?".
|
|
260
|
+
- **A malformed frame drops that client** with close code 1007, and `EADDRINUSE`
|
|
261
|
+
exits the process with a message naming the env var to change.
|
|
262
|
+
|
|
263
|
+
The `devtools` UI serves `/`, `/index.html`, `/app.js`, `/app.css`, plus
|
|
264
|
+
`/api/inspect`, which answers `{ connected, tab, data }`:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
curl http://127.0.0.1:7433/api/inspect
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Subcommands
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
npx @oimdb/mcp # the MCP server; `mcp` and `serve` are accepted aliases
|
|
274
|
+
npx @oimdb/mcp devtools # the standalone UI
|
|
275
|
+
npx @oimdb/mcp cdp # one-shot snapshot over the Chrome DevTools Protocol
|
|
176
276
|
```
|
package/bin/cdp.cjs
CHANGED
|
@@ -74,7 +74,7 @@ async function run() {
|
|
|
74
74
|
if (val === null || val === undefined) {
|
|
75
75
|
process.stderr.write(
|
|
76
76
|
'[OIMDB CDP] __OIMDB_DEV__ not found on this page.\n' +
|
|
77
|
-
'Make sure debug.ts sets: window.__OIMDB_DEV__ =
|
|
77
|
+
'Make sure debug.ts sets: window.__OIMDB_DEV__ = oimDevRegistry\n'
|
|
78
78
|
);
|
|
79
79
|
process.exit(1);
|
|
80
80
|
}
|
package/bin/devtools.cjs
CHANGED
|
@@ -11,7 +11,16 @@ module.exports = function runDevtools() {
|
|
|
11
11
|
: 7433;
|
|
12
12
|
const noOpen = process.argv.includes('--no-open');
|
|
13
13
|
const uiDir = path.join(__dirname, '../ui');
|
|
14
|
+
// Loopback unless told otherwise. This port hands out the application's
|
|
15
|
+
// whole data model, so binding every interface — which is what happens when
|
|
16
|
+
// no host is given — puts it on the café Wi-Fi.
|
|
17
|
+
const host = process.env.OIMDB_HOST || '127.0.0.1';
|
|
18
|
+
// Exact page origins, comma-separated. Never '*'.
|
|
19
|
+
const allowedOrigins = (process.env.OIMDB_ALLOWED_ORIGINS || '')
|
|
20
|
+
.split(',')
|
|
21
|
+
.map(s => s.trim())
|
|
22
|
+
.filter(Boolean);
|
|
14
23
|
|
|
15
|
-
const server = new OIMDevUIServer({ wsPort, uiPort, uiDir, open: !noOpen });
|
|
24
|
+
const server = new OIMDevUIServer({ wsPort, uiPort, uiDir, open: !noOpen, host, allowedOrigins });
|
|
16
25
|
server.start();
|
|
17
26
|
};
|
package/bin/oimdb-mcp.cjs
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
|
+
});
|
|
2
13
|
/**
|
|
3
14
|
* Single entry point for @oimdb/mcp. Dispatches on the first argument so all of
|
|
4
15
|
* these work via npx (one bin → npx can always resolve it):
|
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
|
-
|
|
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);
|