@modootoday/datalab-extension-mcp 1.8.11 → 1.8.13

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.
@@ -1,15 +1,5 @@
1
- // ../mcp-server/dist/chunk-377F4BGN.js
1
+ // ../mcp-server/dist/chunk-HS7SL22A.js
2
2
  import { createHash } from "crypto";
3
- import { createHash as createHash2, randomUUID } from "crypto";
4
- import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
5
- import { join } from "path";
6
- import {
7
- createServer
8
- } from "http";
9
- import { pipeline } from "stream/promises";
10
- import { constants } from "fs";
11
- import { lstat, open } from "fs/promises";
12
- import { basename, isAbsolute } from "path";
13
3
  import {
14
4
  appendFileSync,
15
5
  mkdirSync,
@@ -18,14 +8,24 @@ import {
18
8
  statSync
19
9
  } from "fs";
20
10
  import { homedir } from "os";
21
- import { join as join2 } from "path";
11
+ import { join } from "path";
12
+ import { randomBytes } from "crypto";
13
+ import { readFileSync } from "fs";
14
+ import { mkdir, rename, writeFile } from "fs/promises";
15
+ import { dirname, join as join2 } from "path";
16
+ import { createHash as createHash2, randomUUID } from "crypto";
17
+ import { mkdir as mkdir2, readFile, readdir, rm, writeFile as writeFile2 } from "fs/promises";
18
+ import { join as join3 } from "path";
19
+ import {
20
+ createServer
21
+ } from "http";
22
+ import { pipeline } from "stream/promises";
23
+ import { constants } from "fs";
24
+ import { lstat, open } from "fs/promises";
25
+ import { basename, isAbsolute } from "path";
22
26
  import { createConnection } from "net";
23
27
  import { spawn } from "child_process";
24
28
  import { closeSync } from "fs";
25
- import { randomBytes } from "crypto";
26
- import { readFileSync } from "fs";
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;
@@ -16334,62 +16334,419 @@ var BridgeError = class extends Error {
16334
16334
  if (timing) this.timing = timing;
16335
16335
  }
16336
16336
  };
16337
- var MAX_STAGE_BYTES = 10 * 1024 * 1024;
16338
- var DEFAULT_TOKEN_TTL_MS = 30 * 60 * 1e3;
16339
- var STAGE_CAPABILITY = "datalab-mcp-stage-v1";
16340
- function refuse(reason, retryable, message) {
16341
- return { state: "REFUSED", reason, retryable, message };
16337
+ var LOG_MAX_BYTES = 1024 * 1024;
16338
+ var DIR_NAME = ".datalab-mcp";
16339
+ var FILE_NAME = "connector.log";
16340
+ function logDir(home = homedir()) {
16341
+ return join(home, DIR_NAME);
16342
16342
  }
16343
- function segment(...parts) {
16344
- return createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
16343
+ function logPath(home = homedir()) {
16344
+ return join(logDir(home), FILE_NAME);
16345
16345
  }
16346
- function createMediaStage(opts) {
16347
- const now = opts.now ?? Date.now;
16348
- const ttl = opts.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS;
16349
- const ledgerDir = join(opts.root, "ledger");
16350
- const blobDir = join(opts.root, "blob");
16351
- const tokens = /* @__PURE__ */ new Map();
16352
- const active = /* @__PURE__ */ new Map();
16353
- const keyOf = (j) => segment(j.blogId, j.runId, j.actionId, j.jobId);
16354
- const ledgerPath2 = (key) => join(ledgerDir, `${key}.json`);
16355
- const blobPath = (key) => join(blobDir, key);
16356
- async function readRow(key) {
16357
- try {
16358
- const raw = await readFile(ledgerPath2(key), "utf8");
16359
- return JSON.parse(raw);
16360
- } catch (err) {
16361
- if (err.code === "ENOENT") return null;
16362
- return "unreadable";
16346
+ function rolledLogPath(home = homedir()) {
16347
+ return `${logPath(home)}.1`;
16348
+ }
16349
+ function rollIfLarge(home = homedir(), maxBytes = LOG_MAX_BYTES) {
16350
+ try {
16351
+ const size = statSync(logPath(home)).size;
16352
+ if (size < maxBytes) return false;
16353
+ renameSync(logPath(home), rolledLogPath(home));
16354
+ return true;
16355
+ } catch {
16356
+ return false;
16357
+ }
16358
+ }
16359
+ function openLogFd(home = homedir()) {
16360
+ try {
16361
+ mkdirSync(logDir(home), { recursive: true });
16362
+ rollIfLarge(home);
16363
+ return openSync(logPath(home), "a");
16364
+ } catch {
16365
+ return null;
16366
+ }
16367
+ }
16368
+ var FILE_NAME2 = "browsers.json";
16369
+ function ledgerPath(home) {
16370
+ return join2(logDir(home), FILE_NAME2);
16371
+ }
16372
+ function isRegistration(row) {
16373
+ if (typeof row !== "object" || row === null) return false;
16374
+ const r = row;
16375
+ const id = r["browserId"];
16376
+ const hash2 = r["tokenHash"];
16377
+ return (id === null || typeof id === "string" && id.length > 0) && typeof hash2 === "string" && /^[a-f0-9]{64}$/.test(hash2) && typeof r["label"] === "string" && typeof r["createdAt"] === "string";
16378
+ }
16379
+ function readLedger(path) {
16380
+ let raw;
16381
+ try {
16382
+ raw = readFileSync(path, "utf8");
16383
+ } catch (err) {
16384
+ if (err.code === "ENOENT") {
16385
+ return { state: "absent" };
16363
16386
  }
16387
+ return { state: "unreadable", reason: describe3(err) };
16364
16388
  }
16365
- async function writeRow(key, row) {
16366
- await mkdir(ledgerDir, { recursive: true });
16367
- await writeFile(ledgerPath2(key), JSON.stringify(row), "utf8");
16389
+ let parsed;
16390
+ try {
16391
+ parsed = JSON.parse(raw);
16392
+ } catch (err) {
16393
+ return { state: "unreadable", reason: describe3(err) };
16394
+ }
16395
+ if (!Array.isArray(parsed)) {
16396
+ return { state: "unreadable", reason: "not a list of browsers" };
16397
+ }
16398
+ if (!parsed.every(isRegistration)) {
16399
+ return { state: "unreadable", reason: "a row is not a browser" };
16400
+ }
16401
+ const browserIds = /* @__PURE__ */ new Set();
16402
+ const tokenHashes = /* @__PURE__ */ new Set();
16403
+ for (const row of parsed) {
16404
+ if (tokenHashes.has(row.tokenHash) || row.browserId !== null && browserIds.has(row.browserId)) {
16405
+ return { state: "unreadable", reason: "duplicate browser authority" };
16406
+ }
16407
+ tokenHashes.add(row.tokenHash);
16408
+ if (row.browserId !== null) browserIds.add(row.browserId);
16409
+ }
16410
+ return { state: "ok", rows: parsed };
16411
+ }
16412
+ function ledgerOverlapsToken(rows, tokenHash) {
16413
+ return rows.some((row) => row.tokenHash === tokenHash);
16414
+ }
16415
+ async function writeLedger(path, claims) {
16416
+ const queued = (writing.get(path) ?? Promise.resolve()).then(
16417
+ () => writeOnce(path, claims),
16418
+ () => writeOnce(path, claims)
16419
+ );
16420
+ writing.set(path, queued);
16421
+ try {
16422
+ await queued;
16423
+ } finally {
16424
+ if (writing.get(path) === queued) writing.delete(path);
16425
+ }
16426
+ }
16427
+ var writing = /* @__PURE__ */ new Map();
16428
+ async function writeOnce(path, claims) {
16429
+ const tmp = `${path}.${process.pid}.tmp`;
16430
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
16431
+ await writeFile(tmp, `${JSON.stringify(claims, null, 2)}
16432
+ `, {
16433
+ mode: 384
16434
+ });
16435
+ await rename(tmp, path);
16436
+ }
16437
+ function claimedDigestsFrom(claims) {
16438
+ const pairs = [];
16439
+ for (const claim of claims) {
16440
+ if (claim.browserId !== null && claim.browserId.length > 0) {
16441
+ pairs.push([claim.browserId, claim.tokenHash]);
16442
+ }
16443
+ }
16444
+ return pairs;
16445
+ }
16446
+ function describe3(err) {
16447
+ return err instanceof Error ? err.message : String(err);
16448
+ }
16449
+ var HANDLE_LENGTH = 8;
16450
+ var BrowserRegistryError = class extends Error {
16451
+ code;
16452
+ constructor(code) {
16453
+ super(code);
16454
+ this.name = "BrowserRegistryError";
16455
+ this.code = code;
16368
16456
  }
16457
+ };
16458
+ function mintToken() {
16459
+ return randomBytes(32).toString("hex");
16460
+ }
16461
+ function handleOf(claim) {
16462
+ return claim.tokenHash.slice(0, HANDLE_LENGTH);
16463
+ }
16464
+ function matches(claim, handle) {
16465
+ return handleOf(claim) === handle || claim.browserId === handle;
16466
+ }
16467
+ function createBrowserRegistry(opts) {
16468
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
16469
+ const revoked = /* @__PURE__ */ new Set();
16470
+ const initial = readLedger(opts.path);
16471
+ const unreadable = initial.state === "unreadable" ? initial.reason : null;
16472
+ let rows = initial.state === "ok" ? initial.rows.map((row) => ({ ...row })) : [];
16473
+ let mutationTail = Promise.resolve();
16474
+ let pendingMutations = 0;
16475
+ const view = (source = rows) => {
16476
+ if (unreadable !== null) {
16477
+ throw new Error(`browser registry is unreadable: ${unreadable}`);
16478
+ }
16479
+ if (!opts.forbiddenTokenHash) return { rows: [...source], quarantined: [] };
16480
+ const quarantined = source.filter(
16481
+ (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16482
+ );
16483
+ return {
16484
+ rows: source.filter(
16485
+ (row) => !timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16486
+ ),
16487
+ quarantined
16488
+ };
16489
+ };
16490
+ const current = () => view().rows;
16491
+ const serialize = (work) => {
16492
+ pendingMutations += 1;
16493
+ const result = mutationTail.then(work, work);
16494
+ mutationTail = result.then(
16495
+ () => {
16496
+ pendingMutations -= 1;
16497
+ },
16498
+ () => {
16499
+ pendingMutations -= 1;
16500
+ }
16501
+ );
16502
+ return result;
16503
+ };
16504
+ const transact = (propose) => {
16505
+ if (unreadable !== null) {
16506
+ return Promise.reject(
16507
+ new Error(`browser registry is unreadable: ${unreadable}`)
16508
+ );
16509
+ }
16510
+ return serialize(async () => {
16511
+ const proposal = propose(rows.map((row) => ({ ...row })));
16512
+ await writeLedger(opts.path, proposal.next);
16513
+ rows = proposal.next;
16514
+ proposal.commit?.();
16515
+ return proposal.result;
16516
+ });
16517
+ };
16369
16518
  return {
16370
- async begin(req) {
16371
- const key = keyOf(req);
16372
- const row = await readRow(key);
16373
- if (row === "unreadable") {
16374
- return refuse("uploads_unproven", true, "\uC791\uC5C5 \uC0C1\uD0DC\uB97C \uC77D\uC9C0 \uBABB\uD588\uC5B4\uC694.");
16519
+ async add(label) {
16520
+ const token = mintToken();
16521
+ const claim = {
16522
+ browserId: null,
16523
+ tokenHash: digestToken(token),
16524
+ label,
16525
+ createdAt: now().toISOString()
16526
+ };
16527
+ return transact((snapshot) => ({
16528
+ next: [...snapshot, claim],
16529
+ result: { handle: handleOf(claim), token }
16530
+ }));
16531
+ },
16532
+ async list() {
16533
+ return current().map((claim) => ({
16534
+ handle: handleOf(claim),
16535
+ label: claim.label,
16536
+ browserId: claim.browserId,
16537
+ createdAt: claim.createdAt
16538
+ }));
16539
+ },
16540
+ claim(browserId, tokenHash) {
16541
+ if (pendingMutations > 0) return false;
16542
+ if (opts.claimed.has(browserId)) return false;
16543
+ for (const held of opts.claimed.values()) {
16544
+ if (timingSafeEqual(held, tokenHash)) return false;
16375
16545
  }
16376
- if (row) {
16377
- if (req.fence < row.fence) {
16378
- return refuse(
16379
- "stale_fence",
16380
- false,
16381
- "\uB354 \uC0C8\uB85C\uC6B4 \uC2DC\uB3C4\uAC00 \uC774\uBBF8 \uC9C4\uD589\uB410\uC5B4\uC694."
16382
- );
16383
- }
16384
- if (row.state === "SEALED") {
16385
- return refuse("already_sealed", false, "\uC774\uBBF8 \uBD09\uC778\uB41C \uC790\uC0B0\uC774\uC5D0\uC694.");
16386
- }
16387
- if (row.state === "OPEN") {
16388
- for (const [token2, held] of tokens) {
16389
- if (held.key === key && held.expiresAt > now()) {
16390
- return {
16391
- state: "OPEN",
16392
- jobId: req.jobId,
16546
+ let claims;
16547
+ try {
16548
+ claims = current();
16549
+ } catch {
16550
+ return false;
16551
+ }
16552
+ const row = claims.find(
16553
+ (claim) => claim.browserId === null && timingSafeEqual(claim.tokenHash, tokenHash)
16554
+ );
16555
+ if (!row) return false;
16556
+ const stored = rows.find((claim) => claim.tokenHash === row.tokenHash);
16557
+ if (!stored) return false;
16558
+ stored.browserId = browserId;
16559
+ opts.claimed.set(browserId, row.tokenHash);
16560
+ opts.onClaim?.(browserId, stored.label);
16561
+ const snapshot = rows.map((entry) => ({ ...entry }));
16562
+ void serialize(async () => {
16563
+ try {
16564
+ await writeLedger(opts.path, snapshot);
16565
+ } catch (error51) {
16566
+ if (stored.browserId === browserId) stored.browserId = null;
16567
+ if (opts.claimed.get(browserId) === row.tokenHash) {
16568
+ opts.claimed.delete(browserId);
16569
+ opts.onRevoke?.(browserId);
16570
+ }
16571
+ throw error51;
16572
+ }
16573
+ }).catch(() => void 0);
16574
+ return true;
16575
+ },
16576
+ async revoke(handle) {
16577
+ return transact((snapshot) => {
16578
+ const claims = view(snapshot).rows;
16579
+ const removed = claims.filter((claim) => matches(claim, handle));
16580
+ if (removed.length === 0) {
16581
+ return { next: snapshot, result: false };
16582
+ }
16583
+ const removedHashes = new Set(removed.map((claim) => claim.tokenHash));
16584
+ return {
16585
+ next: snapshot.filter((claim) => !removedHashes.has(claim.tokenHash)),
16586
+ result: true,
16587
+ commit: () => {
16588
+ for (const claim of removed) {
16589
+ revoked.add(claim.tokenHash);
16590
+ if (claim.browserId !== null) {
16591
+ opts.claimed.delete(claim.browserId);
16592
+ opts.onRevoke?.(claim.browserId);
16593
+ }
16594
+ }
16595
+ }
16596
+ };
16597
+ });
16598
+ },
16599
+ async repair() {
16600
+ return transact((snapshot) => {
16601
+ const currentView = view(snapshot);
16602
+ const removed = currentView.quarantined.map(handleOf);
16603
+ const removedHashes = new Set(
16604
+ currentView.quarantined.map((row) => row.tokenHash)
16605
+ );
16606
+ return {
16607
+ next: snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
16608
+ result: { removed }
16609
+ };
16610
+ });
16611
+ },
16612
+ async repairRegistration(browserId, requestedLabel) {
16613
+ if (browserId.length === 0) {
16614
+ throw new BrowserRegistryError("registration_not_found");
16615
+ }
16616
+ return transact((snapshot) => {
16617
+ const currentView = view(snapshot);
16618
+ if (currentView.rows.some((row) => row.browserId === browserId)) {
16619
+ throw new BrowserRegistryError("registration_already_valid");
16620
+ }
16621
+ const quarantined = currentView.quarantined.filter(
16622
+ (row) => row.browserId === browserId
16623
+ );
16624
+ if (quarantined.length === 0) {
16625
+ throw new BrowserRegistryError(
16626
+ currentView.quarantined.length > 0 ? "registration_not_quarantined" : "registration_not_found"
16627
+ );
16628
+ }
16629
+ const token = mintToken();
16630
+ const tokenHash = digestToken(token);
16631
+ const label = requestedLabel?.trim() || quarantined[0]?.label || "\uC774 \uBE0C\uB77C\uC6B0\uC800";
16632
+ const removedHashes = new Set(quarantined.map((row) => row.tokenHash));
16633
+ const next = {
16634
+ browserId: null,
16635
+ tokenHash,
16636
+ label,
16637
+ createdAt: now().toISOString()
16638
+ };
16639
+ return {
16640
+ next: [
16641
+ ...snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
16642
+ next
16643
+ ],
16644
+ result: {
16645
+ outcome: "repaired",
16646
+ removed: quarantined.map(handleOf),
16647
+ registration: { handle: handleOf(next), label, token }
16648
+ },
16649
+ commit: () => {
16650
+ for (const row of quarantined) {
16651
+ revoked.add(row.tokenHash);
16652
+ if (row.browserId !== null && opts.claimed.get(row.browserId) === row.tokenHash) {
16653
+ opts.claimed.delete(row.browserId);
16654
+ opts.onRevoke?.(row.browserId);
16655
+ }
16656
+ }
16657
+ }
16658
+ };
16659
+ });
16660
+ },
16661
+ status() {
16662
+ if (unreadable !== null) {
16663
+ return {
16664
+ state: "disabled",
16665
+ error: "browser_registry_unreadable",
16666
+ detail: unreadable
16667
+ };
16668
+ }
16669
+ if (opts.forbiddenTokenHash && ledgerOverlapsToken(rows, opts.forbiddenTokenHash)) {
16670
+ return {
16671
+ state: "degraded",
16672
+ error: "browser_registry_root_authority_overlap",
16673
+ quarantinedHandles: rows.filter(
16674
+ (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16675
+ ).map(handleOf),
16676
+ quarantined: rows.filter(
16677
+ (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
16678
+ ).map((row) => ({
16679
+ handle: handleOf(row),
16680
+ label: row.label,
16681
+ browserId: row.browserId,
16682
+ createdAt: row.createdAt
16683
+ })),
16684
+ recovery: "repair_registration"
16685
+ };
16686
+ }
16687
+ return { state: "ready" };
16688
+ },
16689
+ wasRevoked(tokenHash) {
16690
+ return revoked.has(tokenHash);
16691
+ }
16692
+ };
16693
+ }
16694
+ var MAX_STAGE_BYTES = 10 * 1024 * 1024;
16695
+ var DEFAULT_TOKEN_TTL_MS = 30 * 60 * 1e3;
16696
+ var STAGE_CAPABILITY = "datalab-mcp-stage-v1";
16697
+ function refuse(reason, retryable, message) {
16698
+ return { state: "REFUSED", reason, retryable, message };
16699
+ }
16700
+ function segment(...parts) {
16701
+ return createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
16702
+ }
16703
+ function createMediaStage(opts) {
16704
+ const now = opts.now ?? Date.now;
16705
+ const ttl = opts.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS;
16706
+ const ledgerDir = join3(opts.root, "ledger");
16707
+ const blobDir = join3(opts.root, "blob");
16708
+ const tokens = /* @__PURE__ */ new Map();
16709
+ const active = /* @__PURE__ */ new Map();
16710
+ const keyOf = (j) => segment(j.blogId, j.runId, j.actionId, j.jobId);
16711
+ const ledgerPath2 = (key) => join3(ledgerDir, `${key}.json`);
16712
+ const blobPath = (key) => join3(blobDir, key);
16713
+ async function readRow(key) {
16714
+ try {
16715
+ const raw = await readFile(ledgerPath2(key), "utf8");
16716
+ return JSON.parse(raw);
16717
+ } catch (err) {
16718
+ if (err.code === "ENOENT") return null;
16719
+ return "unreadable";
16720
+ }
16721
+ }
16722
+ async function writeRow(key, row) {
16723
+ await mkdir2(ledgerDir, { recursive: true });
16724
+ await writeFile2(ledgerPath2(key), JSON.stringify(row), "utf8");
16725
+ }
16726
+ return {
16727
+ async begin(req) {
16728
+ const key = keyOf(req);
16729
+ const row = await readRow(key);
16730
+ if (row === "unreadable") {
16731
+ return refuse("uploads_unproven", true, "\uC791\uC5C5 \uC0C1\uD0DC\uB97C \uC77D\uC9C0 \uBABB\uD588\uC5B4\uC694.");
16732
+ }
16733
+ if (row) {
16734
+ if (req.fence < row.fence) {
16735
+ return refuse(
16736
+ "stale_fence",
16737
+ false,
16738
+ "\uB354 \uC0C8\uB85C\uC6B4 \uC2DC\uB3C4\uAC00 \uC774\uBBF8 \uC9C4\uD589\uB410\uC5B4\uC694."
16739
+ );
16740
+ }
16741
+ if (row.state === "SEALED") {
16742
+ return refuse("already_sealed", false, "\uC774\uBBF8 \uBD09\uC778\uB41C \uC790\uC0B0\uC774\uC5D0\uC694.");
16743
+ }
16744
+ if (row.state === "OPEN") {
16745
+ for (const [token2, held] of tokens) {
16746
+ if (held.key === key && held.expiresAt > now()) {
16747
+ return {
16748
+ state: "OPEN",
16749
+ jobId: req.jobId,
16393
16750
  uploadToken: token2,
16394
16751
  expiresAt: held.expiresAt,
16395
16752
  capabilityVersion: STAGE_CAPABILITY
@@ -16424,7 +16781,7 @@ function createMediaStage(opts) {
16424
16781
  }
16425
16782
  active.set(held.key, (active.get(held.key) ?? 0) + 1);
16426
16783
  try {
16427
- await mkdir(blobDir, { recursive: true });
16784
+ await mkdir2(blobDir, { recursive: true });
16428
16785
  const chunks = [];
16429
16786
  let total = 0;
16430
16787
  for await (const chunk of body) {
@@ -16435,7 +16792,7 @@ function createMediaStage(opts) {
16435
16792
  }
16436
16793
  chunks.push(buf);
16437
16794
  }
16438
- await writeFile(blobPath(held.key), Buffer.concat(chunks));
16795
+ await writeFile2(blobPath(held.key), Buffer.concat(chunks));
16439
16796
  return { ok: true, bytes: total };
16440
16797
  } finally {
16441
16798
  const left = (active.get(held.key) ?? 1) - 1;
@@ -16568,7 +16925,7 @@ function createMediaStage(opts) {
16568
16925
  const out = [];
16569
16926
  for (const dir of [ledgerDir, blobDir]) {
16570
16927
  try {
16571
- for (const name of await readdir(dir)) out.push(join(dir, name));
16928
+ for (const name of await readdir(dir)) out.push(join3(dir, name));
16572
16929
  } catch {
16573
16930
  }
16574
16931
  }
@@ -16838,7 +17195,15 @@ async function handleMcpRequest(bridge, body, accept, stage) {
16838
17195
  }
16839
17196
  if (params.name === BROWSERS_TOOL) {
16840
17197
  const registryStatus = bridge.browserRegistryStatus();
16841
- const { state: registryState, ...registryDetail } = registryStatus;
17198
+ const { state: registryState } = registryStatus;
17199
+ const registryDetail = registryStatus.state === "degraded" ? {
17200
+ error: registryStatus.error,
17201
+ quarantinedHandles: registryStatus.quarantinedHandles,
17202
+ recovery: registryStatus.recovery
17203
+ } : registryStatus.state === "disabled" ? {
17204
+ error: registryStatus.error,
17205
+ detail: registryStatus.detail
17206
+ } : {};
16842
17207
  const payload = {
16843
17208
  browsers: bridge.connectedBrowsers(),
16844
17209
  registryStatus: registryState,
@@ -17018,6 +17383,11 @@ async function openLocalImage(path) {
17018
17383
  };
17019
17384
  }
17020
17385
  }
17386
+ var MCP_SERVER_HEALTH_NAME = "datalab-extension-mcp-server";
17387
+ var MCP_SERVER_HEALTH_SCHEMA = "datalab-extension-mcp-health-v1";
17388
+ var MCP_MANAGEMENT_CAPABILITIES = Object.freeze([
17389
+ "browser-registration-repair-v1"
17390
+ ]);
17021
17391
  var MAX_BODY_BYTES = 64 * 1024;
17022
17392
  var DEFAULT_TOOLS_CHANGED_DEBOUNCE_MS = 600;
17023
17393
  var MAX_MCP_BODY_BYTES2 = 256 * 1024;
@@ -17188,8 +17558,9 @@ function createHttpBridge(opts) {
17188
17558
  }
17189
17559
  if (req.method === "GET" && url2.pathname === "/bridge/health") {
17190
17560
  json2(res, 200, {
17191
- name: opts.identity.name,
17192
- version: opts.identity.version
17561
+ name: MCP_SERVER_HEALTH_NAME,
17562
+ version: opts.identity.version,
17563
+ schema: MCP_SERVER_HEALTH_SCHEMA
17193
17564
  });
17194
17565
  return;
17195
17566
  }
@@ -17284,6 +17655,7 @@ function createHttpBridge(opts) {
17284
17655
  json2(res, 404, { error: "not found" });
17285
17656
  return;
17286
17657
  }
17658
+ res.setHeader("cache-control", "no-store");
17287
17659
  void readJson(req, MAX_BODY_BYTES).then(async (body) => {
17288
17660
  const b = body;
17289
17661
  if (!opts.bridge.verifyRootToken(
@@ -17304,7 +17676,8 @@ function createHttpBridge(opts) {
17304
17676
  json2(res, 200, {
17305
17677
  ok: true,
17306
17678
  browsers,
17307
- registryStatus: registry2.status()
17679
+ registryStatus: registry2.status(),
17680
+ capabilities: [...MCP_MANAGEMENT_CAPABILITIES]
17308
17681
  });
17309
17682
  return;
17310
17683
  }
@@ -17325,10 +17698,33 @@ function createHttpBridge(opts) {
17325
17698
  json2(res, 200, { ok: true, ...await registry2.repair() });
17326
17699
  return;
17327
17700
  }
17701
+ if (action === "repair_registration") {
17702
+ const browserId = typeof b?.["browserId"] === "string" ? b["browserId"] : "";
17703
+ const label = typeof b?.["label"] === "string" ? b["label"] : void 0;
17704
+ const repaired = await registry2.repairRegistration(
17705
+ browserId,
17706
+ label
17707
+ );
17708
+ json2(res, 200, {
17709
+ ok: true,
17710
+ ...repaired,
17711
+ registryStatus: registry2.status(),
17712
+ capabilities: [...MCP_MANAGEMENT_CAPABILITIES]
17713
+ });
17714
+ return;
17715
+ }
17328
17716
  json2(res, 400, { error: "unknown action" });
17329
17717
  }).catch((err) => {
17330
17718
  const why = err instanceof Error ? err.message : String(err);
17331
17719
  opts.log?.(`browsers route failed: ${why}`);
17720
+ if (err instanceof BrowserRegistryError) {
17721
+ json2(res, err.code === "registration_not_found" ? 404 : 409, {
17722
+ error: err.code,
17723
+ detail: err.code,
17724
+ capabilities: [...MCP_MANAGEMENT_CAPABILITIES]
17725
+ });
17726
+ return;
17727
+ }
17332
17728
  json2(res, 400, { error: "bad request", detail: why });
17333
17729
  });
17334
17730
  return;
@@ -17630,37 +18026,6 @@ function parsePort(raw) {
17630
18026
  if (!Number.isInteger(n) || n < 1 || n > 65535) return null;
17631
18027
  return n;
17632
18028
  }
17633
- var LOG_MAX_BYTES = 1024 * 1024;
17634
- var DIR_NAME = ".datalab-mcp";
17635
- var FILE_NAME = "connector.log";
17636
- function logDir(home = homedir()) {
17637
- return join2(home, DIR_NAME);
17638
- }
17639
- function logPath(home = homedir()) {
17640
- return join2(logDir(home), FILE_NAME);
17641
- }
17642
- function rolledLogPath(home = homedir()) {
17643
- return `${logPath(home)}.1`;
17644
- }
17645
- function rollIfLarge(home = homedir(), maxBytes = LOG_MAX_BYTES) {
17646
- try {
17647
- const size = statSync(logPath(home)).size;
17648
- if (size < maxBytes) return false;
17649
- renameSync(logPath(home), rolledLogPath(home));
17650
- return true;
17651
- } catch {
17652
- return false;
17653
- }
17654
- }
17655
- function openLogFd(home = homedir()) {
17656
- try {
17657
- mkdirSync(logDir(home), { recursive: true });
17658
- rollIfLarge(home);
17659
- return openSync(logPath(home), "a");
17660
- } catch {
17661
- return null;
17662
- }
17663
- }
17664
18029
  function tryConnect(host, port, timeoutMs, deps = {}) {
17665
18030
  const connect = deps.createConnection ?? createConnection;
17666
18031
  return new Promise((resolve) => {
@@ -17775,268 +18140,7 @@ var Lifecycle = class {
17775
18140
  }
17776
18141
  }
17777
18142
  };
17778
- var FILE_NAME2 = "browsers.json";
17779
- function ledgerPath(home) {
17780
- return join3(logDir(home), FILE_NAME2);
17781
- }
17782
- function isRegistration(row) {
17783
- if (typeof row !== "object" || row === null) return false;
17784
- const r = row;
17785
- const id = r["browserId"];
17786
- const hash2 = r["tokenHash"];
17787
- return (id === null || typeof id === "string" && id.length > 0) && typeof hash2 === "string" && /^[a-f0-9]{64}$/.test(hash2) && typeof r["label"] === "string" && typeof r["createdAt"] === "string";
17788
- }
17789
- function readLedger(path) {
17790
- let raw;
17791
- try {
17792
- raw = readFileSync(path, "utf8");
17793
- } catch (err) {
17794
- if (err.code === "ENOENT") {
17795
- return { state: "absent" };
17796
- }
17797
- return { state: "unreadable", reason: describe3(err) };
17798
- }
17799
- let parsed;
17800
- try {
17801
- parsed = JSON.parse(raw);
17802
- } catch (err) {
17803
- return { state: "unreadable", reason: describe3(err) };
17804
- }
17805
- if (!Array.isArray(parsed)) {
17806
- return { state: "unreadable", reason: "not a list of browsers" };
17807
- }
17808
- if (!parsed.every(isRegistration)) {
17809
- return { state: "unreadable", reason: "a row is not a browser" };
17810
- }
17811
- const browserIds = /* @__PURE__ */ new Set();
17812
- const tokenHashes = /* @__PURE__ */ new Set();
17813
- for (const row of parsed) {
17814
- if (tokenHashes.has(row.tokenHash) || row.browserId !== null && browserIds.has(row.browserId)) {
17815
- return { state: "unreadable", reason: "duplicate browser authority" };
17816
- }
17817
- tokenHashes.add(row.tokenHash);
17818
- if (row.browserId !== null) browserIds.add(row.browserId);
17819
- }
17820
- return { state: "ok", rows: parsed };
17821
- }
17822
- function ledgerOverlapsToken(rows, tokenHash) {
17823
- return rows.some((row) => row.tokenHash === tokenHash);
17824
- }
17825
- async function writeLedger(path, claims) {
17826
- const queued = (writing.get(path) ?? Promise.resolve()).then(
17827
- () => writeOnce(path, claims),
17828
- () => writeOnce(path, claims)
17829
- );
17830
- writing.set(path, queued);
17831
- try {
17832
- await queued;
17833
- } finally {
17834
- if (writing.get(path) === queued) writing.delete(path);
17835
- }
17836
- }
17837
- var writing = /* @__PURE__ */ new Map();
17838
- async function writeOnce(path, claims) {
17839
- const tmp = `${path}.${process.pid}.tmp`;
17840
- await mkdir2(dirname(path), { recursive: true, mode: 448 });
17841
- await writeFile2(tmp, `${JSON.stringify(claims, null, 2)}
17842
- `, {
17843
- mode: 384
17844
- });
17845
- await rename(tmp, path);
17846
- }
17847
- function claimedDigestsFrom(claims) {
17848
- const pairs = [];
17849
- for (const claim of claims) {
17850
- if (claim.browserId !== null && claim.browserId.length > 0) {
17851
- pairs.push([claim.browserId, claim.tokenHash]);
17852
- }
17853
- }
17854
- return pairs;
17855
- }
17856
- function describe3(err) {
17857
- return err instanceof Error ? err.message : String(err);
17858
- }
17859
- var HANDLE_LENGTH = 8;
17860
- function mintToken() {
17861
- return randomBytes(32).toString("hex");
17862
- }
17863
- function handleOf(claim) {
17864
- return claim.tokenHash.slice(0, HANDLE_LENGTH);
17865
- }
17866
- function matches(claim, handle) {
17867
- return handleOf(claim) === handle || claim.browserId === handle;
17868
- }
17869
- function createBrowserRegistry(opts) {
17870
- const now = opts.now ?? (() => /* @__PURE__ */ new Date());
17871
- const revoked = /* @__PURE__ */ new Set();
17872
- const initial = readLedger(opts.path);
17873
- const unreadable = initial.state === "unreadable" ? initial.reason : null;
17874
- let rows = initial.state === "ok" ? initial.rows.map((row) => ({ ...row })) : [];
17875
- let mutationTail = Promise.resolve();
17876
- let pendingMutations = 0;
17877
- const view = (source = rows) => {
17878
- if (unreadable !== null) {
17879
- throw new Error(`browser registry is unreadable: ${unreadable}`);
17880
- }
17881
- if (!opts.forbiddenTokenHash) return { rows: [...source], quarantined: [] };
17882
- const quarantined = source.filter(
17883
- (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
17884
- );
17885
- return {
17886
- rows: source.filter(
17887
- (row) => !timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
17888
- ),
17889
- quarantined
17890
- };
17891
- };
17892
- const current = () => view().rows;
17893
- const serialize = (work) => {
17894
- pendingMutations += 1;
17895
- const result = mutationTail.then(work, work);
17896
- mutationTail = result.then(
17897
- () => {
17898
- pendingMutations -= 1;
17899
- },
17900
- () => {
17901
- pendingMutations -= 1;
17902
- }
17903
- );
17904
- return result;
17905
- };
17906
- const transact = (propose) => {
17907
- if (unreadable !== null) {
17908
- return Promise.reject(
17909
- new Error(`browser registry is unreadable: ${unreadable}`)
17910
- );
17911
- }
17912
- return serialize(async () => {
17913
- const proposal = propose(rows.map((row) => ({ ...row })));
17914
- await writeLedger(opts.path, proposal.next);
17915
- rows = proposal.next;
17916
- proposal.commit?.();
17917
- return proposal.result;
17918
- });
17919
- };
17920
- return {
17921
- async add(label) {
17922
- const token = mintToken();
17923
- const claim = {
17924
- browserId: null,
17925
- tokenHash: digestToken(token),
17926
- label,
17927
- createdAt: now().toISOString()
17928
- };
17929
- return transact((snapshot) => ({
17930
- next: [...snapshot, claim],
17931
- result: { handle: handleOf(claim), token }
17932
- }));
17933
- },
17934
- async list() {
17935
- return current().map((claim) => ({
17936
- handle: handleOf(claim),
17937
- label: claim.label,
17938
- browserId: claim.browserId,
17939
- createdAt: claim.createdAt
17940
- }));
17941
- },
17942
- claim(browserId, tokenHash) {
17943
- if (pendingMutations > 0) return false;
17944
- if (opts.claimed.has(browserId)) return false;
17945
- for (const held of opts.claimed.values()) {
17946
- if (timingSafeEqual(held, tokenHash)) return false;
17947
- }
17948
- let claims;
17949
- try {
17950
- claims = current();
17951
- } catch {
17952
- return false;
17953
- }
17954
- const row = claims.find(
17955
- (claim) => claim.browserId === null && timingSafeEqual(claim.tokenHash, tokenHash)
17956
- );
17957
- if (!row) return false;
17958
- const stored = rows.find((claim) => claim.tokenHash === row.tokenHash);
17959
- if (!stored) return false;
17960
- stored.browserId = browserId;
17961
- opts.claimed.set(browserId, row.tokenHash);
17962
- opts.onClaim?.(browserId, stored.label);
17963
- const snapshot = rows.map((entry) => ({ ...entry }));
17964
- void serialize(async () => {
17965
- try {
17966
- await writeLedger(opts.path, snapshot);
17967
- } catch (error51) {
17968
- if (stored.browserId === browserId) stored.browserId = null;
17969
- if (opts.claimed.get(browserId) === row.tokenHash) {
17970
- opts.claimed.delete(browserId);
17971
- opts.onRevoke?.(browserId);
17972
- }
17973
- throw error51;
17974
- }
17975
- }).catch(() => void 0);
17976
- return true;
17977
- },
17978
- async revoke(handle) {
17979
- return transact((snapshot) => {
17980
- const claims = view(snapshot).rows;
17981
- const removed = claims.filter((claim) => matches(claim, handle));
17982
- if (removed.length === 0) {
17983
- return { next: snapshot, result: false };
17984
- }
17985
- const removedHashes = new Set(removed.map((claim) => claim.tokenHash));
17986
- return {
17987
- next: snapshot.filter((claim) => !removedHashes.has(claim.tokenHash)),
17988
- result: true,
17989
- commit: () => {
17990
- for (const claim of removed) {
17991
- revoked.add(claim.tokenHash);
17992
- if (claim.browserId !== null) {
17993
- opts.claimed.delete(claim.browserId);
17994
- opts.onRevoke?.(claim.browserId);
17995
- }
17996
- }
17997
- }
17998
- };
17999
- });
18000
- },
18001
- async repair() {
18002
- return transact((snapshot) => {
18003
- const currentView = view(snapshot);
18004
- const removed = currentView.quarantined.map(handleOf);
18005
- const removedHashes = new Set(
18006
- currentView.quarantined.map((row) => row.tokenHash)
18007
- );
18008
- return {
18009
- next: snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
18010
- result: { removed }
18011
- };
18012
- });
18013
- },
18014
- status() {
18015
- if (unreadable !== null) {
18016
- return {
18017
- state: "disabled",
18018
- error: "browser_registry_unreadable",
18019
- detail: unreadable
18020
- };
18021
- }
18022
- if (opts.forbiddenTokenHash && ledgerOverlapsToken(rows, opts.forbiddenTokenHash)) {
18023
- return {
18024
- state: "degraded",
18025
- error: "browser_registry_root_authority_overlap",
18026
- quarantinedHandles: rows.filter(
18027
- (row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
18028
- ).map(handleOf),
18029
- recovery: "browsers repair"
18030
- };
18031
- }
18032
- return { state: "ready" };
18033
- },
18034
- wasRevoked(tokenHash) {
18035
- return revoked.has(tokenHash);
18036
- }
18037
- };
18038
- }
18039
- var NAME = "datalab-extension-mcp-server";
18143
+ var NAME = MCP_SERVER_HEALTH_NAME;
18040
18144
  function readVersion() {
18041
18145
  return createRequire(import.meta.url)("../package.json").version;
18042
18146
  }