@modootoday/datalab-extension-mcp 1.8.8 → 1.8.10
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-AGEUPMJA.js} +17 -3
- package/dist/{chunk-U7AIWUSN.js → chunk-DOPJ4OZ4.js} +148 -149
- package/dist/{chunk-DR3Q6KT2.js → chunk-YB7L4SZC.js} +34 -4
- package/dist/cli.js +4 -4
- 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.10` 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.10 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.10 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.10 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.10`은 커넥터가 나도 모르게 바뀌지 않도록 고정한 버전이에요.)
|
|
53
53
|
|
|
54
54
|
```
|
|
55
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
55
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.10 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.10 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.10 uninstall
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
2. 확장 설정에서 **MCP 연결을 꺼요.** 패널이 브릿지라, 꺼 두면 어딘가 설정 줄이 남아 있어도 AI 앱이 아무것도 조회할 수 없어요.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_HOST,
|
|
3
3
|
DEFAULT_PORT
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DOPJ4OZ4.js";
|
|
5
5
|
import {
|
|
6
6
|
TOKEN_PROMPT_GUIDE,
|
|
7
7
|
TOKEN_PROMPT_QUESTION,
|
|
@@ -40,9 +40,9 @@ async function runBrowsers(argv, deps) {
|
|
|
40
40
|
const send = deps.fetchImpl ?? globalThis.fetch;
|
|
41
41
|
const rest = withoutFlag(argv, "--token");
|
|
42
42
|
const action = rest[0] ?? "list";
|
|
43
|
-
if (action !== "add" && action !== "list" && action !== "revoke") {
|
|
43
|
+
if (action !== "add" && action !== "list" && action !== "revoke" && action !== "repair") {
|
|
44
44
|
deps.out(
|
|
45
|
-
`usage: browsers [add <label>|list|revoke <handle
|
|
45
|
+
`usage: browsers [add <label>|list|revoke <handle>|repair] [--token <t>]`
|
|
46
46
|
);
|
|
47
47
|
return 2;
|
|
48
48
|
}
|
|
@@ -93,6 +93,13 @@ async function runBrowsers(argv, deps) {
|
|
|
93
93
|
}
|
|
94
94
|
if (action === "list") {
|
|
95
95
|
const rows = parsed["browsers"] ?? [];
|
|
96
|
+
const registryStatus = parsed["registryStatus"];
|
|
97
|
+
if (registryStatus?.state === "degraded") {
|
|
98
|
+
deps.out(
|
|
99
|
+
`Registry warning: ${String(registryStatus.error ?? "degraded")}.`
|
|
100
|
+
);
|
|
101
|
+
deps.out("Run: datalab-extension-mcp browsers repair");
|
|
102
|
+
}
|
|
96
103
|
if (rows.length === 0) {
|
|
97
104
|
deps.out("No browsers registered.");
|
|
98
105
|
return 0;
|
|
@@ -109,6 +116,13 @@ async function runBrowsers(argv, deps) {
|
|
|
109
116
|
);
|
|
110
117
|
return parsed["removed"] === true ? 0 : 1;
|
|
111
118
|
}
|
|
119
|
+
if (action === "repair") {
|
|
120
|
+
const removed = Array.isArray(parsed["removed"]) ? parsed["removed"].length : 0;
|
|
121
|
+
deps.out(
|
|
122
|
+
removed > 0 ? `Repaired ${String(removed)} conflicting browser registration.` : "No conflicting browser registrations found."
|
|
123
|
+
);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
112
126
|
deps.out(`Registered ${String(parsed["handle"])}.`);
|
|
113
127
|
deps.out(`Paste this into the browser's panel \u2014 it is shown only now:`);
|
|
114
128
|
deps.out(String(parsed["token"]));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../mcp-server/dist/chunk-
|
|
1
|
+
// ../mcp-server/dist/chunk-Q4ALBPO7.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";
|
|
@@ -24,8 +24,8 @@ import { spawn } from "child_process";
|
|
|
24
24
|
import { closeSync } from "fs";
|
|
25
25
|
import { randomBytes } from "crypto";
|
|
26
26
|
import { readFileSync } from "fs";
|
|
27
|
-
import { rename, writeFile as writeFile2 } from "fs/promises";
|
|
28
|
-
import { join as join3 } from "path";
|
|
27
|
+
import { mkdir as mkdir2, rename, writeFile as writeFile2 } from "fs/promises";
|
|
28
|
+
import { dirname, join as join3 } from "path";
|
|
29
29
|
import { createRequire } from "module";
|
|
30
30
|
import { join as join4 } from "path";
|
|
31
31
|
var __defProp = Object.defineProperty;
|
|
@@ -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({
|
|
@@ -14463,7 +14462,9 @@ var HelloNackReasonSchema = external_exports.enum([
|
|
|
14463
14462
|
/** Origin header absent or not our extension. */
|
|
14464
14463
|
"forbidden_origin",
|
|
14465
14464
|
/** This browser already has an active event stream. */
|
|
14466
|
-
"session_active"
|
|
14465
|
+
"session_active",
|
|
14466
|
+
/** Root authority cannot open a browser session. */
|
|
14467
|
+
"browser_registration_required"
|
|
14467
14468
|
]);
|
|
14468
14469
|
var BrowserRoutingSchema = external_exports.object({
|
|
14469
14470
|
targetBrowserId: external_exports.string(),
|
|
@@ -14653,6 +14654,14 @@ var REFUSED = {
|
|
|
14653
14654
|
message: "Pairing token missing or invalid."
|
|
14654
14655
|
};
|
|
14655
14656
|
function resolveCredential(presented, known) {
|
|
14657
|
+
const root = checkToken(presented.token, known.rootToken);
|
|
14658
|
+
if (root.ok) {
|
|
14659
|
+
return {
|
|
14660
|
+
kind: "refused",
|
|
14661
|
+
reason: "browser_registration_required",
|
|
14662
|
+
message: "The root token is management-only. Register this browser with its own token."
|
|
14663
|
+
};
|
|
14664
|
+
}
|
|
14656
14665
|
const { browserId } = presented;
|
|
14657
14666
|
if (browserId !== void 0 && browserId.length > 0) {
|
|
14658
14667
|
const expected = known.claimed.get(browserId);
|
|
@@ -14663,8 +14672,7 @@ function resolveCredential(presented, known) {
|
|
|
14663
14672
|
return REFUSED;
|
|
14664
14673
|
}
|
|
14665
14674
|
}
|
|
14666
|
-
|
|
14667
|
-
return root.ok ? { kind: "root" } : REFUSED;
|
|
14675
|
+
return REFUSED;
|
|
14668
14676
|
}
|
|
14669
14677
|
var READ_ONLY_TOOLS = [
|
|
14670
14678
|
"datalab_browsers",
|
|
@@ -15616,12 +15624,7 @@ var PendingRegistry = class {
|
|
|
15616
15624
|
function digestToken(token) {
|
|
15617
15625
|
return createHash("sha256").update(token, "utf8").digest("hex");
|
|
15618
15626
|
}
|
|
15619
|
-
var LEGACY_SESSION_KEY = "legacy";
|
|
15620
|
-
function sessionKeyFor(credential) {
|
|
15621
|
-
return credential.kind === "claimed" ? credential.browserId : LEGACY_SESSION_KEY;
|
|
15622
|
-
}
|
|
15623
15627
|
var DEFAULT_RATE_LIMIT = BRIDGE_LIMITS;
|
|
15624
|
-
var STALE_TOKEN_LIMIT = 5;
|
|
15625
15628
|
function daemonIdTag() {
|
|
15626
15629
|
return Math.floor(Math.random() * 16777215).toString(36).padStart(4, "0");
|
|
15627
15630
|
}
|
|
@@ -15631,8 +15634,7 @@ var Bridge = class {
|
|
|
15631
15634
|
#idTag = daemonIdTag();
|
|
15632
15635
|
#sessionSeq = 0;
|
|
15633
15636
|
#defaultKey = null;
|
|
15634
|
-
|
|
15635
|
-
#badTokenStreak = 0;
|
|
15637
|
+
#retireSession = null;
|
|
15636
15638
|
#bucketTokens = 0;
|
|
15637
15639
|
#bucketRefilledAt = 0;
|
|
15638
15640
|
// Last catalog the panel reported, kept across disconnects: many hosts cache
|
|
@@ -15642,6 +15644,12 @@ var Bridge = class {
|
|
|
15642
15644
|
constructor(deps) {
|
|
15643
15645
|
this.#deps = deps;
|
|
15644
15646
|
}
|
|
15647
|
+
bindRetireSession(retire) {
|
|
15648
|
+
if (this.#retireSession !== null) {
|
|
15649
|
+
throw new Error("bridge transport retirement is already bound");
|
|
15650
|
+
}
|
|
15651
|
+
this.#retireSession = retire;
|
|
15652
|
+
}
|
|
15645
15653
|
/**
|
|
15646
15654
|
* The session a request with no browser named goes to. Map preserves
|
|
15647
15655
|
* insertion order, so this is the oldest one — while the key space holds a
|
|
@@ -15673,6 +15681,9 @@ var Bridge = class {
|
|
|
15673
15681
|
primary: primary === id
|
|
15674
15682
|
}));
|
|
15675
15683
|
}
|
|
15684
|
+
browserRegistryStatus() {
|
|
15685
|
+
return this.#deps.browserRegistryStatus?.() ?? { state: "ready" };
|
|
15686
|
+
}
|
|
15676
15687
|
/**
|
|
15677
15688
|
* The only place a registry is born, and it is born already in the map.
|
|
15678
15689
|
* One living anywhere else would be missed by `sweep` and by `#closeSession`,
|
|
@@ -15696,7 +15707,6 @@ var Bridge = class {
|
|
|
15696
15707
|
key,
|
|
15697
15708
|
session,
|
|
15698
15709
|
tokenHash: auth.tokenHash,
|
|
15699
|
-
authRequired: auth.authRequired,
|
|
15700
15710
|
routingRequired: auth.routingRequired,
|
|
15701
15711
|
fileRequestBound: auth.fileRequestBound,
|
|
15702
15712
|
streamActive: false,
|
|
@@ -15748,9 +15758,6 @@ var Bridge = class {
|
|
|
15748
15758
|
sessionForKey(key) {
|
|
15749
15759
|
return this.#sessions.get(key)?.session ?? null;
|
|
15750
15760
|
}
|
|
15751
|
-
legacySessionKey() {
|
|
15752
|
-
return this.#sessions.has(LEGACY_SESSION_KEY) ? LEGACY_SESSION_KEY : null;
|
|
15753
|
-
}
|
|
15754
15761
|
/** Is a panel connected and ready to serve tool calls? */
|
|
15755
15762
|
get connected() {
|
|
15756
15763
|
return this.#primary() !== null;
|
|
@@ -15797,9 +15804,6 @@ var Bridge = class {
|
|
|
15797
15804
|
if (!timingSafeEqual(hash2, entry.tokenHash) || this.#deps.wasRevoked(hash2)) {
|
|
15798
15805
|
return null;
|
|
15799
15806
|
}
|
|
15800
|
-
if (entry.key === LEGACY_SESSION_KEY) {
|
|
15801
|
-
return this.verifyRootToken(presented) ? "root" : null;
|
|
15802
|
-
}
|
|
15803
15807
|
const current = this.#deps.claimedBrowsers.get(entry.key);
|
|
15804
15808
|
return current !== void 0 && timingSafeEqual(hash2, current) ? "claimed_browser" : null;
|
|
15805
15809
|
}
|
|
@@ -15811,33 +15815,6 @@ var Bridge = class {
|
|
|
15811
15815
|
}
|
|
15812
15816
|
return "unknown";
|
|
15813
15817
|
}
|
|
15814
|
-
authorizeLegacyFileCredential(presented) {
|
|
15815
|
-
if (typeof presented !== "string" || presented.length === 0) {
|
|
15816
|
-
return {
|
|
15817
|
-
ok: false,
|
|
15818
|
-
reason: "file_session_token_missing",
|
|
15819
|
-
credential: "missing"
|
|
15820
|
-
};
|
|
15821
|
-
}
|
|
15822
|
-
const credential = this.#fileCredentialClass(presented);
|
|
15823
|
-
for (const entry of this.#sessions.values()) {
|
|
15824
|
-
const accepted = this.#fileCredential(entry, presented);
|
|
15825
|
-
if (accepted && !entry.fileRequestBound) {
|
|
15826
|
-
return {
|
|
15827
|
-
ok: true,
|
|
15828
|
-
key: entry.key,
|
|
15829
|
-
sessionId: entry.session.sessionId,
|
|
15830
|
-
credential: accepted,
|
|
15831
|
-
requestBound: false
|
|
15832
|
-
};
|
|
15833
|
-
}
|
|
15834
|
-
}
|
|
15835
|
-
return {
|
|
15836
|
-
ok: false,
|
|
15837
|
-
reason: "file_session_token_mismatch",
|
|
15838
|
-
credential
|
|
15839
|
-
};
|
|
15840
|
-
}
|
|
15841
15818
|
authorizeFileRequest(input) {
|
|
15842
15819
|
const entry = [...this.#sessions.values()].find(
|
|
15843
15820
|
(candidate) => candidate.session.sessionId === input.sessionId
|
|
@@ -15856,7 +15833,7 @@ var Bridge = class {
|
|
|
15856
15833
|
credential: "missing",
|
|
15857
15834
|
key: entry.key,
|
|
15858
15835
|
sessionId: entry.session.sessionId,
|
|
15859
|
-
|
|
15836
|
+
requestId: input.requestId
|
|
15860
15837
|
};
|
|
15861
15838
|
}
|
|
15862
15839
|
const credential = this.#fileCredential(entry, input.token);
|
|
@@ -15867,32 +15844,23 @@ var Bridge = class {
|
|
|
15867
15844
|
credential: this.#fileCredentialClass(input.token),
|
|
15868
15845
|
key: entry.key,
|
|
15869
15846
|
sessionId: entry.session.sessionId,
|
|
15870
|
-
|
|
15847
|
+
requestId: input.requestId
|
|
15871
15848
|
};
|
|
15872
15849
|
}
|
|
15873
15850
|
if (!entry.fileRequestBound) {
|
|
15874
15851
|
return {
|
|
15875
|
-
ok:
|
|
15852
|
+
ok: false,
|
|
15853
|
+
reason: "file_request_expired",
|
|
15854
|
+
credential,
|
|
15876
15855
|
key: entry.key,
|
|
15877
15856
|
sessionId: entry.session.sessionId,
|
|
15878
|
-
|
|
15879
|
-
credential,
|
|
15880
|
-
requestBound: false
|
|
15857
|
+
requestId: input.requestId
|
|
15881
15858
|
};
|
|
15882
15859
|
}
|
|
15883
15860
|
const now = (this.#deps.now ?? Date.now)();
|
|
15884
15861
|
for (const [requestId, permit2] of entry.filePermits) {
|
|
15885
15862
|
if (permit2.expiresAt <= now) entry.filePermits.delete(requestId);
|
|
15886
15863
|
}
|
|
15887
|
-
if (input.requestId === void 0) {
|
|
15888
|
-
return {
|
|
15889
|
-
ok: false,
|
|
15890
|
-
reason: "file_request_expired",
|
|
15891
|
-
credential,
|
|
15892
|
-
key: entry.key,
|
|
15893
|
-
sessionId: entry.session.sessionId
|
|
15894
|
-
};
|
|
15895
|
-
}
|
|
15896
15864
|
const permit = entry.filePermits.get(input.requestId);
|
|
15897
15865
|
if (!permit) {
|
|
15898
15866
|
return {
|
|
@@ -15948,36 +15916,6 @@ var Bridge = class {
|
|
|
15948
15916
|
}
|
|
15949
15917
|
return null;
|
|
15950
15918
|
}
|
|
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
15919
|
/**
|
|
15982
15920
|
* Run the handshake.
|
|
15983
15921
|
*
|
|
@@ -16018,7 +15956,9 @@ var Bridge = class {
|
|
|
16018
15956
|
{ rootToken: this.#deps.token, claimed: this.#deps.claimedBrowsers }
|
|
16019
15957
|
);
|
|
16020
15958
|
if (credential.kind === "refused") {
|
|
16021
|
-
this.#
|
|
15959
|
+
this.#deps.log?.(
|
|
15960
|
+
this.#deps.wasRevoked(tokenHash) ? "refused a revoked browser credential" : "refused an unauthorized browser credential"
|
|
15961
|
+
);
|
|
16022
15962
|
return {
|
|
16023
15963
|
t: "hello_nack",
|
|
16024
15964
|
reason: credential.reason,
|
|
@@ -16026,7 +15966,14 @@ var Bridge = class {
|
|
|
16026
15966
|
message: credential.message
|
|
16027
15967
|
};
|
|
16028
15968
|
}
|
|
16029
|
-
if (credential.kind
|
|
15969
|
+
if (credential.kind !== "claimed") {
|
|
15970
|
+
return {
|
|
15971
|
+
t: "hello_nack",
|
|
15972
|
+
reason: "browser_registration_required",
|
|
15973
|
+
supported: [],
|
|
15974
|
+
message: "The root token is management-only. Register this browser with its own token."
|
|
15975
|
+
};
|
|
15976
|
+
}
|
|
16030
15977
|
const negotiated = negotiateProtocol(parsed.data.protocolVersions);
|
|
16031
15978
|
if (!negotiated.ok) {
|
|
16032
15979
|
return {
|
|
@@ -16036,14 +15983,10 @@ var Bridge = class {
|
|
|
16036
15983
|
message: negotiated.message
|
|
16037
15984
|
};
|
|
16038
15985
|
}
|
|
16039
|
-
const key =
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
reason: "session_active",
|
|
16044
|
-
supported: [BRIDGE_PROTOCOL_VERSION],
|
|
16045
|
-
message: "This browser already has an active panel session."
|
|
16046
|
-
};
|
|
15986
|
+
const key = credential.browserId;
|
|
15987
|
+
const held = this.#sessions.get(key);
|
|
15988
|
+
if (held?.streamActive) {
|
|
15989
|
+
this.#retireSession?.(key, held.session.sessionId);
|
|
16047
15990
|
}
|
|
16048
15991
|
const session = {
|
|
16049
15992
|
// A counter, not a timestamp: a panel reload produces two handshakes
|
|
@@ -16053,16 +15996,12 @@ var Bridge = class {
|
|
|
16053
15996
|
protocolVersion: negotiated.version,
|
|
16054
15997
|
extensionVersion: parsed.data.extensionVersion
|
|
16055
15998
|
};
|
|
16056
|
-
const authRequired = parsed.data.capabilities.includes(
|
|
16057
|
-
BRIDGE_AUTH_CAPABILITY
|
|
16058
|
-
);
|
|
16059
15999
|
const routingRequired = credential.kind === "claimed" && parsed.data.capabilities.includes(BROWSER_ROUTING_CAPABILITY);
|
|
16060
16000
|
const fileRequestBound = parsed.data.capabilities.includes(
|
|
16061
16001
|
LOCAL_FILE_REQUEST_CAPABILITY
|
|
16062
16002
|
);
|
|
16063
16003
|
this.#installSession(key, session, {
|
|
16064
16004
|
tokenHash,
|
|
16065
|
-
authRequired,
|
|
16066
16005
|
routingRequired,
|
|
16067
16006
|
fileRequestBound
|
|
16068
16007
|
});
|
|
@@ -16074,10 +16013,8 @@ var Bridge = class {
|
|
|
16074
16013
|
protocolVersion: negotiated.version,
|
|
16075
16014
|
serverVersion: this.#deps.serverVersion,
|
|
16076
16015
|
sessionId: session.sessionId,
|
|
16077
|
-
// Echoed only when it is actually on, so the panel can tell a server that
|
|
16078
|
-
// is holding it to the header from one that predates the gate entirely.
|
|
16079
16016
|
capabilities: [
|
|
16080
|
-
|
|
16017
|
+
BRIDGE_AUTH_CAPABILITY,
|
|
16081
16018
|
...routingRequired ? [BROWSER_ROUTING_CAPABILITY] : [],
|
|
16082
16019
|
...fileRequestBound ? [LOCAL_FILE_REQUEST_CAPABILITY] : []
|
|
16083
16020
|
]
|
|
@@ -16098,15 +16035,13 @@ var Bridge = class {
|
|
|
16098
16035
|
return this.#mayAct(entry, presented);
|
|
16099
16036
|
}
|
|
16100
16037
|
#mayAct(entry, presented) {
|
|
16101
|
-
if (!entry.authRequired) return true;
|
|
16102
16038
|
if (typeof presented !== "string" || presented.length === 0) return false;
|
|
16103
16039
|
return timingSafeEqual(digestToken(presented), entry.tokenHash);
|
|
16104
16040
|
}
|
|
16105
16041
|
/**
|
|
16106
16042
|
* Route a frame from the panel to whoever is waiting for it.
|
|
16107
16043
|
*
|
|
16108
|
-
* `presented` is the credential the poster carried.
|
|
16109
|
-
* be held to it refuses anything else; one that did not is unaffected.
|
|
16044
|
+
* `presented` is the credential the poster carried.
|
|
16110
16045
|
*/
|
|
16111
16046
|
receive(raw, presented) {
|
|
16112
16047
|
const classified = classifyFrame(
|
|
@@ -16298,7 +16233,7 @@ var Bridge = class {
|
|
|
16298
16233
|
if (browser === void 0) {
|
|
16299
16234
|
entry = this.#primary();
|
|
16300
16235
|
} else {
|
|
16301
|
-
const known = this.#deps.claimedBrowsers.has(browser)
|
|
16236
|
+
const known = this.#deps.claimedBrowsers.has(browser);
|
|
16302
16237
|
if (!known) {
|
|
16303
16238
|
return {
|
|
16304
16239
|
ok: false,
|
|
@@ -16887,14 +16822,22 @@ async function handleMcpRequest(bridge, body, accept, stage) {
|
|
|
16887
16822
|
delete args["browser"];
|
|
16888
16823
|
}
|
|
16889
16824
|
if (params.name === BROWSERS_TOOL) {
|
|
16825
|
+
const registryStatus = bridge.browserRegistryStatus();
|
|
16826
|
+
const { state: registryState, ...registryDetail } = registryStatus;
|
|
16827
|
+
const payload = {
|
|
16828
|
+
browsers: bridge.connectedBrowsers(),
|
|
16829
|
+
registryStatus: registryState,
|
|
16830
|
+
...registryState === "ready" ? {} : registryDetail
|
|
16831
|
+
};
|
|
16890
16832
|
return frameMcpResponse(
|
|
16891
16833
|
rpcResult(id, {
|
|
16892
16834
|
content: [
|
|
16893
16835
|
{
|
|
16894
16836
|
type: "text",
|
|
16895
|
-
text: JSON.stringify(
|
|
16837
|
+
text: JSON.stringify(payload)
|
|
16896
16838
|
}
|
|
16897
|
-
]
|
|
16839
|
+
],
|
|
16840
|
+
...registryState === "disabled" ? { isError: true } : {}
|
|
16898
16841
|
}),
|
|
16899
16842
|
accept
|
|
16900
16843
|
);
|
|
@@ -17194,6 +17137,13 @@ function createHttpBridge(opts) {
|
|
|
17194
17137
|
opts.bridge.disconnect(key);
|
|
17195
17138
|
if (wasPrimary) scheduleToolsDrop();
|
|
17196
17139
|
};
|
|
17140
|
+
opts.bridge.bindRetireSession((key, sessionId) => {
|
|
17141
|
+
const stream = streams.get(key);
|
|
17142
|
+
if (!stream || stream.sessionId !== sessionId) return;
|
|
17143
|
+
const wasPrimary = key === opts.bridge.sessionKey;
|
|
17144
|
+
removeStream(key)?.end();
|
|
17145
|
+
if (wasPrimary) scheduleToolsDrop();
|
|
17146
|
+
});
|
|
17197
17147
|
const server = createServer((req, res) => {
|
|
17198
17148
|
const url2 = new URL(req.url ?? "/", `http://${host}`);
|
|
17199
17149
|
const origin = req.headers.origin;
|
|
@@ -17235,6 +17185,18 @@ function createHttpBridge(opts) {
|
|
|
17235
17185
|
});
|
|
17236
17186
|
return;
|
|
17237
17187
|
}
|
|
17188
|
+
if (req.method === "POST" && url2.pathname === "/mcp/authority") {
|
|
17189
|
+
void readJson(req, MAX_BODY_BYTES).then((body) => {
|
|
17190
|
+
const token = body?.token;
|
|
17191
|
+
if (!opts.bridge.verifyRootToken(typeof token === "string" ? token : "")) {
|
|
17192
|
+
json2(res, 403, { error: "authority_mismatch" });
|
|
17193
|
+
return;
|
|
17194
|
+
}
|
|
17195
|
+
res.writeHead(204, { "cache-control": "no-store" });
|
|
17196
|
+
res.end();
|
|
17197
|
+
}).catch(() => json2(res, 400, { error: "bad request" }));
|
|
17198
|
+
return;
|
|
17199
|
+
}
|
|
17238
17200
|
if (req.method === "POST" && url2.pathname === "/bridge/hello") {
|
|
17239
17201
|
void readJson(req, MAX_BODY_BYTES).then((body) => {
|
|
17240
17202
|
const out = opts.bridge.handshake(body, origin);
|
|
@@ -17251,7 +17213,11 @@ function createHttpBridge(opts) {
|
|
|
17251
17213
|
}
|
|
17252
17214
|
if (req.method === "GET" && url2.pathname === "/bridge/events") {
|
|
17253
17215
|
const claimed = url2.searchParams.get("session");
|
|
17254
|
-
|
|
17216
|
+
if (claimed === null || claimed.length === 0) {
|
|
17217
|
+
json2(res, 400, { error: "session_required" });
|
|
17218
|
+
return;
|
|
17219
|
+
}
|
|
17220
|
+
const key = claimed === null ? null : opts.bridge.keyForSession(claimed);
|
|
17255
17221
|
if (key === null) {
|
|
17256
17222
|
opts.log?.("refused a stream for an unknown session");
|
|
17257
17223
|
res.writeHead(200, { "content-type": "text/event-stream" });
|
|
@@ -17278,13 +17244,8 @@ function createHttpBridge(opts) {
|
|
|
17278
17244
|
// by nature.
|
|
17279
17245
|
"x-accel-buffering": "no"
|
|
17280
17246
|
});
|
|
17281
|
-
const sessionId = claimed ?? opts.bridge.sessionForKey(key)?.sessionId ?? "";
|
|
17282
|
-
if (!sessionId) {
|
|
17283
|
-
res.end();
|
|
17284
|
-
return;
|
|
17285
|
-
}
|
|
17286
17247
|
try {
|
|
17287
|
-
installStream(key,
|
|
17248
|
+
installStream(key, claimed, res);
|
|
17288
17249
|
} catch {
|
|
17289
17250
|
res.end();
|
|
17290
17251
|
return;
|
|
@@ -17325,7 +17286,11 @@ function createHttpBridge(opts) {
|
|
|
17325
17286
|
}
|
|
17326
17287
|
const action = b?.["action"];
|
|
17327
17288
|
if (action === "list") {
|
|
17328
|
-
json2(res, 200, {
|
|
17289
|
+
json2(res, 200, {
|
|
17290
|
+
ok: true,
|
|
17291
|
+
browsers: await registry2.list(),
|
|
17292
|
+
registryStatus: registry2.status()
|
|
17293
|
+
});
|
|
17329
17294
|
return;
|
|
17330
17295
|
}
|
|
17331
17296
|
if (action === "add") {
|
|
@@ -17341,6 +17306,10 @@ function createHttpBridge(opts) {
|
|
|
17341
17306
|
});
|
|
17342
17307
|
return;
|
|
17343
17308
|
}
|
|
17309
|
+
if (action === "repair") {
|
|
17310
|
+
json2(res, 200, { ok: true, ...await registry2.repair() });
|
|
17311
|
+
return;
|
|
17312
|
+
}
|
|
17344
17313
|
json2(res, 400, { error: "unknown action" });
|
|
17345
17314
|
}).catch((err) => {
|
|
17346
17315
|
const why = err instanceof Error ? err.message : String(err);
|
|
@@ -17358,20 +17327,20 @@ function createHttpBridge(opts) {
|
|
|
17358
17327
|
const hasRequestId = b !== null && Object.hasOwn(b, "requestId");
|
|
17359
17328
|
const invalidSessionId = hasSessionId && (typeof b?.sessionId !== "string" || b.sessionId.length === 0);
|
|
17360
17329
|
const invalidRequestId = hasRequestId && (typeof b?.requestId !== "string" || b.requestId.length === 0);
|
|
17361
|
-
if (invalidSessionId || invalidRequestId) {
|
|
17330
|
+
if (!hasSessionId || !hasRequestId || invalidSessionId || invalidRequestId) {
|
|
17362
17331
|
json2(res, 403, {
|
|
17363
17332
|
error: "unauthorized",
|
|
17364
17333
|
message: "\uC0AC\uC9C4 \uC694\uCCAD\uC758 \uC5F0\uACB0 \uC815\uBCF4\uB97C \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC5B4\uC694."
|
|
17365
17334
|
});
|
|
17366
17335
|
return;
|
|
17367
17336
|
}
|
|
17368
|
-
const auth =
|
|
17337
|
+
const auth = opts.bridge.authorizeFileRequest({
|
|
17369
17338
|
sessionId: b.sessionId,
|
|
17370
|
-
|
|
17339
|
+
requestId: b.requestId,
|
|
17371
17340
|
token,
|
|
17372
17341
|
path,
|
|
17373
17342
|
...typeof b?.tool === "string" ? { tool: b.tool } : {}
|
|
17374
|
-
})
|
|
17343
|
+
});
|
|
17375
17344
|
if (!auth.ok) {
|
|
17376
17345
|
opts.log?.(
|
|
17377
17346
|
`file request refused: reason=${auth.reason} credential=${auth.credential} tokenPresent=${String(token.length > 0)} sessionMatch=${String(auth.key !== void 0)} open=not-entered`
|
|
@@ -17394,7 +17363,7 @@ function createHttpBridge(opts) {
|
|
|
17394
17363
|
return;
|
|
17395
17364
|
}
|
|
17396
17365
|
opts.log?.(
|
|
17397
|
-
`file request authorised: browser=${correlationValue(auth.key, 8)} session=${correlationValue(auth.sessionId, 32)} request=${
|
|
17366
|
+
`file request authorised: browser=${correlationValue(auth.key, 8)} session=${correlationValue(auth.sessionId, 32)} request=${correlationValue(auth.requestId, 96)} credential=${auth.credential} registryMatch=${String(auth.credential === "claimed_browser")} open=entered`
|
|
17398
17367
|
);
|
|
17399
17368
|
let source = null;
|
|
17400
17369
|
let owner = null;
|
|
@@ -17853,6 +17822,7 @@ async function writeLedger(path, claims) {
|
|
|
17853
17822
|
var writing = /* @__PURE__ */ new Map();
|
|
17854
17823
|
async function writeOnce(path, claims) {
|
|
17855
17824
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
17825
|
+
await mkdir2(dirname(path), { recursive: true, mode: 448 });
|
|
17856
17826
|
await writeFile2(tmp, `${JSON.stringify(claims, null, 2)}
|
|
17857
17827
|
`, {
|
|
17858
17828
|
mode: 384
|
|
@@ -17884,17 +17854,24 @@ function matches(claim, handle) {
|
|
|
17884
17854
|
function createBrowserRegistry(opts) {
|
|
17885
17855
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
17886
17856
|
const revoked = /* @__PURE__ */ new Set();
|
|
17887
|
-
const
|
|
17857
|
+
const readCurrent = () => {
|
|
17888
17858
|
const read = readLedger(opts.path);
|
|
17889
17859
|
if (read.state === "unreadable") {
|
|
17890
17860
|
throw new Error(`browser registry is unreadable: ${read.reason}`);
|
|
17891
17861
|
}
|
|
17892
17862
|
const rows = read.state === "ok" ? [...read.rows] : [];
|
|
17893
|
-
if (opts.forbiddenTokenHash
|
|
17894
|
-
|
|
17895
|
-
|
|
17896
|
-
|
|
17863
|
+
if (!opts.forbiddenTokenHash) return { rows, quarantined: [] };
|
|
17864
|
+
const quarantined = rows.filter(
|
|
17865
|
+
(row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
|
|
17866
|
+
);
|
|
17867
|
+
return {
|
|
17868
|
+
rows: rows.filter(
|
|
17869
|
+
(row) => !timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
|
|
17870
|
+
),
|
|
17871
|
+
quarantined
|
|
17872
|
+
};
|
|
17897
17873
|
};
|
|
17874
|
+
const current = () => readCurrent().rows;
|
|
17898
17875
|
return {
|
|
17899
17876
|
async add(label) {
|
|
17900
17877
|
const token = mintToken();
|
|
@@ -17951,6 +17928,33 @@ function createBrowserRegistry(opts) {
|
|
|
17951
17928
|
}
|
|
17952
17929
|
return true;
|
|
17953
17930
|
},
|
|
17931
|
+
async repair() {
|
|
17932
|
+
const view = readCurrent();
|
|
17933
|
+
if (view.quarantined.length === 0) return { removed: [] };
|
|
17934
|
+
await writeLedger(opts.path, view.rows);
|
|
17935
|
+
return { removed: view.quarantined.map(handleOf) };
|
|
17936
|
+
},
|
|
17937
|
+
status() {
|
|
17938
|
+
const read = readLedger(opts.path);
|
|
17939
|
+
if (read.state === "unreadable") {
|
|
17940
|
+
return {
|
|
17941
|
+
state: "disabled",
|
|
17942
|
+
error: "browser_registry_unreadable",
|
|
17943
|
+
detail: read.reason
|
|
17944
|
+
};
|
|
17945
|
+
}
|
|
17946
|
+
if (read.state === "ok" && opts.forbiddenTokenHash && ledgerOverlapsToken(read.rows, opts.forbiddenTokenHash)) {
|
|
17947
|
+
return {
|
|
17948
|
+
state: "degraded",
|
|
17949
|
+
error: "browser_registry_root_authority_overlap",
|
|
17950
|
+
quarantinedHandles: read.rows.filter(
|
|
17951
|
+
(row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
|
|
17952
|
+
).map(handleOf),
|
|
17953
|
+
recovery: "browsers repair"
|
|
17954
|
+
};
|
|
17955
|
+
}
|
|
17956
|
+
return { state: "ready" };
|
|
17957
|
+
},
|
|
17954
17958
|
wasRevoked(tokenHash) {
|
|
17955
17959
|
return revoked.has(tokenHash);
|
|
17956
17960
|
}
|
|
@@ -17983,12 +17987,12 @@ function runDaemon(env, deps = {}) {
|
|
|
17983
17987
|
const browserLabels = /* @__PURE__ */ new Map();
|
|
17984
17988
|
const ledger = readLedger(ledgerPath(deps.home));
|
|
17985
17989
|
const rootTokenHash = digestToken(token);
|
|
17986
|
-
const
|
|
17987
|
-
if (ledger.state === "ok"
|
|
17988
|
-
for (const [id, hash2] of claimedDigestsFrom(
|
|
17990
|
+
const safeLedgerRows = ledger.state === "ok" ? ledger.rows.filter((row) => row.tokenHash !== rootTokenHash) : [];
|
|
17991
|
+
if (ledger.state === "ok") {
|
|
17992
|
+
for (const [id, hash2] of claimedDigestsFrom(safeLedgerRows)) {
|
|
17989
17993
|
claimedBrowsers.set(id, hash2);
|
|
17990
17994
|
}
|
|
17991
|
-
for (const row of
|
|
17995
|
+
for (const row of safeLedgerRows) {
|
|
17992
17996
|
if (row.browserId !== null) browserLabels.set(row.browserId, row.label);
|
|
17993
17997
|
}
|
|
17994
17998
|
}
|
|
@@ -17997,26 +18001,21 @@ function runDaemon(env, deps = {}) {
|
|
|
17997
18001
|
`browser registry is unreadable (${ledger.reason}); registering is off`
|
|
17998
18002
|
);
|
|
17999
18003
|
}
|
|
18000
|
-
if (
|
|
18001
|
-
log(
|
|
18004
|
+
if (ledger.state === "ok" && ledgerOverlapsToken(ledger.rows, rootTokenHash)) {
|
|
18005
|
+
log(
|
|
18006
|
+
"browser registry overlaps the root authority; conflicting rows are quarantined"
|
|
18007
|
+
);
|
|
18002
18008
|
}
|
|
18003
18009
|
const bridge = new Bridge({
|
|
18004
18010
|
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
18011
|
token,
|
|
18013
18012
|
extensionIds,
|
|
18014
18013
|
serverVersion: version2,
|
|
18015
18014
|
rateLimit: deps.rateLimit,
|
|
18016
|
-
// Empty until a browser is registered
|
|
18017
|
-
// yet — so every panel still shares the one slot it always had.
|
|
18015
|
+
// Empty until a browser is registered.
|
|
18018
18016
|
claimedBrowsers,
|
|
18019
18017
|
browserLabels,
|
|
18018
|
+
browserRegistryStatus: () => registry2.status(),
|
|
18020
18019
|
claimBrowser: (browserId, tokenHash) => {
|
|
18021
18020
|
const took = registry2.claim(browserId, tokenHash);
|
|
18022
18021
|
if (took) {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
mediaToolDescriptors,
|
|
5
5
|
spawnDaemon,
|
|
6
6
|
tryConnect
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DOPJ4OZ4.js";
|
|
8
8
|
import {
|
|
9
9
|
__export
|
|
10
10
|
} from "./chunk-MLKGABMK.js";
|
|
@@ -14601,7 +14601,9 @@ var HelloNackReasonSchema = external_exports.enum([
|
|
|
14601
14601
|
/** Origin header absent or not our extension. */
|
|
14602
14602
|
"forbidden_origin",
|
|
14603
14603
|
/** This browser already has an active event stream. */
|
|
14604
|
-
"session_active"
|
|
14604
|
+
"session_active",
|
|
14605
|
+
/** Root authority cannot open a browser session. */
|
|
14606
|
+
"browser_registration_required"
|
|
14605
14607
|
]);
|
|
14606
14608
|
var BrowserRoutingSchema = external_exports.object({
|
|
14607
14609
|
targetBrowserId: external_exports.string(),
|
|
@@ -15586,6 +15588,21 @@ async function defaultShutdown(base, token) {
|
|
|
15586
15588
|
return false;
|
|
15587
15589
|
}
|
|
15588
15590
|
}
|
|
15591
|
+
async function defaultCheckAuthority(base, token) {
|
|
15592
|
+
try {
|
|
15593
|
+
const res = await globalThis.fetch(
|
|
15594
|
+
`${base}/mcp/authority`,
|
|
15595
|
+
{
|
|
15596
|
+
method: "POST",
|
|
15597
|
+
headers: { "content-type": "application/json" },
|
|
15598
|
+
body: JSON.stringify({ token })
|
|
15599
|
+
}
|
|
15600
|
+
);
|
|
15601
|
+
return res.status === 204;
|
|
15602
|
+
} catch {
|
|
15603
|
+
return false;
|
|
15604
|
+
}
|
|
15605
|
+
}
|
|
15589
15606
|
async function ensureDaemonRunning(deps = {}) {
|
|
15590
15607
|
const host = deps.host ?? DEFAULT_HOST;
|
|
15591
15608
|
const port = deps.port ?? DEFAULT_PORT;
|
|
@@ -15601,6 +15618,7 @@ async function ensureDaemonRunning(deps = {}) {
|
|
|
15601
15618
|
const token = deps.token ?? process.env["DATALAB_MCP_TOKEN"];
|
|
15602
15619
|
const readVersion = deps.readVersion ?? defaultReadVersion;
|
|
15603
15620
|
const shutdown = deps.shutdown ?? defaultShutdown;
|
|
15621
|
+
const checkAuthority = deps.checkAuthority ?? defaultCheckAuthority;
|
|
15604
15622
|
const spawnAndWait = async () => {
|
|
15605
15623
|
spawn(daemonEntry, ["serve"]);
|
|
15606
15624
|
for (let i = 0; i < attempts; i += 1) {
|
|
@@ -15623,14 +15641,26 @@ async function ensureDaemonRunning(deps = {}) {
|
|
|
15623
15641
|
fast.destroy();
|
|
15624
15642
|
if (!selfVersion || !token) return;
|
|
15625
15643
|
const running = await readVersion(base);
|
|
15626
|
-
if (!running
|
|
15644
|
+
if (!running) {
|
|
15645
|
+
throw new Error(
|
|
15646
|
+
`port ${String(port)} is occupied by something other than the DataLab connector`
|
|
15647
|
+
);
|
|
15648
|
+
}
|
|
15649
|
+
if (!isOlderVersion(running, selfVersion)) {
|
|
15650
|
+
if (await checkAuthority(base, token)) return;
|
|
15651
|
+
throw new Error(
|
|
15652
|
+
`connector authority conflict on port ${String(port)}; every MCP host on this machine must use the same DataLab workbench token`
|
|
15653
|
+
);
|
|
15654
|
+
}
|
|
15627
15655
|
log(`updating the connector: replacing ${running} with ${selfVersion}`);
|
|
15628
15656
|
const accepted = await shutdown(base, token);
|
|
15629
15657
|
if (!accepted) {
|
|
15630
15658
|
log(
|
|
15631
15659
|
"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
15660
|
);
|
|
15633
|
-
|
|
15661
|
+
throw new Error(
|
|
15662
|
+
`connector authority conflict on port ${String(port)}; the running connector did not accept this workbench token`
|
|
15663
|
+
);
|
|
15634
15664
|
}
|
|
15635
15665
|
for (let i = 0; i < attempts; i += 1) {
|
|
15636
15666
|
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-YB7L4SZC.js";
|
|
6
6
|
import {
|
|
7
7
|
runDaemon
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DOPJ4OZ4.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-AGEUPMJA.js");
|
|
66
66
|
const { createNodeIo } = await import("./dist-JICXADYR.js");
|
|
67
67
|
const io = createNodeIo();
|
|
68
68
|
const code = await runBrowsers(argv, {
|
|
@@ -109,7 +109,7 @@ async function main() {
|
|
|
109
109
|
unknown: (sub) => {
|
|
110
110
|
log(`unknown command "${sub}".`);
|
|
111
111
|
log(`usage: ${NAME} [install|uninstall|serve|browsers|skills]`);
|
|
112
|
-
log(` browsers [add <label>|list|revoke <handle
|
|
112
|
+
log(` browsers [add <label>|list|revoke <handle>|repair] [--token <t>]`);
|
|
113
113
|
log(` skills [list|status|attach <slug>|detach <slug>]`);
|
|
114
114
|
log(`run with no command to serve an MCP host over stdio.`);
|
|
115
115
|
process.exit(2);
|
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