@modootoday/datalab-extension-mcp 1.8.8 → 1.8.9
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.en.md +4 -4
- package/README.md +4 -4
- package/dist/{browsers-KBFQARNA.js → browsers-DRGNGPSI.js} +1 -1
- package/dist/{chunk-U7AIWUSN.js → chunk-GCRJQLTV.js} +33 -51
- package/dist/{chunk-DR3Q6KT2.js → chunk-WBMZLPXC.js} +31 -3
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -55,10 +55,10 @@ Avoid sharing a screenshot or the copied setup. If it may have leaked, press **[
|
|
|
55
55
|
One command finds **every** AI app on your computer and connects them automatically. Nothing to do per app.
|
|
56
56
|
|
|
57
57
|
1. **Open a terminal** — on Windows type `cmd` in the search box → "Command Prompt"; on Mac press `⌘+Space` and type `Terminal`; on Linux open your terminal app (often `Ctrl+Alt+T`).
|
|
58
|
-
2. **Paste this command and press Enter.** (`@1.8.
|
|
58
|
+
2. **Paste this command and press Enter.** (`@1.8.9` is the pinned version, so the connector never changes behind your back.)
|
|
59
59
|
|
|
60
60
|
```
|
|
61
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
61
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.9 install
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
Lines of English scrolling by is normal. This only downloads and runs the connector — it touches nothing else.
|
|
@@ -252,7 +252,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
|
|
|
252
252
|
- **When the connection disappears** — run the very command you used at the start once more; it safely re-registers. (Some AI apps clean up their settings file and drop the connection.)
|
|
253
253
|
|
|
254
254
|
```
|
|
255
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
255
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.9 install
|
|
256
256
|
```
|
|
257
257
|
|
|
258
258
|
- **`'npx' is not recognized...`** — Node.js isn't installed. The "No Node.js?" one-liner in Step 2 is the simplest fix.
|
|
@@ -282,7 +282,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
|
|
|
282
282
|
1. Run this in a terminal (it removes the entries registered in your AI apps).
|
|
283
283
|
|
|
284
284
|
```
|
|
285
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
285
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.9 uninstall
|
|
286
286
|
```
|
|
287
287
|
|
|
288
288
|
2. Turn **MCP 연결** off in the extension settings. The panel is the bridge, so with it off a leftover config line can look nothing up.
|
package/README.md
CHANGED
|
@@ -49,10 +49,10 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
|
|
|
49
49
|
명령 한 줄이면 컴퓨터에 있는 AI 앱을 **모두 자동으로** 찾아 연결해 줘요. 앱마다 따로 할 일은 없어요.
|
|
50
50
|
|
|
51
51
|
1. **터미널을 열어요** — Windows는 검색창에 `cmd` → "명령 프롬프트", Mac은 `⌘+스페이스`로 `터미널`, Linux는 터미널 앱(보통 `Ctrl+Alt+T`)이에요.
|
|
52
|
-
2. **아래 명령을 붙여넣고 Enter를 눌러요.** (`@1.8.
|
|
52
|
+
2. **아래 명령을 붙여넣고 Enter를 눌러요.** (`@1.8.9`은 커넥터가 나도 모르게 바뀌지 않도록 고정한 버전이에요.)
|
|
53
53
|
|
|
54
54
|
```
|
|
55
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
55
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.9 install
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
영어 글자가 여러 줄 올라오는 건 정상이에요. 이 명령은 커넥터를 내려받아 실행할 뿐, 다른 건 건드리지 않아요.
|
|
@@ -220,7 +220,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
|
|
|
220
220
|
- **연결이 사라졌을 때** — 처음 실행했던 그 명령을 한 번 더 실행하면 안전하게 다시 등록돼요. (일부 AI 앱이 설정 파일을 정리하며 연결을 지우기도 해요.)
|
|
221
221
|
|
|
222
222
|
```
|
|
223
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
223
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.9 install
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
- **`'npx'을(는) 내부 또는 외부 명령...`** — Node.js가 없는 거예요. 위 2단계의 "Node.js가 없다면" 한 줄 설치가 가장 간단해요.
|
|
@@ -250,7 +250,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
|
|
|
250
250
|
1. 터미널에서 아래 명령을 실행해요(AI 앱에 등록된 설정을 지워 줘요).
|
|
251
251
|
|
|
252
252
|
```
|
|
253
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
253
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.9 uninstall
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
2. 확장 설정에서 **MCP 연결을 꺼요.** 패널이 브릿지라, 꺼 두면 어딘가 설정 줄이 남아 있어도 AI 앱이 아무것도 조회할 수 없어요.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../mcp-server/dist/chunk-
|
|
1
|
+
// ../mcp-server/dist/chunk-TUYW5HMB.js
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
3
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
4
4
|
import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
|
|
@@ -14387,7 +14387,6 @@ function date4(params) {
|
|
|
14387
14387
|
return /* @__PURE__ */ _coercedDate(ZodDate, params);
|
|
14388
14388
|
}
|
|
14389
14389
|
config(en_default());
|
|
14390
|
-
var BRIDGE_PROTOCOL_VERSION = 1;
|
|
14391
14390
|
var SUPPORTED_PROTOCOL_VERSIONS = [1];
|
|
14392
14391
|
var MCP_TOOL_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
14393
14392
|
var BridgeToolDescriptorSchema = external_exports.object({
|
|
@@ -15621,7 +15620,6 @@ function sessionKeyFor(credential) {
|
|
|
15621
15620
|
return credential.kind === "claimed" ? credential.browserId : LEGACY_SESSION_KEY;
|
|
15622
15621
|
}
|
|
15623
15622
|
var DEFAULT_RATE_LIMIT = BRIDGE_LIMITS;
|
|
15624
|
-
var STALE_TOKEN_LIMIT = 5;
|
|
15625
15623
|
function daemonIdTag() {
|
|
15626
15624
|
return Math.floor(Math.random() * 16777215).toString(36).padStart(4, "0");
|
|
15627
15625
|
}
|
|
@@ -15631,8 +15629,7 @@ var Bridge = class {
|
|
|
15631
15629
|
#idTag = daemonIdTag();
|
|
15632
15630
|
#sessionSeq = 0;
|
|
15633
15631
|
#defaultKey = null;
|
|
15634
|
-
|
|
15635
|
-
#badTokenStreak = 0;
|
|
15632
|
+
#retireSession = null;
|
|
15636
15633
|
#bucketTokens = 0;
|
|
15637
15634
|
#bucketRefilledAt = 0;
|
|
15638
15635
|
// Last catalog the panel reported, kept across disconnects: many hosts cache
|
|
@@ -15642,6 +15639,12 @@ var Bridge = class {
|
|
|
15642
15639
|
constructor(deps) {
|
|
15643
15640
|
this.#deps = deps;
|
|
15644
15641
|
}
|
|
15642
|
+
bindRetireSession(retire) {
|
|
15643
|
+
if (this.#retireSession !== null) {
|
|
15644
|
+
throw new Error("bridge transport retirement is already bound");
|
|
15645
|
+
}
|
|
15646
|
+
this.#retireSession = retire;
|
|
15647
|
+
}
|
|
15645
15648
|
/**
|
|
15646
15649
|
* The session a request with no browser named goes to. Map preserves
|
|
15647
15650
|
* insertion order, so this is the oldest one — while the key space holds a
|
|
@@ -15948,36 +15951,6 @@ var Bridge = class {
|
|
|
15948
15951
|
}
|
|
15949
15952
|
return null;
|
|
15950
15953
|
}
|
|
15951
|
-
/**
|
|
15952
|
-
* The extension is right about the token, and we can be wrong about it.
|
|
15953
|
-
*
|
|
15954
|
-
* This server is detached and outlives the app that started it, and a
|
|
15955
|
-
* replacement only takes over when its version is newer. Once the user
|
|
15956
|
-
* issues a fresh token, stepping aside is gated on the very token that no
|
|
15957
|
-
* longer matches, so the server has to notice on its own. A run of refusals
|
|
15958
|
-
* with nobody connected says exactly that, and exiting costs nothing.
|
|
15959
|
-
*/
|
|
15960
|
-
#noteStaleToken(browserId, tokenHash) {
|
|
15961
|
-
if (this.#deps.wasRevoked(tokenHash)) {
|
|
15962
|
-
this.#deps.log?.("refused a revoked registration; not ours to leave for");
|
|
15963
|
-
return;
|
|
15964
|
-
}
|
|
15965
|
-
if (this.#isClaimed(browserId)) {
|
|
15966
|
-
this.#deps.log?.("refused a claimed browser; not ours to step aside for");
|
|
15967
|
-
return;
|
|
15968
|
-
}
|
|
15969
|
-
if (this.#sessions.has(LEGACY_SESSION_KEY)) return;
|
|
15970
|
-
this.#badTokenStreak += 1;
|
|
15971
|
-
if (this.#badTokenStreak < STALE_TOKEN_LIMIT) return;
|
|
15972
|
-
this.#deps.log?.(
|
|
15973
|
-
`refused ${this.#badTokenStreak} handshakes for a stale token; stepping aside`
|
|
15974
|
-
);
|
|
15975
|
-
this.#badTokenStreak = 0;
|
|
15976
|
-
this.#deps.onStaleToken?.();
|
|
15977
|
-
}
|
|
15978
|
-
#isClaimed(browserId) {
|
|
15979
|
-
return browserId !== void 0 && this.#deps.claimedBrowsers.has(browserId);
|
|
15980
|
-
}
|
|
15981
15954
|
/**
|
|
15982
15955
|
* Run the handshake.
|
|
15983
15956
|
*
|
|
@@ -16018,7 +15991,9 @@ var Bridge = class {
|
|
|
16018
15991
|
{ rootToken: this.#deps.token, claimed: this.#deps.claimedBrowsers }
|
|
16019
15992
|
);
|
|
16020
15993
|
if (credential.kind === "refused") {
|
|
16021
|
-
this.#
|
|
15994
|
+
this.#deps.log?.(
|
|
15995
|
+
this.#deps.wasRevoked(tokenHash) ? "refused a revoked browser credential" : "refused an unauthorized browser credential"
|
|
15996
|
+
);
|
|
16022
15997
|
return {
|
|
16023
15998
|
t: "hello_nack",
|
|
16024
15999
|
reason: credential.reason,
|
|
@@ -16026,7 +16001,6 @@ var Bridge = class {
|
|
|
16026
16001
|
message: credential.message
|
|
16027
16002
|
};
|
|
16028
16003
|
}
|
|
16029
|
-
if (credential.kind === "root") this.#badTokenStreak = 0;
|
|
16030
16004
|
const negotiated = negotiateProtocol(parsed.data.protocolVersions);
|
|
16031
16005
|
if (!negotiated.ok) {
|
|
16032
16006
|
return {
|
|
@@ -16037,13 +16011,9 @@ var Bridge = class {
|
|
|
16037
16011
|
};
|
|
16038
16012
|
}
|
|
16039
16013
|
const key = sessionKeyFor(credential);
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
reason: "session_active",
|
|
16044
|
-
supported: [BRIDGE_PROTOCOL_VERSION],
|
|
16045
|
-
message: "This browser already has an active panel session."
|
|
16046
|
-
};
|
|
16014
|
+
const held = this.#sessions.get(key);
|
|
16015
|
+
if (held?.streamActive) {
|
|
16016
|
+
this.#retireSession?.(key, held.session.sessionId);
|
|
16047
16017
|
}
|
|
16048
16018
|
const session = {
|
|
16049
16019
|
// A counter, not a timestamp: a panel reload produces two handshakes
|
|
@@ -17194,6 +17164,13 @@ function createHttpBridge(opts) {
|
|
|
17194
17164
|
opts.bridge.disconnect(key);
|
|
17195
17165
|
if (wasPrimary) scheduleToolsDrop();
|
|
17196
17166
|
};
|
|
17167
|
+
opts.bridge.bindRetireSession((key, sessionId) => {
|
|
17168
|
+
const stream = streams.get(key);
|
|
17169
|
+
if (!stream || stream.sessionId !== sessionId) return;
|
|
17170
|
+
const wasPrimary = key === opts.bridge.sessionKey;
|
|
17171
|
+
removeStream(key)?.end();
|
|
17172
|
+
if (wasPrimary) scheduleToolsDrop();
|
|
17173
|
+
});
|
|
17197
17174
|
const server = createServer((req, res) => {
|
|
17198
17175
|
const url2 = new URL(req.url ?? "/", `http://${host}`);
|
|
17199
17176
|
const origin = req.headers.origin;
|
|
@@ -17235,6 +17212,18 @@ function createHttpBridge(opts) {
|
|
|
17235
17212
|
});
|
|
17236
17213
|
return;
|
|
17237
17214
|
}
|
|
17215
|
+
if (req.method === "POST" && url2.pathname === "/mcp/authority") {
|
|
17216
|
+
void readJson(req, MAX_BODY_BYTES).then((body) => {
|
|
17217
|
+
const token = body?.token;
|
|
17218
|
+
if (!opts.bridge.verifyRootToken(typeof token === "string" ? token : "")) {
|
|
17219
|
+
json2(res, 403, { error: "authority_mismatch" });
|
|
17220
|
+
return;
|
|
17221
|
+
}
|
|
17222
|
+
res.writeHead(204, { "cache-control": "no-store" });
|
|
17223
|
+
res.end();
|
|
17224
|
+
}).catch(() => json2(res, 400, { error: "bad request" }));
|
|
17225
|
+
return;
|
|
17226
|
+
}
|
|
17238
17227
|
if (req.method === "POST" && url2.pathname === "/bridge/hello") {
|
|
17239
17228
|
void readJson(req, MAX_BODY_BYTES).then((body) => {
|
|
17240
17229
|
const out = opts.bridge.handshake(body, origin);
|
|
@@ -18002,13 +17991,6 @@ function runDaemon(env, deps = {}) {
|
|
|
18002
17991
|
}
|
|
18003
17992
|
const bridge = new Bridge({
|
|
18004
17993
|
send: (frame, key) => http.send(frame, key),
|
|
18005
|
-
// Step aside once our own token looks stale. This process is detached
|
|
18006
|
-
// and survives an app restart, and a replacement only takes over on a newer
|
|
18007
|
-
// version — so nothing outside can unblock the install if we do not.
|
|
18008
|
-
onStaleToken: () => {
|
|
18009
|
-
log("pairing token is stale; exiting so the next call can start fresh");
|
|
18010
|
-
void http.close().finally(() => exit(0));
|
|
18011
|
-
},
|
|
18012
17994
|
token,
|
|
18013
17995
|
extensionIds,
|
|
18014
17996
|
serverVersion: version2,
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
mediaToolDescriptors,
|
|
5
5
|
spawnDaemon,
|
|
6
6
|
tryConnect
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GCRJQLTV.js";
|
|
8
8
|
import {
|
|
9
9
|
__export
|
|
10
10
|
} from "./chunk-MLKGABMK.js";
|
|
@@ -15586,6 +15586,21 @@ async function defaultShutdown(base, token) {
|
|
|
15586
15586
|
return false;
|
|
15587
15587
|
}
|
|
15588
15588
|
}
|
|
15589
|
+
async function defaultCheckAuthority(base, token) {
|
|
15590
|
+
try {
|
|
15591
|
+
const res = await globalThis.fetch(
|
|
15592
|
+
`${base}/mcp/authority`,
|
|
15593
|
+
{
|
|
15594
|
+
method: "POST",
|
|
15595
|
+
headers: { "content-type": "application/json" },
|
|
15596
|
+
body: JSON.stringify({ token })
|
|
15597
|
+
}
|
|
15598
|
+
);
|
|
15599
|
+
return res.status === 204;
|
|
15600
|
+
} catch {
|
|
15601
|
+
return false;
|
|
15602
|
+
}
|
|
15603
|
+
}
|
|
15589
15604
|
async function ensureDaemonRunning(deps = {}) {
|
|
15590
15605
|
const host = deps.host ?? DEFAULT_HOST;
|
|
15591
15606
|
const port = deps.port ?? DEFAULT_PORT;
|
|
@@ -15601,6 +15616,7 @@ async function ensureDaemonRunning(deps = {}) {
|
|
|
15601
15616
|
const token = deps.token ?? process.env["DATALAB_MCP_TOKEN"];
|
|
15602
15617
|
const readVersion = deps.readVersion ?? defaultReadVersion;
|
|
15603
15618
|
const shutdown = deps.shutdown ?? defaultShutdown;
|
|
15619
|
+
const checkAuthority = deps.checkAuthority ?? defaultCheckAuthority;
|
|
15604
15620
|
const spawnAndWait = async () => {
|
|
15605
15621
|
spawn(daemonEntry, ["serve"]);
|
|
15606
15622
|
for (let i = 0; i < attempts; i += 1) {
|
|
@@ -15623,14 +15639,26 @@ async function ensureDaemonRunning(deps = {}) {
|
|
|
15623
15639
|
fast.destroy();
|
|
15624
15640
|
if (!selfVersion || !token) return;
|
|
15625
15641
|
const running = await readVersion(base);
|
|
15626
|
-
if (!running
|
|
15642
|
+
if (!running) {
|
|
15643
|
+
throw new Error(
|
|
15644
|
+
`port ${String(port)} is occupied by something other than the DataLab connector`
|
|
15645
|
+
);
|
|
15646
|
+
}
|
|
15647
|
+
if (!isOlderVersion(running, selfVersion)) {
|
|
15648
|
+
if (await checkAuthority(base, token)) return;
|
|
15649
|
+
throw new Error(
|
|
15650
|
+
`connector authority conflict on port ${String(port)}; every MCP host on this machine must use the same DataLab workbench token`
|
|
15651
|
+
);
|
|
15652
|
+
}
|
|
15627
15653
|
log(`updating the connector: replacing ${running} with ${selfVersion}`);
|
|
15628
15654
|
const accepted = await shutdown(base, token);
|
|
15629
15655
|
if (!accepted) {
|
|
15630
15656
|
log(
|
|
15631
15657
|
"the running connector is too old to update automatically; restart it (or wait for it to idle out) to pick up the new version"
|
|
15632
15658
|
);
|
|
15633
|
-
|
|
15659
|
+
throw new Error(
|
|
15660
|
+
`connector authority conflict on port ${String(port)}; the running connector did not accept this workbench token`
|
|
15661
|
+
);
|
|
15634
15662
|
}
|
|
15635
15663
|
for (let i = 0; i < attempts; i += 1) {
|
|
15636
15664
|
const socket = await connect(host, port, PROBE_TIMEOUT_MS);
|
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
dispatchCli,
|
|
4
4
|
runAdapter
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WBMZLPXC.js";
|
|
6
6
|
import {
|
|
7
7
|
runDaemon
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GCRJQLTV.js";
|
|
9
9
|
import "./chunk-MLKGABMK.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
@@ -62,7 +62,7 @@ async function main() {
|
|
|
62
62
|
// Loaded lazily, like the installer: the adapter path is what an MCP host
|
|
63
63
|
// spawns and must not pay for a command only an operator types.
|
|
64
64
|
browsers: async (argv) => {
|
|
65
|
-
const { runBrowsers } = await import("./browsers-
|
|
65
|
+
const { runBrowsers } = await import("./browsers-DRGNGPSI.js");
|
|
66
66
|
const { createNodeIo } = await import("./dist-JICXADYR.js");
|
|
67
67
|
const io = createNodeIo();
|
|
68
68
|
const code = await runBrowsers(argv, {
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,8 @@ interface EnsureRunningDeps {
|
|
|
62
62
|
readVersion?: (base: string) => Promise<string | null>;
|
|
63
63
|
/** Injected in tests; defaults to `POST /mcp/shutdown`. */
|
|
64
64
|
shutdown?: (base: string, token: string) => Promise<boolean>;
|
|
65
|
+
/** Injected in tests; defaults to the authenticated authority probe. */
|
|
66
|
+
checkAuthority?: (base: string, token: string) => Promise<boolean>;
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
69
|
* Ensure a daemon is up before the adapter starts proxying. A daemon already
|
package/dist/index.js
CHANGED