@modootoday/datalab-extension-mcp 1.8.9 → 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-DRGNGPSI.js → browsers-AGEUPMJA.js} +17 -3
- package/dist/{chunk-GCRJQLTV.js → chunk-DOPJ4OZ4.js} +116 -99
- package/dist/{chunk-WBMZLPXC.js → chunk-YB7L4SZC.js} +4 -2
- package/dist/cli.js +4 -4
- 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;
|
|
@@ -14462,7 +14462,9 @@ var HelloNackReasonSchema = external_exports.enum([
|
|
|
14462
14462
|
/** Origin header absent or not our extension. */
|
|
14463
14463
|
"forbidden_origin",
|
|
14464
14464
|
/** This browser already has an active event stream. */
|
|
14465
|
-
"session_active"
|
|
14465
|
+
"session_active",
|
|
14466
|
+
/** Root authority cannot open a browser session. */
|
|
14467
|
+
"browser_registration_required"
|
|
14466
14468
|
]);
|
|
14467
14469
|
var BrowserRoutingSchema = external_exports.object({
|
|
14468
14470
|
targetBrowserId: external_exports.string(),
|
|
@@ -14652,6 +14654,14 @@ var REFUSED = {
|
|
|
14652
14654
|
message: "Pairing token missing or invalid."
|
|
14653
14655
|
};
|
|
14654
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
|
+
}
|
|
14655
14665
|
const { browserId } = presented;
|
|
14656
14666
|
if (browserId !== void 0 && browserId.length > 0) {
|
|
14657
14667
|
const expected = known.claimed.get(browserId);
|
|
@@ -14662,8 +14672,7 @@ function resolveCredential(presented, known) {
|
|
|
14662
14672
|
return REFUSED;
|
|
14663
14673
|
}
|
|
14664
14674
|
}
|
|
14665
|
-
|
|
14666
|
-
return root.ok ? { kind: "root" } : REFUSED;
|
|
14675
|
+
return REFUSED;
|
|
14667
14676
|
}
|
|
14668
14677
|
var READ_ONLY_TOOLS = [
|
|
14669
14678
|
"datalab_browsers",
|
|
@@ -15615,10 +15624,6 @@ var PendingRegistry = class {
|
|
|
15615
15624
|
function digestToken(token) {
|
|
15616
15625
|
return createHash("sha256").update(token, "utf8").digest("hex");
|
|
15617
15626
|
}
|
|
15618
|
-
var LEGACY_SESSION_KEY = "legacy";
|
|
15619
|
-
function sessionKeyFor(credential) {
|
|
15620
|
-
return credential.kind === "claimed" ? credential.browserId : LEGACY_SESSION_KEY;
|
|
15621
|
-
}
|
|
15622
15627
|
var DEFAULT_RATE_LIMIT = BRIDGE_LIMITS;
|
|
15623
15628
|
function daemonIdTag() {
|
|
15624
15629
|
return Math.floor(Math.random() * 16777215).toString(36).padStart(4, "0");
|
|
@@ -15676,6 +15681,9 @@ var Bridge = class {
|
|
|
15676
15681
|
primary: primary === id
|
|
15677
15682
|
}));
|
|
15678
15683
|
}
|
|
15684
|
+
browserRegistryStatus() {
|
|
15685
|
+
return this.#deps.browserRegistryStatus?.() ?? { state: "ready" };
|
|
15686
|
+
}
|
|
15679
15687
|
/**
|
|
15680
15688
|
* The only place a registry is born, and it is born already in the map.
|
|
15681
15689
|
* One living anywhere else would be missed by `sweep` and by `#closeSession`,
|
|
@@ -15699,7 +15707,6 @@ var Bridge = class {
|
|
|
15699
15707
|
key,
|
|
15700
15708
|
session,
|
|
15701
15709
|
tokenHash: auth.tokenHash,
|
|
15702
|
-
authRequired: auth.authRequired,
|
|
15703
15710
|
routingRequired: auth.routingRequired,
|
|
15704
15711
|
fileRequestBound: auth.fileRequestBound,
|
|
15705
15712
|
streamActive: false,
|
|
@@ -15751,9 +15758,6 @@ var Bridge = class {
|
|
|
15751
15758
|
sessionForKey(key) {
|
|
15752
15759
|
return this.#sessions.get(key)?.session ?? null;
|
|
15753
15760
|
}
|
|
15754
|
-
legacySessionKey() {
|
|
15755
|
-
return this.#sessions.has(LEGACY_SESSION_KEY) ? LEGACY_SESSION_KEY : null;
|
|
15756
|
-
}
|
|
15757
15761
|
/** Is a panel connected and ready to serve tool calls? */
|
|
15758
15762
|
get connected() {
|
|
15759
15763
|
return this.#primary() !== null;
|
|
@@ -15800,9 +15804,6 @@ var Bridge = class {
|
|
|
15800
15804
|
if (!timingSafeEqual(hash2, entry.tokenHash) || this.#deps.wasRevoked(hash2)) {
|
|
15801
15805
|
return null;
|
|
15802
15806
|
}
|
|
15803
|
-
if (entry.key === LEGACY_SESSION_KEY) {
|
|
15804
|
-
return this.verifyRootToken(presented) ? "root" : null;
|
|
15805
|
-
}
|
|
15806
15807
|
const current = this.#deps.claimedBrowsers.get(entry.key);
|
|
15807
15808
|
return current !== void 0 && timingSafeEqual(hash2, current) ? "claimed_browser" : null;
|
|
15808
15809
|
}
|
|
@@ -15814,33 +15815,6 @@ var Bridge = class {
|
|
|
15814
15815
|
}
|
|
15815
15816
|
return "unknown";
|
|
15816
15817
|
}
|
|
15817
|
-
authorizeLegacyFileCredential(presented) {
|
|
15818
|
-
if (typeof presented !== "string" || presented.length === 0) {
|
|
15819
|
-
return {
|
|
15820
|
-
ok: false,
|
|
15821
|
-
reason: "file_session_token_missing",
|
|
15822
|
-
credential: "missing"
|
|
15823
|
-
};
|
|
15824
|
-
}
|
|
15825
|
-
const credential = this.#fileCredentialClass(presented);
|
|
15826
|
-
for (const entry of this.#sessions.values()) {
|
|
15827
|
-
const accepted = this.#fileCredential(entry, presented);
|
|
15828
|
-
if (accepted && !entry.fileRequestBound) {
|
|
15829
|
-
return {
|
|
15830
|
-
ok: true,
|
|
15831
|
-
key: entry.key,
|
|
15832
|
-
sessionId: entry.session.sessionId,
|
|
15833
|
-
credential: accepted,
|
|
15834
|
-
requestBound: false
|
|
15835
|
-
};
|
|
15836
|
-
}
|
|
15837
|
-
}
|
|
15838
|
-
return {
|
|
15839
|
-
ok: false,
|
|
15840
|
-
reason: "file_session_token_mismatch",
|
|
15841
|
-
credential
|
|
15842
|
-
};
|
|
15843
|
-
}
|
|
15844
15818
|
authorizeFileRequest(input) {
|
|
15845
15819
|
const entry = [...this.#sessions.values()].find(
|
|
15846
15820
|
(candidate) => candidate.session.sessionId === input.sessionId
|
|
@@ -15859,7 +15833,7 @@ var Bridge = class {
|
|
|
15859
15833
|
credential: "missing",
|
|
15860
15834
|
key: entry.key,
|
|
15861
15835
|
sessionId: entry.session.sessionId,
|
|
15862
|
-
|
|
15836
|
+
requestId: input.requestId
|
|
15863
15837
|
};
|
|
15864
15838
|
}
|
|
15865
15839
|
const credential = this.#fileCredential(entry, input.token);
|
|
@@ -15870,32 +15844,23 @@ var Bridge = class {
|
|
|
15870
15844
|
credential: this.#fileCredentialClass(input.token),
|
|
15871
15845
|
key: entry.key,
|
|
15872
15846
|
sessionId: entry.session.sessionId,
|
|
15873
|
-
|
|
15847
|
+
requestId: input.requestId
|
|
15874
15848
|
};
|
|
15875
15849
|
}
|
|
15876
15850
|
if (!entry.fileRequestBound) {
|
|
15877
15851
|
return {
|
|
15878
|
-
ok:
|
|
15852
|
+
ok: false,
|
|
15853
|
+
reason: "file_request_expired",
|
|
15854
|
+
credential,
|
|
15879
15855
|
key: entry.key,
|
|
15880
15856
|
sessionId: entry.session.sessionId,
|
|
15881
|
-
|
|
15882
|
-
credential,
|
|
15883
|
-
requestBound: false
|
|
15857
|
+
requestId: input.requestId
|
|
15884
15858
|
};
|
|
15885
15859
|
}
|
|
15886
15860
|
const now = (this.#deps.now ?? Date.now)();
|
|
15887
15861
|
for (const [requestId, permit2] of entry.filePermits) {
|
|
15888
15862
|
if (permit2.expiresAt <= now) entry.filePermits.delete(requestId);
|
|
15889
15863
|
}
|
|
15890
|
-
if (input.requestId === void 0) {
|
|
15891
|
-
return {
|
|
15892
|
-
ok: false,
|
|
15893
|
-
reason: "file_request_expired",
|
|
15894
|
-
credential,
|
|
15895
|
-
key: entry.key,
|
|
15896
|
-
sessionId: entry.session.sessionId
|
|
15897
|
-
};
|
|
15898
|
-
}
|
|
15899
15864
|
const permit = entry.filePermits.get(input.requestId);
|
|
15900
15865
|
if (!permit) {
|
|
15901
15866
|
return {
|
|
@@ -16001,6 +15966,14 @@ var Bridge = class {
|
|
|
16001
15966
|
message: credential.message
|
|
16002
15967
|
};
|
|
16003
15968
|
}
|
|
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
|
+
}
|
|
16004
15977
|
const negotiated = negotiateProtocol(parsed.data.protocolVersions);
|
|
16005
15978
|
if (!negotiated.ok) {
|
|
16006
15979
|
return {
|
|
@@ -16010,7 +15983,7 @@ var Bridge = class {
|
|
|
16010
15983
|
message: negotiated.message
|
|
16011
15984
|
};
|
|
16012
15985
|
}
|
|
16013
|
-
const key =
|
|
15986
|
+
const key = credential.browserId;
|
|
16014
15987
|
const held = this.#sessions.get(key);
|
|
16015
15988
|
if (held?.streamActive) {
|
|
16016
15989
|
this.#retireSession?.(key, held.session.sessionId);
|
|
@@ -16023,16 +15996,12 @@ var Bridge = class {
|
|
|
16023
15996
|
protocolVersion: negotiated.version,
|
|
16024
15997
|
extensionVersion: parsed.data.extensionVersion
|
|
16025
15998
|
};
|
|
16026
|
-
const authRequired = parsed.data.capabilities.includes(
|
|
16027
|
-
BRIDGE_AUTH_CAPABILITY
|
|
16028
|
-
);
|
|
16029
15999
|
const routingRequired = credential.kind === "claimed" && parsed.data.capabilities.includes(BROWSER_ROUTING_CAPABILITY);
|
|
16030
16000
|
const fileRequestBound = parsed.data.capabilities.includes(
|
|
16031
16001
|
LOCAL_FILE_REQUEST_CAPABILITY
|
|
16032
16002
|
);
|
|
16033
16003
|
this.#installSession(key, session, {
|
|
16034
16004
|
tokenHash,
|
|
16035
|
-
authRequired,
|
|
16036
16005
|
routingRequired,
|
|
16037
16006
|
fileRequestBound
|
|
16038
16007
|
});
|
|
@@ -16044,10 +16013,8 @@ var Bridge = class {
|
|
|
16044
16013
|
protocolVersion: negotiated.version,
|
|
16045
16014
|
serverVersion: this.#deps.serverVersion,
|
|
16046
16015
|
sessionId: session.sessionId,
|
|
16047
|
-
// Echoed only when it is actually on, so the panel can tell a server that
|
|
16048
|
-
// is holding it to the header from one that predates the gate entirely.
|
|
16049
16016
|
capabilities: [
|
|
16050
|
-
|
|
16017
|
+
BRIDGE_AUTH_CAPABILITY,
|
|
16051
16018
|
...routingRequired ? [BROWSER_ROUTING_CAPABILITY] : [],
|
|
16052
16019
|
...fileRequestBound ? [LOCAL_FILE_REQUEST_CAPABILITY] : []
|
|
16053
16020
|
]
|
|
@@ -16068,15 +16035,13 @@ var Bridge = class {
|
|
|
16068
16035
|
return this.#mayAct(entry, presented);
|
|
16069
16036
|
}
|
|
16070
16037
|
#mayAct(entry, presented) {
|
|
16071
|
-
if (!entry.authRequired) return true;
|
|
16072
16038
|
if (typeof presented !== "string" || presented.length === 0) return false;
|
|
16073
16039
|
return timingSafeEqual(digestToken(presented), entry.tokenHash);
|
|
16074
16040
|
}
|
|
16075
16041
|
/**
|
|
16076
16042
|
* Route a frame from the panel to whoever is waiting for it.
|
|
16077
16043
|
*
|
|
16078
|
-
* `presented` is the credential the poster carried.
|
|
16079
|
-
* be held to it refuses anything else; one that did not is unaffected.
|
|
16044
|
+
* `presented` is the credential the poster carried.
|
|
16080
16045
|
*/
|
|
16081
16046
|
receive(raw, presented) {
|
|
16082
16047
|
const classified = classifyFrame(
|
|
@@ -16268,7 +16233,7 @@ var Bridge = class {
|
|
|
16268
16233
|
if (browser === void 0) {
|
|
16269
16234
|
entry = this.#primary();
|
|
16270
16235
|
} else {
|
|
16271
|
-
const known = this.#deps.claimedBrowsers.has(browser)
|
|
16236
|
+
const known = this.#deps.claimedBrowsers.has(browser);
|
|
16272
16237
|
if (!known) {
|
|
16273
16238
|
return {
|
|
16274
16239
|
ok: false,
|
|
@@ -16857,14 +16822,22 @@ async function handleMcpRequest(bridge, body, accept, stage) {
|
|
|
16857
16822
|
delete args["browser"];
|
|
16858
16823
|
}
|
|
16859
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
|
+
};
|
|
16860
16832
|
return frameMcpResponse(
|
|
16861
16833
|
rpcResult(id, {
|
|
16862
16834
|
content: [
|
|
16863
16835
|
{
|
|
16864
16836
|
type: "text",
|
|
16865
|
-
text: JSON.stringify(
|
|
16837
|
+
text: JSON.stringify(payload)
|
|
16866
16838
|
}
|
|
16867
|
-
]
|
|
16839
|
+
],
|
|
16840
|
+
...registryState === "disabled" ? { isError: true } : {}
|
|
16868
16841
|
}),
|
|
16869
16842
|
accept
|
|
16870
16843
|
);
|
|
@@ -17240,7 +17213,11 @@ function createHttpBridge(opts) {
|
|
|
17240
17213
|
}
|
|
17241
17214
|
if (req.method === "GET" && url2.pathname === "/bridge/events") {
|
|
17242
17215
|
const claimed = url2.searchParams.get("session");
|
|
17243
|
-
|
|
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);
|
|
17244
17221
|
if (key === null) {
|
|
17245
17222
|
opts.log?.("refused a stream for an unknown session");
|
|
17246
17223
|
res.writeHead(200, { "content-type": "text/event-stream" });
|
|
@@ -17267,13 +17244,8 @@ function createHttpBridge(opts) {
|
|
|
17267
17244
|
// by nature.
|
|
17268
17245
|
"x-accel-buffering": "no"
|
|
17269
17246
|
});
|
|
17270
|
-
const sessionId = claimed ?? opts.bridge.sessionForKey(key)?.sessionId ?? "";
|
|
17271
|
-
if (!sessionId) {
|
|
17272
|
-
res.end();
|
|
17273
|
-
return;
|
|
17274
|
-
}
|
|
17275
17247
|
try {
|
|
17276
|
-
installStream(key,
|
|
17248
|
+
installStream(key, claimed, res);
|
|
17277
17249
|
} catch {
|
|
17278
17250
|
res.end();
|
|
17279
17251
|
return;
|
|
@@ -17314,7 +17286,11 @@ function createHttpBridge(opts) {
|
|
|
17314
17286
|
}
|
|
17315
17287
|
const action = b?.["action"];
|
|
17316
17288
|
if (action === "list") {
|
|
17317
|
-
json2(res, 200, {
|
|
17289
|
+
json2(res, 200, {
|
|
17290
|
+
ok: true,
|
|
17291
|
+
browsers: await registry2.list(),
|
|
17292
|
+
registryStatus: registry2.status()
|
|
17293
|
+
});
|
|
17318
17294
|
return;
|
|
17319
17295
|
}
|
|
17320
17296
|
if (action === "add") {
|
|
@@ -17330,6 +17306,10 @@ function createHttpBridge(opts) {
|
|
|
17330
17306
|
});
|
|
17331
17307
|
return;
|
|
17332
17308
|
}
|
|
17309
|
+
if (action === "repair") {
|
|
17310
|
+
json2(res, 200, { ok: true, ...await registry2.repair() });
|
|
17311
|
+
return;
|
|
17312
|
+
}
|
|
17333
17313
|
json2(res, 400, { error: "unknown action" });
|
|
17334
17314
|
}).catch((err) => {
|
|
17335
17315
|
const why = err instanceof Error ? err.message : String(err);
|
|
@@ -17347,20 +17327,20 @@ function createHttpBridge(opts) {
|
|
|
17347
17327
|
const hasRequestId = b !== null && Object.hasOwn(b, "requestId");
|
|
17348
17328
|
const invalidSessionId = hasSessionId && (typeof b?.sessionId !== "string" || b.sessionId.length === 0);
|
|
17349
17329
|
const invalidRequestId = hasRequestId && (typeof b?.requestId !== "string" || b.requestId.length === 0);
|
|
17350
|
-
if (invalidSessionId || invalidRequestId) {
|
|
17330
|
+
if (!hasSessionId || !hasRequestId || invalidSessionId || invalidRequestId) {
|
|
17351
17331
|
json2(res, 403, {
|
|
17352
17332
|
error: "unauthorized",
|
|
17353
17333
|
message: "\uC0AC\uC9C4 \uC694\uCCAD\uC758 \uC5F0\uACB0 \uC815\uBCF4\uB97C \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC5B4\uC694."
|
|
17354
17334
|
});
|
|
17355
17335
|
return;
|
|
17356
17336
|
}
|
|
17357
|
-
const auth =
|
|
17337
|
+
const auth = opts.bridge.authorizeFileRequest({
|
|
17358
17338
|
sessionId: b.sessionId,
|
|
17359
|
-
|
|
17339
|
+
requestId: b.requestId,
|
|
17360
17340
|
token,
|
|
17361
17341
|
path,
|
|
17362
17342
|
...typeof b?.tool === "string" ? { tool: b.tool } : {}
|
|
17363
|
-
})
|
|
17343
|
+
});
|
|
17364
17344
|
if (!auth.ok) {
|
|
17365
17345
|
opts.log?.(
|
|
17366
17346
|
`file request refused: reason=${auth.reason} credential=${auth.credential} tokenPresent=${String(token.length > 0)} sessionMatch=${String(auth.key !== void 0)} open=not-entered`
|
|
@@ -17383,7 +17363,7 @@ function createHttpBridge(opts) {
|
|
|
17383
17363
|
return;
|
|
17384
17364
|
}
|
|
17385
17365
|
opts.log?.(
|
|
17386
|
-
`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`
|
|
17387
17367
|
);
|
|
17388
17368
|
let source = null;
|
|
17389
17369
|
let owner = null;
|
|
@@ -17842,6 +17822,7 @@ async function writeLedger(path, claims) {
|
|
|
17842
17822
|
var writing = /* @__PURE__ */ new Map();
|
|
17843
17823
|
async function writeOnce(path, claims) {
|
|
17844
17824
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
17825
|
+
await mkdir2(dirname(path), { recursive: true, mode: 448 });
|
|
17845
17826
|
await writeFile2(tmp, `${JSON.stringify(claims, null, 2)}
|
|
17846
17827
|
`, {
|
|
17847
17828
|
mode: 384
|
|
@@ -17873,17 +17854,24 @@ function matches(claim, handle) {
|
|
|
17873
17854
|
function createBrowserRegistry(opts) {
|
|
17874
17855
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
17875
17856
|
const revoked = /* @__PURE__ */ new Set();
|
|
17876
|
-
const
|
|
17857
|
+
const readCurrent = () => {
|
|
17877
17858
|
const read = readLedger(opts.path);
|
|
17878
17859
|
if (read.state === "unreadable") {
|
|
17879
17860
|
throw new Error(`browser registry is unreadable: ${read.reason}`);
|
|
17880
17861
|
}
|
|
17881
17862
|
const rows = read.state === "ok" ? [...read.rows] : [];
|
|
17882
|
-
if (opts.forbiddenTokenHash
|
|
17883
|
-
|
|
17884
|
-
|
|
17885
|
-
|
|
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
|
+
};
|
|
17886
17873
|
};
|
|
17874
|
+
const current = () => readCurrent().rows;
|
|
17887
17875
|
return {
|
|
17888
17876
|
async add(label) {
|
|
17889
17877
|
const token = mintToken();
|
|
@@ -17940,6 +17928,33 @@ function createBrowserRegistry(opts) {
|
|
|
17940
17928
|
}
|
|
17941
17929
|
return true;
|
|
17942
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
|
+
},
|
|
17943
17958
|
wasRevoked(tokenHash) {
|
|
17944
17959
|
return revoked.has(tokenHash);
|
|
17945
17960
|
}
|
|
@@ -17972,12 +17987,12 @@ function runDaemon(env, deps = {}) {
|
|
|
17972
17987
|
const browserLabels = /* @__PURE__ */ new Map();
|
|
17973
17988
|
const ledger = readLedger(ledgerPath(deps.home));
|
|
17974
17989
|
const rootTokenHash = digestToken(token);
|
|
17975
|
-
const
|
|
17976
|
-
if (ledger.state === "ok"
|
|
17977
|
-
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)) {
|
|
17978
17993
|
claimedBrowsers.set(id, hash2);
|
|
17979
17994
|
}
|
|
17980
|
-
for (const row of
|
|
17995
|
+
for (const row of safeLedgerRows) {
|
|
17981
17996
|
if (row.browserId !== null) browserLabels.set(row.browserId, row.label);
|
|
17982
17997
|
}
|
|
17983
17998
|
}
|
|
@@ -17986,8 +18001,10 @@ function runDaemon(env, deps = {}) {
|
|
|
17986
18001
|
`browser registry is unreadable (${ledger.reason}); registering is off`
|
|
17987
18002
|
);
|
|
17988
18003
|
}
|
|
17989
|
-
if (
|
|
17990
|
-
log(
|
|
18004
|
+
if (ledger.state === "ok" && ledgerOverlapsToken(ledger.rows, rootTokenHash)) {
|
|
18005
|
+
log(
|
|
18006
|
+
"browser registry overlaps the root authority; conflicting rows are quarantined"
|
|
18007
|
+
);
|
|
17991
18008
|
}
|
|
17992
18009
|
const bridge = new Bridge({
|
|
17993
18010
|
send: (frame, key) => http.send(frame, key),
|
|
@@ -17995,10 +18012,10 @@ function runDaemon(env, deps = {}) {
|
|
|
17995
18012
|
extensionIds,
|
|
17996
18013
|
serverVersion: version2,
|
|
17997
18014
|
rateLimit: deps.rateLimit,
|
|
17998
|
-
// Empty until a browser is registered
|
|
17999
|
-
// yet — so every panel still shares the one slot it always had.
|
|
18015
|
+
// Empty until a browser is registered.
|
|
18000
18016
|
claimedBrowsers,
|
|
18001
18017
|
browserLabels,
|
|
18018
|
+
browserRegistryStatus: () => registry2.status(),
|
|
18002
18019
|
claimBrowser: (browserId, tokenHash) => {
|
|
18003
18020
|
const took = registry2.claim(browserId, tokenHash);
|
|
18004
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(),
|
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.js
CHANGED