@modootoday/datalab-extension-mcp 1.2.5

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.
@@ -0,0 +1,135 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
3
+
4
+ /**
5
+ * The thin stdio adapter — a stdio↔daemon proxy that owns nothing.
6
+ *
7
+ * An MCP host spawns this bin; it speaks MCP over stdio to that host and
8
+ * forwards the two methods it supports (`tools/list`, `tools/call`) to the
9
+ * background daemon over loopback HTTP. It binds no port and holds no bridge:
10
+ * the daemon owns 127.0.0.1:8765 and the panel connection, and many adapters
11
+ * (one per host) share that single daemon. This is why the earlier
12
+ * one-server-per-host model was replaced — every host raced for the same port
13
+ * and only one survived.
14
+ *
15
+ * Everything with an edge (the readiness probe, the child spawn, the HTTP
16
+ * fetch) is injectable, so the whole proxy is unit-testable with neither a real
17
+ * socket nor a real daemon.
18
+ */
19
+
20
+ /** The `fetch` shape this module needs — injectable so tests need no real HTTP. */
21
+ type FetchImpl = (url: string, init: {
22
+ method: string;
23
+ headers: Record<string, string>;
24
+ body: string;
25
+ /** Cancels the request once its ceiling passes. Tests may ignore it. */
26
+ signal?: AbortSignal;
27
+ }) => Promise<{
28
+ text: () => Promise<string>;
29
+ }>;
30
+ interface EnsureRunningDeps {
31
+ host?: string;
32
+ port?: number;
33
+ /** Probe for an existing daemon. Injected in tests; defaults to `tryConnect`. */
34
+ connect?: (host: string, port: number, timeoutMs: number) => Promise<{
35
+ destroy: () => void;
36
+ } | null>;
37
+ /** Start the daemon. Injected in tests; defaults to `spawnDaemon`. */
38
+ spawn?: (daemonEntry: string, args: string[]) => void;
39
+ /** Injected sleep so the ready-poll never waits real milliseconds in tests. */
40
+ sleep?: (ms: number) => Promise<void>;
41
+ /**
42
+ * Absolute path the spawner runs. Defaults to this bin (`process.argv[1]`):
43
+ * running THIS file with the `serve` subcommand starts the inlined daemon.
44
+ */
45
+ daemonEntry?: string;
46
+ log?: (message: string) => void;
47
+ attempts?: number;
48
+ intervalMs?: number;
49
+ /**
50
+ * This adapter's own version. When set, a running daemon OLDER than this is
51
+ * replaced (it is a stale one still holding the port after an update); a daemon
52
+ * at this version or newer is left alone, so an older adapter never downgrades
53
+ * a newer daemon. Unset ⇒ no reconciliation (the daemon is used as-is).
54
+ */
55
+ selfVersion?: string;
56
+ /** Pairing token authorising the shutdown. Defaults to `DATALAB_MCP_TOKEN`. */
57
+ token?: string;
58
+ /** Injected in tests; defaults to reading `/bridge/health`. */
59
+ readVersion?: (base: string) => Promise<string | null>;
60
+ /** Injected in tests; defaults to `POST /mcp/shutdown`. */
61
+ shutdown?: (base: string, token: string) => Promise<boolean>;
62
+ }
63
+ /**
64
+ * Ensure a daemon is up before the adapter starts proxying.
65
+ *
66
+ * Fast path first: if a daemon already owns the port, we are done — we only
67
+ * needed to know it exists, so the probe socket is closed immediately and the
68
+ * real calls go over HTTP. Otherwise spawn THIS bin as `serve` (which runs the
69
+ * inlined daemon) and poll until it answers. Racing is fine: whichever daemon
70
+ * binds first wins and the losers exit 0 on EADDRINUSE.
71
+ *
72
+ * If it never comes up inside the budget we log and return rather than throw —
73
+ * the panel-status card will show disconnected, which is a far better outcome
74
+ * than crashing the host's whole MCP session over a daemon that is slow to boot.
75
+ */
76
+ declare function ensureDaemonRunning(deps?: EnsureRunningDeps): Promise<void>;
77
+ interface AdapterServerDeps {
78
+ host?: string;
79
+ port?: number;
80
+ /** Injected in tests; defaults to the global `fetch`. */
81
+ fetchImpl?: FetchImpl;
82
+ log?: (message: string) => void;
83
+ /** MCP handshake identity reported to the host. */
84
+ name?: string;
85
+ version?: string;
86
+ }
87
+ /**
88
+ * Build the low-level MCP `Server` whose two handlers proxy to the daemon.
89
+ *
90
+ * The low-level `Server` (not `McpServer`) is the honest fit: we own no tools
91
+ * and declare no static schema — the catalog is discovered at runtime from the
92
+ * daemon and changes while running. The request-handler form matches that, and
93
+ * it is the same shape the daemon it replaced used, so the host sees no
94
+ * difference across the swap.
95
+ */
96
+ declare function createAdapterServer(deps?: AdapterServerDeps): Server;
97
+ /**
98
+ * Read the daemon's `/mcp/notifications` SSE stream, calling `onEvent` with each
99
+ * `data:` payload. Injectable so the subscription loop is testable without a
100
+ * real stream. Heartbeat comments (`: hb`) are not data lines, so they are
101
+ * skipped here and never reach `onEvent`.
102
+ */
103
+ type SubscribeImpl = (url: string, onEvent: (data: string) => void, signal: AbortSignal) => Promise<void>;
104
+ interface RunAdapterDeps extends AdapterServerDeps {
105
+ /** Injected in tests; defaults to `ensureDaemonRunning`. */
106
+ ensure?: (deps: EnsureRunningDeps) => Promise<void>;
107
+ /** Injected in tests; defaults to a real stdio transport. */
108
+ transport?: Transport;
109
+ /** Injected in tests; defaults to the real SSE subscription. */
110
+ subscribe?: SubscribeImpl;
111
+ }
112
+ /**
113
+ * Run the adapter: make sure a daemon exists, then serve MCP over stdio.
114
+ *
115
+ * The two legs are separate on purpose — `ensureDaemonRunning` deals with the
116
+ * socket/spawn edges and `createAdapterServer` with the request handlers — so
117
+ * each is testable alone. This function is the only place that touches the real
118
+ * stdio transport, which is why it stays a thin composition.
119
+ */
120
+ declare function runAdapter(deps?: RunAdapterDeps): Promise<void>;
121
+ interface CliHandlers {
122
+ install: (sub: "install" | "uninstall", argv: readonly string[]) => Promise<void>;
123
+ serve: () => void;
124
+ adapter: () => Promise<void>;
125
+ }
126
+ /**
127
+ * Route the process argv to one of the three subcommand handlers.
128
+ *
129
+ * Extracted from `main()` so the routing decision is testable with injected
130
+ * handlers, without spawning a daemon or connecting a transport. The default
131
+ * (no recognised subcommand) is the adapter, which is what an MCP host spawns.
132
+ */
133
+ declare function dispatchCli(argv: readonly string[], handlers: CliHandlers): Promise<void>;
134
+
135
+ export { type AdapterServerDeps, type CliHandlers, type EnsureRunningDeps, type FetchImpl, type RunAdapterDeps, createAdapterServer, dispatchCli, ensureDaemonRunning, runAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ createAdapterServer,
3
+ dispatchCli,
4
+ ensureDaemonRunning,
5
+ runAdapter
6
+ } from "./chunk-BF4AMIXC.js";
7
+ import "./chunk-MLKGABMK.js";
8
+ export {
9
+ createAdapterServer,
10
+ dispatchCli,
11
+ ensureDaemonRunning,
12
+ runAdapter
13
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@modootoday/datalab-extension-mcp",
3
+ "version": "1.2.5",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "datalab-extension-mcp": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "test": "vitest run",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.29.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=22.0.0",
30
+ "bun": ">=1.3.0"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/modootoday/datalab-extension-mcp.git",
38
+ "directory": "mcp"
39
+ },
40
+ "homepage": "https://github.com/modootoday/datalab-extension-mcp#readme",
41
+ "license": "MIT",
42
+ "description": "Local MCP server that exposes the DataLab Tools browser extension's tools to an MCP host. Read-only lookups run without a prompt; anything that edits a document or costs money is gated by the extension and confirmed by you first. Holds no credentials; every call runs inside your own browser session.",
43
+ "keywords": [
44
+ "mcp",
45
+ "modelcontextprotocol",
46
+ "chrome-extension",
47
+ "naver"
48
+ ]
49
+ }