@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 +25 -129
- package/bin/oimdb-mcp.cjs +13 -14
- package/bin/server.cjs +10 -1
- package/dist/index.cjs +32 -305
- package/dist/index.d.cts +13 -65
- package/dist/index.d.ts +13 -65
- package/dist/index.js +37 -302
- package/package.json +15 -8
- package/bin/cdp.cjs +0 -98
- package/bin/devtools.cjs +0 -17
- package/ui/app.css +0 -323
- package/ui/app.js +0 -409
- package/ui/index.html +0 -63
package/dist/index.d.cts
CHANGED
|
@@ -1,79 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* `@oimdb/devtools` already satisfies it. Declared structurally (following the
|
|
4
|
-
* existing `IOIMDev*Like` convention) so `@oimdb/mcp` need not depend on
|
|
5
|
-
* `@oimdb/devtools`; it is not a separate registry implementation.
|
|
6
|
-
*/
|
|
7
|
-
interface IOIMDevRegistryLike {
|
|
8
|
-
inspect(): unknown;
|
|
9
|
-
dumpString(): string;
|
|
10
|
-
}
|
|
1
|
+
import { IOIMDevRegistry } from '@oimdb/cli';
|
|
2
|
+
|
|
11
3
|
type TOIMDevMcpOptions = {
|
|
12
|
-
/**
|
|
4
|
+
/** Default 7432; unused when `registry` is given — no bridge is started. */
|
|
13
5
|
wsPort?: number;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* provided, the tools answer from it without a browser/WebSocket bridge
|
|
17
|
-
* (no `wsPort` server started).
|
|
18
|
-
*/
|
|
19
|
-
registry?: IOIMDevRegistryLike;
|
|
20
|
-
/**
|
|
21
|
-
* Path to a module that builds the model in this process for offline
|
|
22
|
-
* introspection. The module must export a `registry` (or a default export,
|
|
23
|
-
* or a zero-arg factory) exposing `inspect()` and `dumpString()`.
|
|
24
|
-
* Also settable via the `OIMDB_MODEL_MODULE` env var for the CLI.
|
|
25
|
-
*/
|
|
6
|
+
registry?: IOIMDevRegistry;
|
|
7
|
+
/** Must export `oimDevRegistry`, a default, or a zero-arg factory. Env: `OIMDB_MODEL_MODULE`. */
|
|
26
8
|
modelModulePath?: string;
|
|
9
|
+
/** LOOPBACK BY DEFAULT: a `WebSocketServer` built without a host binds everything. */
|
|
10
|
+
host?: string;
|
|
11
|
+
/** On top of the loopback origins. Exact, never `'*'`. */
|
|
12
|
+
allowedOrigins?: readonly string[];
|
|
27
13
|
};
|
|
28
14
|
|
|
29
15
|
declare class OIMDevMcpServer {
|
|
30
16
|
private readonly mcpServer;
|
|
31
|
-
private readonly
|
|
32
|
-
private
|
|
33
|
-
private
|
|
34
|
-
private readonly pending;
|
|
35
|
-
private inProcessRegistry;
|
|
36
|
-
private readonly modelModulePath?;
|
|
37
|
-
private modelModuleLoaded;
|
|
17
|
+
private readonly devBridge;
|
|
18
|
+
private readonly devModuleStateSource;
|
|
19
|
+
private readonly devStateReader;
|
|
38
20
|
constructor(options?: TOIMDevMcpOptions);
|
|
39
|
-
/**
|
|
40
|
-
* Resolves the in-process registry, loading the model module on first use.
|
|
41
|
-
* Returns null when no in-process source is configured (browser-bridge mode).
|
|
42
|
-
*/
|
|
43
|
-
private getInProcessRegistry;
|
|
44
|
-
/** Reads the inspect snapshot from the in-process model or the browser. */
|
|
45
|
-
private readInspect;
|
|
46
|
-
/** Reads the human-readable dump from the in-process model or the browser. */
|
|
47
|
-
private readDump;
|
|
48
21
|
private registerTools;
|
|
49
|
-
private queryBrowser;
|
|
50
|
-
private setupWebSocketServer;
|
|
51
22
|
start(): Promise<void>;
|
|
52
23
|
}
|
|
53
24
|
|
|
54
|
-
type TOIMDevUIServerOptions = {
|
|
55
|
-
wsPort?: number;
|
|
56
|
-
uiPort?: number;
|
|
57
|
-
uiDir: string;
|
|
58
|
-
open?: boolean;
|
|
59
|
-
};
|
|
60
|
-
declare class OIMDevUIServer {
|
|
61
|
-
private readonly wsPort;
|
|
62
|
-
private readonly uiPort;
|
|
63
|
-
private readonly uiDir;
|
|
64
|
-
private readonly shouldOpen;
|
|
65
|
-
private wss;
|
|
66
|
-
private latestBrowser;
|
|
67
|
-
private readonly pending;
|
|
68
|
-
constructor(options: TOIMDevUIServerOptions);
|
|
69
|
-
private queryBrowser;
|
|
70
|
-
private setupBridge;
|
|
71
|
-
private setupHTTP;
|
|
72
|
-
private openURL;
|
|
73
|
-
start(): void;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
25
|
declare function createOIMDevMcpServer(options?: TOIMDevMcpOptions): OIMDevMcpServer;
|
|
77
|
-
declare function createOIMDevUIServer(options: TOIMDevUIServerOptions): OIMDevUIServer;
|
|
78
26
|
|
|
79
|
-
export {
|
|
27
|
+
export { OIMDevMcpServer, type TOIMDevMcpOptions, createOIMDevMcpServer };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,79 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* `@oimdb/devtools` already satisfies it. Declared structurally (following the
|
|
4
|
-
* existing `IOIMDev*Like` convention) so `@oimdb/mcp` need not depend on
|
|
5
|
-
* `@oimdb/devtools`; it is not a separate registry implementation.
|
|
6
|
-
*/
|
|
7
|
-
interface IOIMDevRegistryLike {
|
|
8
|
-
inspect(): unknown;
|
|
9
|
-
dumpString(): string;
|
|
10
|
-
}
|
|
1
|
+
import { IOIMDevRegistry } from '@oimdb/cli';
|
|
2
|
+
|
|
11
3
|
type TOIMDevMcpOptions = {
|
|
12
|
-
/**
|
|
4
|
+
/** Default 7432; unused when `registry` is given — no bridge is started. */
|
|
13
5
|
wsPort?: number;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* provided, the tools answer from it without a browser/WebSocket bridge
|
|
17
|
-
* (no `wsPort` server started).
|
|
18
|
-
*/
|
|
19
|
-
registry?: IOIMDevRegistryLike;
|
|
20
|
-
/**
|
|
21
|
-
* Path to a module that builds the model in this process for offline
|
|
22
|
-
* introspection. The module must export a `registry` (or a default export,
|
|
23
|
-
* or a zero-arg factory) exposing `inspect()` and `dumpString()`.
|
|
24
|
-
* Also settable via the `OIMDB_MODEL_MODULE` env var for the CLI.
|
|
25
|
-
*/
|
|
6
|
+
registry?: IOIMDevRegistry;
|
|
7
|
+
/** Must export `oimDevRegistry`, a default, or a zero-arg factory. Env: `OIMDB_MODEL_MODULE`. */
|
|
26
8
|
modelModulePath?: string;
|
|
9
|
+
/** LOOPBACK BY DEFAULT: a `WebSocketServer` built without a host binds everything. */
|
|
10
|
+
host?: string;
|
|
11
|
+
/** On top of the loopback origins. Exact, never `'*'`. */
|
|
12
|
+
allowedOrigins?: readonly string[];
|
|
27
13
|
};
|
|
28
14
|
|
|
29
15
|
declare class OIMDevMcpServer {
|
|
30
16
|
private readonly mcpServer;
|
|
31
|
-
private readonly
|
|
32
|
-
private
|
|
33
|
-
private
|
|
34
|
-
private readonly pending;
|
|
35
|
-
private inProcessRegistry;
|
|
36
|
-
private readonly modelModulePath?;
|
|
37
|
-
private modelModuleLoaded;
|
|
17
|
+
private readonly devBridge;
|
|
18
|
+
private readonly devModuleStateSource;
|
|
19
|
+
private readonly devStateReader;
|
|
38
20
|
constructor(options?: TOIMDevMcpOptions);
|
|
39
|
-
/**
|
|
40
|
-
* Resolves the in-process registry, loading the model module on first use.
|
|
41
|
-
* Returns null when no in-process source is configured (browser-bridge mode).
|
|
42
|
-
*/
|
|
43
|
-
private getInProcessRegistry;
|
|
44
|
-
/** Reads the inspect snapshot from the in-process model or the browser. */
|
|
45
|
-
private readInspect;
|
|
46
|
-
/** Reads the human-readable dump from the in-process model or the browser. */
|
|
47
|
-
private readDump;
|
|
48
21
|
private registerTools;
|
|
49
|
-
private queryBrowser;
|
|
50
|
-
private setupWebSocketServer;
|
|
51
22
|
start(): Promise<void>;
|
|
52
23
|
}
|
|
53
24
|
|
|
54
|
-
type TOIMDevUIServerOptions = {
|
|
55
|
-
wsPort?: number;
|
|
56
|
-
uiPort?: number;
|
|
57
|
-
uiDir: string;
|
|
58
|
-
open?: boolean;
|
|
59
|
-
};
|
|
60
|
-
declare class OIMDevUIServer {
|
|
61
|
-
private readonly wsPort;
|
|
62
|
-
private readonly uiPort;
|
|
63
|
-
private readonly uiDir;
|
|
64
|
-
private readonly shouldOpen;
|
|
65
|
-
private wss;
|
|
66
|
-
private latestBrowser;
|
|
67
|
-
private readonly pending;
|
|
68
|
-
constructor(options: TOIMDevUIServerOptions);
|
|
69
|
-
private queryBrowser;
|
|
70
|
-
private setupBridge;
|
|
71
|
-
private setupHTTP;
|
|
72
|
-
private openURL;
|
|
73
|
-
start(): void;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
25
|
declare function createOIMDevMcpServer(options?: TOIMDevMcpOptions): OIMDevMcpServer;
|
|
77
|
-
declare function createOIMDevUIServer(options: TOIMDevUIServerOptions): OIMDevUIServer;
|
|
78
26
|
|
|
79
|
-
export {
|
|
27
|
+
export { OIMDevMcpServer, type TOIMDevMcpOptions, createOIMDevMcpServer };
|
package/dist/index.js
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
// src/core/OIMDevMcpServer.ts
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { WebSocketServer } from "ws";
|
|
5
|
-
import { pathToFileURL } from "url";
|
|
6
|
-
import { resolve as resolvePath } from "path";
|
|
7
4
|
import { z } from "zod";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!candidate || typeof candidate.inspect !== "function" || typeof candidate.dumpString !== "function") {
|
|
16
|
-
throw new Error(
|
|
17
|
-
`[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).`
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
return candidate;
|
|
21
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
OIMDevBridge,
|
|
7
|
+
OIMDevBridgeStateSource,
|
|
8
|
+
OIMDevModuleStateSource,
|
|
9
|
+
OIMDevRegistryStateSource,
|
|
10
|
+
OIMDevStateReader
|
|
11
|
+
} from "@oimdb/cli";
|
|
22
12
|
var OIMDevMcpServer = class {
|
|
23
13
|
constructor(options = {}) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
const wsPort = options.wsPort ?? 7432;
|
|
15
|
+
let devStateSource;
|
|
16
|
+
if (options.registry !== void 0) {
|
|
17
|
+
devStateSource = new OIMDevRegistryStateSource(options.registry);
|
|
18
|
+
} else if (options.modelModulePath !== void 0) {
|
|
19
|
+
this.devModuleStateSource = new OIMDevModuleStateSource(options.modelModulePath);
|
|
20
|
+
devStateSource = this.devModuleStateSource;
|
|
21
|
+
} else {
|
|
22
|
+
this.devBridge = new OIMDevBridge({
|
|
23
|
+
wsPort,
|
|
24
|
+
host: options.host ?? "127.0.0.1",
|
|
25
|
+
allowedOrigins: new Set(options.allowedOrigins ?? []),
|
|
26
|
+
logPrefix: "[OIMDB MCP]",
|
|
27
|
+
portInUseHint: "npx @oimdb/mcp",
|
|
28
|
+
requestTimeoutMessage: "Browser response timeout (5s). Is the page active?"
|
|
29
|
+
});
|
|
30
|
+
devStateSource = new OIMDevBridgeStateSource(this.devBridge, wsPort);
|
|
31
|
+
}
|
|
32
|
+
this.devStateReader = new OIMDevStateReader(devStateSource);
|
|
31
33
|
this.mcpServer = new McpServer(
|
|
32
34
|
{ name: "oimdb", version: "1.0.0" },
|
|
33
35
|
{
|
|
@@ -36,292 +38,30 @@ var OIMDevMcpServer = class {
|
|
|
36
38
|
);
|
|
37
39
|
this.registerTools();
|
|
38
40
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Resolves the in-process registry, loading the model module on first use.
|
|
41
|
-
* Returns null when no in-process source is configured (browser-bridge mode).
|
|
42
|
-
*/
|
|
43
|
-
async getInProcessRegistry() {
|
|
44
|
-
if (this.inProcessRegistry) return this.inProcessRegistry;
|
|
45
|
-
if (this.modelModulePath && !this.modelModuleLoaded) {
|
|
46
|
-
this.modelModuleLoaded = true;
|
|
47
|
-
this.inProcessRegistry = await loadRegistryFromModule(
|
|
48
|
-
this.modelModulePath
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
return this.inProcessRegistry;
|
|
52
|
-
}
|
|
53
|
-
/** Reads the inspect snapshot from the in-process model or the browser. */
|
|
54
|
-
async readInspect() {
|
|
55
|
-
const registry = await this.getInProcessRegistry();
|
|
56
|
-
if (registry) return registry.inspect();
|
|
57
|
-
return this.queryBrowser("inspect");
|
|
58
|
-
}
|
|
59
|
-
/** Reads the human-readable dump from the in-process model or the browser. */
|
|
60
|
-
async readDump() {
|
|
61
|
-
const registry = await this.getInProcessRegistry();
|
|
62
|
-
if (registry) return registry.dumpString();
|
|
63
|
-
return await this.queryBrowser("dump");
|
|
64
|
-
}
|
|
65
41
|
registerTools() {
|
|
66
42
|
const server = this.mcpServer;
|
|
67
43
|
const nameSchema = { name: z.string().describe("Registered name") };
|
|
44
|
+
const asText = (text) => ({ content: [{ type: "text", text }] });
|
|
68
45
|
server.registerTool("oimdb_inspect", {
|
|
69
46
|
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)."
|
|
70
|
-
}, async () =>
|
|
71
|
-
const data = await this.readInspect();
|
|
72
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
73
|
-
});
|
|
47
|
+
}, async () => asText(await this.devStateReader.readInspectText()));
|
|
74
48
|
server.registerTool("oimdb_dump", {
|
|
75
|
-
description: "Get a human-readable text summary of OIMDB state. Same output as
|
|
76
|
-
}, async () =>
|
|
77
|
-
const text = await this.readDump();
|
|
78
|
-
return { content: [{ type: "text", text }] };
|
|
79
|
-
});
|
|
49
|
+
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."
|
|
50
|
+
}, async () => asText(await this.devStateReader.readDumpText()));
|
|
80
51
|
server.registerTool("oimdb_collection", {
|
|
81
52
|
description: "Get details of a specific registered collection: entity count, field names, index keys, and relations.",
|
|
82
53
|
inputSchema: nameSchema
|
|
83
|
-
}, async ({ name }) =>
|
|
84
|
-
const data = await this.readInspect();
|
|
85
|
-
const info = data.collections[name];
|
|
86
|
-
if (!info) {
|
|
87
|
-
const available = Object.keys(data.collections).join(", ") || "(none registered)";
|
|
88
|
-
return { content: [{ type: "text", text: `Collection '${name}' not found. Registered: ${available}` }] };
|
|
89
|
-
}
|
|
90
|
-
return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
|
|
91
|
-
});
|
|
54
|
+
}, async ({ name }) => asText(await this.devStateReader.readCollectionText(name)));
|
|
92
55
|
server.registerTool("oimdb_computed", {
|
|
93
56
|
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.",
|
|
94
57
|
inputSchema: nameSchema
|
|
95
|
-
}, async ({ name }) =>
|
|
96
|
-
const data = await this.readInspect();
|
|
97
|
-
const info = data.computeds[name];
|
|
98
|
-
if (!info) {
|
|
99
|
-
const available = Object.keys(data.computeds).join(", ") || "(none registered)";
|
|
100
|
-
return { content: [{ type: "text", text: `Computed '${name}' not found. Registered: ${available}` }] };
|
|
101
|
-
}
|
|
102
|
-
return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
async queryBrowser(action) {
|
|
106
|
-
if (!this.latestBrowser) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`No browser tab connected to the OIMDB MCP bridge.
|
|
109
|
-
|
|
110
|
-
Live mode \u2014 add to your debug entry point:
|
|
111
|
-
import { registry } from '@oimdb/devtools';
|
|
112
|
-
registry.connect(); // connects to ws://localhost:${this.wsPort}
|
|
113
|
-
Then reload the page.
|
|
114
|
-
|
|
115
|
-
Offline mode \u2014 point the server at a model module instead:
|
|
116
|
-
OIMDB_MODEL_MODULE=./path/to/model.js npx @oimdb/mcp
|
|
117
|
-
(the module exports a "registry" with inspect()/dumpString()).`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
return new Promise((resolve, reject) => {
|
|
121
|
-
const id = Math.random().toString(36).slice(2);
|
|
122
|
-
const timer = setTimeout(() => {
|
|
123
|
-
this.pending.delete(id);
|
|
124
|
-
reject(new Error("Browser response timeout (5s). Is the page active?"));
|
|
125
|
-
}, 5e3);
|
|
126
|
-
this.pending.set(id, { resolve, reject, timer });
|
|
127
|
-
this.latestBrowser.ws.send(JSON.stringify({ type: "request", id, action }));
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
setupWebSocketServer() {
|
|
131
|
-
this.wss = new WebSocketServer({ port: this.wsPort });
|
|
132
|
-
this.wss.on("error", (err) => {
|
|
133
|
-
if (err.code === "EADDRINUSE") {
|
|
134
|
-
process.stderr.write(
|
|
135
|
-
`[OIMDB MCP] Port ${this.wsPort} is already in use.
|
|
136
|
-
Set a different port: OIMDB_WS_PORT=7433 npx @oimdb/mcp
|
|
137
|
-
`
|
|
138
|
-
);
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
process.stderr.write(`[OIMDB MCP] WebSocket server error: ${err.message}
|
|
142
|
-
`);
|
|
143
|
-
process.exit(1);
|
|
144
|
-
});
|
|
145
|
-
this.wss.on("connection", (ws) => {
|
|
146
|
-
ws.on("message", (raw) => {
|
|
147
|
-
const msg = JSON.parse(raw.toString());
|
|
148
|
-
if (msg.type === "hello") {
|
|
149
|
-
this.latestBrowser = { ws, url: msg.url, title: msg.title };
|
|
150
|
-
process.stderr.write(`[OIMDB MCP] Browser connected: ${msg.title} (${msg.url})
|
|
151
|
-
`);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (msg.type === "response") {
|
|
155
|
-
const pending = this.pending.get(msg.id);
|
|
156
|
-
if (!pending) return;
|
|
157
|
-
this.pending.delete(msg.id);
|
|
158
|
-
clearTimeout(pending.timer);
|
|
159
|
-
if (msg.error) {
|
|
160
|
-
pending.reject(new Error(msg.error));
|
|
161
|
-
} else {
|
|
162
|
-
pending.resolve(msg.data);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
ws.on("close", () => {
|
|
167
|
-
if (this.latestBrowser?.ws === ws) {
|
|
168
|
-
this.latestBrowser = null;
|
|
169
|
-
process.stderr.write("[OIMDB MCP] Browser disconnected\n");
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
process.stderr.write(`[OIMDB MCP] Bridge ready on ws://localhost:${this.wsPort}
|
|
174
|
-
`);
|
|
58
|
+
}, async ({ name }) => asText(await this.devStateReader.readComputedText(name)));
|
|
175
59
|
}
|
|
176
60
|
async start() {
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
182
|
-
} else {
|
|
183
|
-
this.setupWebSocketServer();
|
|
184
|
-
}
|
|
185
|
-
const transport = new StdioServerTransport();
|
|
186
|
-
await this.mcpServer.connect(transport);
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// src/core/OIMDevUIServer.ts
|
|
191
|
-
import { createServer } from "http";
|
|
192
|
-
import { readFileSync } from "fs";
|
|
193
|
-
import { join, extname } from "path";
|
|
194
|
-
import { exec } from "child_process";
|
|
195
|
-
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
196
|
-
var MIME = {
|
|
197
|
-
".html": "text/html; charset=utf-8",
|
|
198
|
-
".js": "text/javascript; charset=utf-8",
|
|
199
|
-
".css": "text/css; charset=utf-8"
|
|
200
|
-
};
|
|
201
|
-
var OIMDevUIServer = class {
|
|
202
|
-
constructor(options) {
|
|
203
|
-
this.wss = null;
|
|
204
|
-
this.latestBrowser = null;
|
|
205
|
-
this.pending = /* @__PURE__ */ new Map();
|
|
206
|
-
this.wsPort = options.wsPort ?? 7432;
|
|
207
|
-
this.uiPort = options.uiPort ?? 7433;
|
|
208
|
-
this.uiDir = options.uiDir;
|
|
209
|
-
this.shouldOpen = options.open ?? true;
|
|
210
|
-
}
|
|
211
|
-
async queryBrowser(action) {
|
|
212
|
-
if (!this.latestBrowser) return null;
|
|
213
|
-
return new Promise((resolve, reject) => {
|
|
214
|
-
const id = Math.random().toString(36).slice(2);
|
|
215
|
-
const timer = setTimeout(() => {
|
|
216
|
-
this.pending.delete(id);
|
|
217
|
-
reject(new Error("timeout"));
|
|
218
|
-
}, 5e3);
|
|
219
|
-
this.pending.set(id, { resolve, reject, timer });
|
|
220
|
-
this.latestBrowser.ws.send(JSON.stringify({ type: "request", id, action }));
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
setupBridge() {
|
|
224
|
-
this.wss = new WebSocketServer2({ port: this.wsPort });
|
|
225
|
-
this.wss.on("error", (err) => {
|
|
226
|
-
if (err.code === "EADDRINUSE") {
|
|
227
|
-
process.stderr.write(
|
|
228
|
-
`[OIMDB DevTools] Port ${this.wsPort} is already in use.
|
|
229
|
-
Set a different port: OIMDB_WS_PORT=7433 npx @oimdb/mcp devtools
|
|
230
|
-
`
|
|
231
|
-
);
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
process.stderr.write(`[OIMDB DevTools] Bridge error: ${err.message}
|
|
235
|
-
`);
|
|
236
|
-
process.exit(1);
|
|
237
|
-
});
|
|
238
|
-
this.wss.on("connection", (ws) => {
|
|
239
|
-
ws.on("message", (raw) => {
|
|
240
|
-
const msg = JSON.parse(raw.toString());
|
|
241
|
-
if (msg.type === "hello") {
|
|
242
|
-
this.latestBrowser = { ws, url: msg.url, title: msg.title };
|
|
243
|
-
process.stderr.write(`[OIMDB DevTools] Browser connected: ${msg.title} (${msg.url})
|
|
244
|
-
`);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (msg.type === "response") {
|
|
248
|
-
const req = this.pending.get(msg.id);
|
|
249
|
-
if (!req) return;
|
|
250
|
-
this.pending.delete(msg.id);
|
|
251
|
-
clearTimeout(req.timer);
|
|
252
|
-
if (msg.error) req.reject(new Error(msg.error));
|
|
253
|
-
else req.resolve(msg.data);
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
ws.on("close", () => {
|
|
257
|
-
if (this.latestBrowser?.ws === ws) {
|
|
258
|
-
this.latestBrowser = null;
|
|
259
|
-
process.stderr.write("[OIMDB DevTools] Browser disconnected\n");
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
process.stderr.write(`[OIMDB DevTools] Bridge on ws://localhost:${this.wsPort}
|
|
264
|
-
`);
|
|
265
|
-
}
|
|
266
|
-
setupHTTP() {
|
|
267
|
-
const server = createServer((req, res) => {
|
|
268
|
-
const url = req.url ?? "/";
|
|
269
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
270
|
-
if (url === "/api/inspect") {
|
|
271
|
-
this.queryBrowser("inspect").then((data) => {
|
|
272
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
273
|
-
res.end(JSON.stringify({
|
|
274
|
-
connected: !!this.latestBrowser,
|
|
275
|
-
tab: this.latestBrowser ? { url: this.latestBrowser.url, title: this.latestBrowser.title } : null,
|
|
276
|
-
data
|
|
277
|
-
}));
|
|
278
|
-
}).catch(() => {
|
|
279
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
280
|
-
res.end(JSON.stringify({ connected: false, tab: null, data: null }));
|
|
281
|
-
});
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
const filePath = join(this.uiDir, url === "/" ? "index.html" : url);
|
|
285
|
-
try {
|
|
286
|
-
const content = readFileSync(filePath);
|
|
287
|
-
res.writeHead(200, { "Content-Type": MIME[extname(filePath)] ?? "text/plain" });
|
|
288
|
-
res.end(content);
|
|
289
|
-
} catch {
|
|
290
|
-
res.writeHead(404);
|
|
291
|
-
res.end("Not found");
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
server.on("error", (err) => {
|
|
295
|
-
if (err.code === "EADDRINUSE") {
|
|
296
|
-
process.stderr.write(
|
|
297
|
-
`[OIMDB DevTools] UI port ${this.uiPort} is already in use.
|
|
298
|
-
Set a different port: OIMDB_UI_PORT=7434 npx @oimdb/mcp devtools
|
|
299
|
-
`
|
|
300
|
-
);
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
server.listen(this.uiPort, () => {
|
|
305
|
-
const uiUrl = `http://localhost:${this.uiPort}`;
|
|
306
|
-
process.stderr.write(`[OIMDB DevTools] UI ready \u2192 ${uiUrl}
|
|
307
|
-
`);
|
|
308
|
-
if (this.shouldOpen) this.openURL(uiUrl);
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
openURL(url) {
|
|
312
|
-
const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
313
|
-
exec(cmd, (err) => {
|
|
314
|
-
if (err) process.stderr.write(`[OIMDB DevTools] Could not open browser: ${err.message}
|
|
315
|
-
`);
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
start() {
|
|
319
|
-
this.setupBridge();
|
|
320
|
-
this.setupHTTP();
|
|
321
|
-
process.stderr.write(
|
|
322
|
-
`[OIMDB DevTools] Add to debug.ts: registry.connect();
|
|
323
|
-
`
|
|
324
|
-
);
|
|
61
|
+
if (this.devModuleStateSource !== void 0) await this.devModuleStateSource.loadDevRegistry();
|
|
62
|
+
if (this.devBridge !== void 0) this.devBridge.start();
|
|
63
|
+
else process.stderr.write("[OIMDB MCP] In-process model loaded \u2014 serving offline (no browser bridge).\n");
|
|
64
|
+
await this.mcpServer.connect(new StdioServerTransport());
|
|
325
65
|
}
|
|
326
66
|
};
|
|
327
67
|
|
|
@@ -329,12 +69,7 @@ Set a different port: OIMDB_UI_PORT=7434 npx @oimdb/mcp devtools
|
|
|
329
69
|
function createOIMDevMcpServer(options) {
|
|
330
70
|
return new OIMDevMcpServer(options);
|
|
331
71
|
}
|
|
332
|
-
function createOIMDevUIServer(options) {
|
|
333
|
-
return new OIMDevUIServer(options);
|
|
334
|
-
}
|
|
335
72
|
export {
|
|
336
73
|
OIMDevMcpServer,
|
|
337
|
-
|
|
338
|
-
createOIMDevMcpServer,
|
|
339
|
-
createOIMDevUIServer
|
|
74
|
+
createOIMDevMcpServer
|
|
340
75
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oimdb/mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "MCP server
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "MCP server for OIMDB — gives AI assistants the oimdb_inspect, oimdb_dump, oimdb_collection and oimdb_computed tools over @oimdb/cli",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -14,30 +14,37 @@
|
|
|
14
14
|
".": {
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"import": "./dist/index.js",
|
|
17
|
-
"require":
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./dist/index.d.cts",
|
|
19
|
+
"default": "./dist/index.cjs"
|
|
20
|
+
}
|
|
18
21
|
}
|
|
19
22
|
},
|
|
23
|
+
"oimdbRelease": {
|
|
24
|
+
"publish": true
|
|
25
|
+
},
|
|
20
26
|
"bin": {
|
|
21
27
|
"oimdb-mcp": "bin/oimdb-mcp.cjs"
|
|
22
28
|
},
|
|
23
29
|
"files": [
|
|
24
30
|
"dist",
|
|
25
|
-
"bin"
|
|
26
|
-
"ui"
|
|
31
|
+
"bin"
|
|
27
32
|
],
|
|
28
33
|
"scripts": {
|
|
29
34
|
"build": "tsup --config tsup.config.ts --tsconfig tsconfig.build.json",
|
|
30
35
|
"dev": "tsup --config tsup.config.ts --tsconfig tsconfig.build.json --watch",
|
|
31
|
-
"type-check": "tsc --noEmit",
|
|
36
|
+
"type-check": "tsc --noEmit -p tsconfig.tests.json",
|
|
32
37
|
"prepublishOnly": "npm run type-check && npm run build"
|
|
33
38
|
},
|
|
34
39
|
"dependencies": {
|
|
35
40
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
-
"ws": "^8.18.0",
|
|
37
41
|
"zod": "^3.22.0"
|
|
38
42
|
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@oimdb/cli": "^1.0.0"
|
|
45
|
+
},
|
|
39
46
|
"devDependencies": {
|
|
40
|
-
"@
|
|
47
|
+
"@oimdb/cli": "*",
|
|
41
48
|
"tsup": "^8.0.0",
|
|
42
49
|
"typescript": "^5.2.2"
|
|
43
50
|
},
|