multi-workspace-for-notion 0.1.1 → 0.2.0

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.

Potentially problematic release.


This version of multi-workspace-for-notion might be problematic. Click here for more details.

package/plugin/server.mjs CHANGED
@@ -15609,34 +15609,180 @@ var StdioServerTransport = class {
15609
15609
  };
15610
15610
 
15611
15611
  // src/config.ts
15612
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
15612
+ import { mkdir as mkdir2, readFile, rename, rm as rm2, writeFile } from "node:fs/promises";
15613
15613
  import { homedir } from "node:os";
15614
+ import { join as join2 } from "node:path";
15615
+
15616
+ // src/keychain.ts
15617
+ import { randomBytes } from "node:crypto";
15618
+ import { spawn } from "node:child_process";
15619
+ var SERVICE = "multi-workspace-for-notion";
15620
+ var CHUNK_CHARS = 3e3;
15621
+ var keychain = {
15622
+ async get(account) {
15623
+ for (let attempt = 0; attempt < 2; attempt++) {
15624
+ const manifest = await readItem(account);
15625
+ if (manifest === void 0) return void 0;
15626
+ const { generation, count } = parseManifest(manifest);
15627
+ const chunks = [];
15628
+ for (let i = 0; i < count; i++) {
15629
+ const chunk = await readItem(chunkAccount(account, generation, i));
15630
+ if (chunk === void 0) break;
15631
+ chunks.push(chunk);
15632
+ }
15633
+ if (chunks.length === count) return Buffer.from(chunks.join(""), "base64").toString("utf8");
15634
+ }
15635
+ throw new Error(`\uD0A4\uCCB4\uC778 \uAC12\uC774 \uC77C\uBD80 \uC5C6\uC2B5\uB2C8\uB2E4: ${account}`);
15636
+ },
15637
+ async set(account, value) {
15638
+ assertSafeAccount(account);
15639
+ const previous = await readItem(account);
15640
+ const encoded = Buffer.from(value, "utf8").toString("base64");
15641
+ const generation = randomBytes(4).toString("hex");
15642
+ const count = Math.max(1, Math.ceil(encoded.length / CHUNK_CHARS));
15643
+ for (let i = 0; i < count; i++) {
15644
+ await writeItem(chunkAccount(account, generation, i), encoded.slice(i * CHUNK_CHARS, (i + 1) * CHUNK_CHARS));
15645
+ }
15646
+ await writeItem(account, `${generation}:${count}`);
15647
+ if (previous !== void 0) await deleteChunks(account, previous);
15648
+ },
15649
+ async delete(account) {
15650
+ const manifest = await readItem(account);
15651
+ if (manifest === void 0) return;
15652
+ await deleteItem(account);
15653
+ await deleteChunks(account, manifest);
15654
+ }
15655
+ };
15656
+ function parseManifest(manifest) {
15657
+ const match = /^([0-9a-f]+):(\d+)$/.exec(manifest);
15658
+ if (!match) throw new Error(`\uD0A4\uCCB4\uC778 \uAC12 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${manifest.slice(0, 20)}`);
15659
+ return { generation: match[1], count: Number(match[2]) };
15660
+ }
15661
+ async function deleteChunks(account, manifest) {
15662
+ const { generation, count } = parseManifest(manifest);
15663
+ for (let i = 0; i < count; i++) await deleteItem(chunkAccount(account, generation, i));
15664
+ }
15665
+ var chunkAccount = (account, generation, index) => `${account}.${generation}.${index}`;
15666
+ function assertSafeAccount(account) {
15667
+ if (!/^[A-Za-z0-9:_.-]+$/.test(account)) throw new Error(`\uD0A4\uCCB4\uC778 \uACC4\uC815 \uC774\uB984\uC5D0 \uC4F8 \uC218 \uC5C6\uB294 \uBB38\uC790: ${account}`);
15668
+ }
15669
+ async function readItem(account) {
15670
+ const { code, stdout, stderr } = await run(["find-generic-password", "-s", SERVICE, "-a", account, "-w"]);
15671
+ if (code === 44) return void 0;
15672
+ if (code !== 0) throw new Error(`\uD0A4\uCCB4\uC778 \uC77D\uAE30 \uC2E4\uD328 (${code}): ${stderr.trim()}`);
15673
+ return stdout.trim();
15674
+ }
15675
+ async function writeItem(account, value) {
15676
+ const { stdout, stderr } = await run(["-i"], `add-generic-password -U -s ${SERVICE} -a ${account} -w ${value}
15677
+ `);
15678
+ if (await readItem(account) !== value) {
15679
+ throw new Error(`\uD0A4\uCCB4\uC778 \uC4F0\uAE30 \uC2E4\uD328: ${(stderr || stdout).trim()}`);
15680
+ }
15681
+ }
15682
+ async function deleteItem(account) {
15683
+ const { code, stderr } = await run(["delete-generic-password", "-s", SERVICE, "-a", account]);
15684
+ if (code !== 0 && code !== 44) throw new Error(`\uD0A4\uCCB4\uC778 \uC0AD\uC81C \uC2E4\uD328 (${code}): ${stderr.trim()}`);
15685
+ }
15686
+ function run(args2, input) {
15687
+ return new Promise((resolve, reject) => {
15688
+ const child = spawn("/usr/bin/security", args2, { stdio: ["pipe", "pipe", "pipe"] });
15689
+ let stdout = "";
15690
+ let stderr = "";
15691
+ child.stdout.on("data", (chunk) => stdout += chunk);
15692
+ child.stderr.on("data", (chunk) => stderr += chunk);
15693
+ child.on("error", reject);
15694
+ child.on("close", (code) => resolve({ code, stdout, stderr }));
15695
+ child.stdin.end(input ?? "");
15696
+ });
15697
+ }
15698
+
15699
+ // src/lock.ts
15700
+ import { mkdir, rm, stat } from "node:fs/promises";
15614
15701
  import { join } from "node:path";
15702
+ var STALE_MS = 1e4;
15703
+ var RETRY_MS = 20;
15704
+ async function withFileLock(name, fn) {
15705
+ const dir = join(configDir(), "locks");
15706
+ const path = join(dir, `${name.replace(/[^A-Za-z0-9_.-]/g, "_")}.lock`);
15707
+ await mkdir(dir, { recursive: true, mode: 448 });
15708
+ for (; ; ) {
15709
+ try {
15710
+ await mkdir(path);
15711
+ break;
15712
+ } catch (error2) {
15713
+ if (error2.code !== "EEXIST") throw error2;
15714
+ const age = await stat(path).then((s) => Date.now() - s.mtimeMs, () => 0);
15715
+ if (age > STALE_MS) await rm(path, { recursive: true, force: true });
15716
+ else await new Promise((resolve) => setTimeout(resolve, RETRY_MS));
15717
+ }
15718
+ }
15719
+ try {
15720
+ return await fn();
15721
+ } finally {
15722
+ await rm(path, { recursive: true, force: true });
15723
+ }
15724
+ }
15725
+
15726
+ // src/config.ts
15615
15727
  function configDir() {
15616
- return process.env.MWN_CONFIG_DIR ?? join(homedir(), ".config", "multi-workspace-for-notion");
15728
+ return process.env.MWN_CONFIG_DIR ?? join2(homedir(), ".config", "multi-workspace-for-notion");
15729
+ }
15730
+ var credentialAccount = (workspaceId) => `workspace:${workspaceId}`;
15731
+ var cachePath = (workspaceId) => join2(configDir(), "cache", `catalog-${workspaceId}.json`);
15732
+ var configPath = () => join2(configDir(), "config.json");
15733
+ function uniqueName(name, workspaceId, others) {
15734
+ return others.some((w) => w.name === name) ? `${name} (${workspaceId.slice(0, 8)})` : name;
15617
15735
  }
15618
- var configPath = () => join(configDir(), "config.json");
15619
15736
  async function loadWorkspaces() {
15620
- try {
15621
- const parsed = JSON.parse(await readFile(configPath(), "utf8"));
15622
- return parsed.workspaces;
15623
- } catch (error2) {
15624
- if (error2.code === "ENOENT") return [];
15625
- throw error2;
15626
- }
15737
+ const file = await readConfig();
15738
+ if (!file) return [];
15739
+ if (file.version === 2) return file.workspaces;
15740
+ return withFileLock("config", migrateIfNeeded);
15627
15741
  }
15628
15742
  async function saveWorkspaces(workspaces) {
15629
- await mkdir(configDir(), { recursive: true, mode: 448 });
15743
+ await mkdir2(configDir(), { recursive: true, mode: 448 });
15630
15744
  const tmp = `${configPath()}.${process.pid}.tmp`;
15631
- const data = { version: 1, workspaces };
15745
+ const data = { version: 2, workspaces };
15632
15746
  await writeFile(tmp, `${JSON.stringify(data, null, 2)}
15633
15747
  `, { mode: 384 });
15634
15748
  await rename(tmp, configPath());
15635
15749
  }
15750
+ async function updateWorkspaces(change) {
15751
+ await withFileLock("config", async () => saveWorkspaces(change(await migrateIfNeeded())));
15752
+ }
15753
+ async function readConfig() {
15754
+ try {
15755
+ return JSON.parse(await readFile(configPath(), "utf8"));
15756
+ } catch (error2) {
15757
+ if (error2.code === "ENOENT") return void 0;
15758
+ throw error2;
15759
+ }
15760
+ }
15761
+ async function migrateIfNeeded() {
15762
+ const file = await readConfig();
15763
+ if (!file) return [];
15764
+ if (file.version === 2) return file.workspaces;
15765
+ const migrated = [];
15766
+ for (const { alias, notionWorkspaceName, ...rest } of file.workspaces) {
15767
+ const from = `workspace:${alias}`;
15768
+ const to = credentialAccount(rest.notionWorkspaceId);
15769
+ await withFileLock(from, async () => {
15770
+ const credentials = await keychain.get(from);
15771
+ if (credentials !== void 0) {
15772
+ await keychain.set(to, credentials);
15773
+ await keychain.delete(from);
15774
+ }
15775
+ });
15776
+ await rm2(join2(configDir(), "cache", `catalog-${alias}.json`), { force: true });
15777
+ migrated.push({ ...rest, name: uniqueName(notionWorkspaceName, rest.notionWorkspaceId, migrated) });
15778
+ }
15779
+ await saveWorkspaces(migrated);
15780
+ return migrated;
15781
+ }
15636
15782
 
15637
15783
  // src/connect.ts
15638
15784
  import { spawn as spawn2 } from "node:child_process";
15639
- import { rm as rm2 } from "node:fs/promises";
15785
+ import { rm as rm3 } from "node:fs/promises";
15640
15786
 
15641
15787
  // node_modules/pkce-challenge/dist/index.node.js
15642
15788
  var crypto;
@@ -16696,89 +16842,6 @@ function listen(server, port, host) {
16696
16842
  });
16697
16843
  }
16698
16844
 
16699
- // src/keychain.ts
16700
- import { randomBytes } from "node:crypto";
16701
- import { spawn } from "node:child_process";
16702
- var SERVICE = "multi-workspace-for-notion";
16703
- var CHUNK_CHARS = 3e3;
16704
- var keychain = {
16705
- async get(account) {
16706
- for (let attempt = 0; attempt < 2; attempt++) {
16707
- const manifest = await readItem(account);
16708
- if (manifest === void 0) return void 0;
16709
- const { generation, count } = parseManifest(manifest);
16710
- const chunks = [];
16711
- for (let i = 0; i < count; i++) {
16712
- const chunk = await readItem(chunkAccount(account, generation, i));
16713
- if (chunk === void 0) break;
16714
- chunks.push(chunk);
16715
- }
16716
- if (chunks.length === count) return Buffer.from(chunks.join(""), "base64").toString("utf8");
16717
- }
16718
- throw new Error(`\uD0A4\uCCB4\uC778 \uAC12\uC774 \uC77C\uBD80 \uC5C6\uC2B5\uB2C8\uB2E4: ${account}`);
16719
- },
16720
- async set(account, value) {
16721
- assertSafeAccount(account);
16722
- const previous = await readItem(account);
16723
- const encoded = Buffer.from(value, "utf8").toString("base64");
16724
- const generation = randomBytes(4).toString("hex");
16725
- const count = Math.max(1, Math.ceil(encoded.length / CHUNK_CHARS));
16726
- for (let i = 0; i < count; i++) {
16727
- await writeItem(chunkAccount(account, generation, i), encoded.slice(i * CHUNK_CHARS, (i + 1) * CHUNK_CHARS));
16728
- }
16729
- await writeItem(account, `${generation}:${count}`);
16730
- if (previous !== void 0) await deleteChunks(account, previous);
16731
- },
16732
- async delete(account) {
16733
- const manifest = await readItem(account);
16734
- if (manifest === void 0) return;
16735
- await deleteItem(account);
16736
- await deleteChunks(account, manifest);
16737
- }
16738
- };
16739
- function parseManifest(manifest) {
16740
- const match = /^([0-9a-f]+):(\d+)$/.exec(manifest);
16741
- if (!match) throw new Error(`\uD0A4\uCCB4\uC778 \uAC12 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${manifest.slice(0, 20)}`);
16742
- return { generation: match[1], count: Number(match[2]) };
16743
- }
16744
- async function deleteChunks(account, manifest) {
16745
- const { generation, count } = parseManifest(manifest);
16746
- for (let i = 0; i < count; i++) await deleteItem(chunkAccount(account, generation, i));
16747
- }
16748
- var chunkAccount = (account, generation, index) => `${account}.${generation}.${index}`;
16749
- function assertSafeAccount(account) {
16750
- if (!/^[A-Za-z0-9:_.-]+$/.test(account)) throw new Error(`\uD0A4\uCCB4\uC778 \uACC4\uC815 \uC774\uB984\uC5D0 \uC4F8 \uC218 \uC5C6\uB294 \uBB38\uC790: ${account}`);
16751
- }
16752
- async function readItem(account) {
16753
- const { code, stdout, stderr } = await run(["find-generic-password", "-s", SERVICE, "-a", account, "-w"]);
16754
- if (code === 44) return void 0;
16755
- if (code !== 0) throw new Error(`\uD0A4\uCCB4\uC778 \uC77D\uAE30 \uC2E4\uD328 (${code}): ${stderr.trim()}`);
16756
- return stdout.trim();
16757
- }
16758
- async function writeItem(account, value) {
16759
- const { stdout, stderr } = await run(["-i"], `add-generic-password -U -s ${SERVICE} -a ${account} -w ${value}
16760
- `);
16761
- if (await readItem(account) !== value) {
16762
- throw new Error(`\uD0A4\uCCB4\uC778 \uC4F0\uAE30 \uC2E4\uD328: ${(stderr || stdout).trim()}`);
16763
- }
16764
- }
16765
- async function deleteItem(account) {
16766
- const { code, stderr } = await run(["delete-generic-password", "-s", SERVICE, "-a", account]);
16767
- if (code !== 0 && code !== 44) throw new Error(`\uD0A4\uCCB4\uC778 \uC0AD\uC81C \uC2E4\uD328 (${code}): ${stderr.trim()}`);
16768
- }
16769
- function run(args2, input) {
16770
- return new Promise((resolve, reject) => {
16771
- const child = spawn("/usr/bin/security", args2, { stdio: ["pipe", "pipe", "pipe"] });
16772
- let stdout = "";
16773
- let stderr = "";
16774
- child.stdout.on("data", (chunk) => stdout += chunk);
16775
- child.stderr.on("data", (chunk) => stderr += chunk);
16776
- child.on("error", reject);
16777
- child.on("close", (code) => resolve({ code, stdout, stderr }));
16778
- child.stdin.end(input ?? "");
16779
- });
16780
- }
16781
-
16782
16845
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
16783
16846
  function isZ4Schema(s) {
16784
16847
  const schema = s;
@@ -19245,7 +19308,7 @@ var StreamableHTTPClientTransport = class {
19245
19308
 
19246
19309
  // src/meta.ts
19247
19310
  var SERVER_NAME = "multi-workspace-for-notion";
19248
- var SERVER_VERSION = "0.1.1";
19311
+ var SERVER_VERSION = "0.2.0";
19249
19312
 
19250
19313
  // src/notion.ts
19251
19314
  var NOTION_MCP_URL = new URL("https://mcp.notion.com/mcp");
@@ -19287,66 +19350,15 @@ var NotionSession = class _NotionSession {
19287
19350
  }
19288
19351
  };
19289
19352
  function parseSelf(text2) {
19290
- const json2 = JSON.parse(text2);
19291
- const id = findString(json2, ["workspace_id", "workspaceId"]) ?? findNested(json2, "workspace", ["id"]);
19292
- const name = findString(json2, ["workspace_name", "workspaceName"]) ?? findNested(json2, "workspace", ["name"]);
19293
- if (!id || !name) throw new Error(`notion-get-self \uC751\uB2F5\uC5D0\uC11C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${text2.slice(0, 300)}`);
19294
- return { workspaceId: id, workspaceName: name };
19295
- }
19296
- function findString(value, keys) {
19297
- if (!value || typeof value !== "object") return void 0;
19298
- for (const [key, child] of Object.entries(value)) {
19299
- if (keys.includes(key) && typeof child === "string") return child;
19300
- const nested = findString(child, keys);
19301
- if (nested) return nested;
19302
- }
19303
- return void 0;
19304
- }
19305
- function findNested(value, parentKey, keys) {
19306
- if (!value || typeof value !== "object") return void 0;
19307
- for (const [key, child] of Object.entries(value)) {
19308
- if (key === parentKey) {
19309
- const found = findString(child, keys);
19310
- if (found) return found;
19311
- }
19312
- const nested = findNested(child, parentKey, keys);
19313
- if (nested) return nested;
19353
+ const workspace = JSON.parse(text2).workspace;
19354
+ if (typeof workspace?.id !== "string" || typeof workspace.name !== "string") {
19355
+ throw new Error(`notion-get-self \uC751\uB2F5\uC5D0\uC11C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${text2.slice(0, 300)}`);
19314
19356
  }
19315
- return void 0;
19357
+ return { workspaceId: workspace.id, workspaceName: workspace.name };
19316
19358
  }
19317
19359
 
19318
19360
  // src/oauth.ts
19319
19361
  import { randomBytes as randomBytes2 } from "node:crypto";
19320
-
19321
- // src/lock.ts
19322
- import { mkdir as mkdir2, rm, stat } from "node:fs/promises";
19323
- import { join as join2 } from "node:path";
19324
- var STALE_MS = 1e4;
19325
- var RETRY_MS = 20;
19326
- async function withFileLock(name, fn) {
19327
- const dir = join2(configDir(), "locks");
19328
- const path = join2(dir, `${name.replace(/[^A-Za-z0-9_.-]/g, "_")}.lock`);
19329
- await mkdir2(dir, { recursive: true, mode: 448 });
19330
- for (; ; ) {
19331
- try {
19332
- await mkdir2(path);
19333
- break;
19334
- } catch (error2) {
19335
- if (error2.code !== "EEXIST") throw error2;
19336
- const age = await stat(path).then((s) => Date.now() - s.mtimeMs, () => 0);
19337
- if (age > STALE_MS) await rm(path, { recursive: true, force: true });
19338
- else await new Promise((resolve) => setTimeout(resolve, RETRY_MS));
19339
- }
19340
- }
19341
- try {
19342
- return await fn();
19343
- } finally {
19344
- await rm(path, { recursive: true, force: true });
19345
- }
19346
- }
19347
-
19348
- // src/oauth.ts
19349
- var credentialAccount = (alias) => `workspace:${alias}`;
19350
19362
  var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
19351
19363
  constructor(account, stored, onRedirect) {
19352
19364
  this.account = account;
@@ -19358,17 +19370,20 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
19358
19370
  onRedirect;
19359
19371
  verifier;
19360
19372
  expectedState;
19361
- /** The keychain value was seen or saved. If it disappears later, the workspace was disconnected or renamed. */
19373
+ /** The keychain value was seen or saved. If it disappears later, the workspace was disconnected. */
19362
19374
  persisted = false;
19363
- /** For a new connection. redirectUrl is the callback server's address. */
19364
- static forNewConnection(alias, redirectUrl, onRedirect) {
19365
- return new _WorkspaceAuthProvider(credentialAccount(alias), { redirectUrl }, onRedirect);
19375
+ /**
19376
+ * For a new connection. The workspace (and so its ID) is only known after sign-in, so this saves under a
19377
+ * temporary account that the caller moves to `workspace:<id>` with moveTo(). redirectUrl is the callback server's address.
19378
+ */
19379
+ static forNewConnection(redirectUrl, onRedirect) {
19380
+ return new _WorkspaceAuthProvider(`pending:${randomBytes2(8).toString("hex")}`, { redirectUrl }, onRedirect);
19366
19381
  }
19367
19382
  /** For a connected workspace. Undefined when nothing is stored. */
19368
- static async forExisting(alias) {
19369
- const raw = await keychain.get(credentialAccount(alias));
19383
+ static async forExisting(workspaceId) {
19384
+ const raw = await keychain.get(credentialAccount(workspaceId));
19370
19385
  if (raw === void 0) return void 0;
19371
- const provider = new _WorkspaceAuthProvider(credentialAccount(alias), JSON.parse(raw), void 0);
19386
+ const provider = new _WorkspaceAuthProvider(credentialAccount(workspaceId), JSON.parse(raw), void 0);
19372
19387
  provider.persisted = true;
19373
19388
  return provider;
19374
19389
  }
@@ -19407,10 +19422,7 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
19407
19422
  return this.stored.tokens;
19408
19423
  }
19409
19424
  async saveTokens(tokens) {
19410
- await this.update((s) => {
19411
- s.tokens = tokens;
19412
- s.tokensSavedAt = Date.now();
19413
- });
19425
+ await this.update((s) => s.tokens = tokens);
19414
19426
  }
19415
19427
  async redirectToAuthorization(authorizationUrl) {
19416
19428
  if (!this.onRedirect) {
@@ -19438,16 +19450,21 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
19438
19450
  if (scope === "verifier") return;
19439
19451
  await this.update((s) => {
19440
19452
  if (scope === "all" || scope === "client") delete s.clientInformation;
19441
- if (scope === "all") {
19442
- delete s.tokens;
19443
- delete s.tokensSavedAt;
19444
- }
19453
+ if (scope === "all") delete s.tokens;
19445
19454
  if (scope === "all" || scope === "discovery") delete s.discoveryState;
19446
19455
  });
19447
19456
  }
19448
19457
  async deleteStored() {
19449
19458
  await keychain.delete(this.account);
19450
19459
  }
19460
+ /** Moves the stored credentials to another keychain account (a new connection's final `workspace:<id>`). */
19461
+ async moveTo(account) {
19462
+ const raw = await keychain.get(this.account);
19463
+ if (raw === void 0) throw new Error("\uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
19464
+ await keychain.set(account, raw);
19465
+ await keychain.delete(this.account);
19466
+ this.account = account;
19467
+ }
19451
19468
  async reload() {
19452
19469
  const raw = await keychain.get(this.account);
19453
19470
  if (raw !== void 0) this.stored = JSON.parse(raw);
@@ -19455,14 +19472,14 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
19455
19472
  /**
19456
19473
  * Writing the in-memory copy wholesale would overwrite newer tokens another process saved meanwhile.
19457
19474
  * So take the lock, re-read the keychain and change only the given fields.
19458
- * If the value vanished because another process disconnected or renamed the workspace, write nothing,
19475
+ * If the value vanished because another process disconnected the workspace, write nothing,
19459
19476
  * so deleted tokens stay deleted.
19460
19477
  */
19461
19478
  async update(change) {
19462
19479
  await withFileLock(this.account, async () => {
19463
19480
  const raw = await keychain.get(this.account);
19464
19481
  if (raw !== void 0) this.stored = JSON.parse(raw);
19465
- else if (this.persisted) throw new Error("\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \uC5F0\uACB0\uC774 \uD574\uC81C\uB418\uC5C8\uAC70\uB098 \uBCC4\uCE6D\uC774 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8 \uB300\uD654\uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
19482
+ else if (this.persisted) throw new Error("\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \uB2E4\uB978 \uC138\uC158\uC5D0\uC11C \uC5F0\uACB0\uC774 \uD574\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8 \uB300\uD654\uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
19466
19483
  change(this.stored);
19467
19484
  await keychain.set(this.account, JSON.stringify(this.stored));
19468
19485
  this.persisted = true;
@@ -19498,110 +19515,12 @@ async function revokeTokens(stored, fetchFn = fetch) {
19498
19515
  }
19499
19516
  }
19500
19517
 
19501
- // src/upstream.ts
19502
- import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
19503
- import { join as join3 } from "node:path";
19504
- var UpstreamPool = class {
19505
- constructor(open2 = openWorkspace) {
19506
- this.open = open2;
19507
- }
19508
- open;
19509
- sessions = /* @__PURE__ */ new Map();
19510
- session(alias) {
19511
- const existing = this.sessions.get(alias);
19512
- if (existing) return existing;
19513
- const opened = this.open(alias);
19514
- opened.catch(() => {
19515
- if (this.sessions.get(alias) === opened) this.sessions.delete(alias);
19516
- });
19517
- this.sessions.set(alias, opened);
19518
- return opened;
19519
- }
19520
- /**
19521
- * An expired upstream MCP session gets a 404 and the request was not processed, so reopen and send it once more.
19522
- * Other errors may come after the request was processed, so those are never resent.
19523
- */
19524
- async callTool(alias, name, args2) {
19525
- try {
19526
- return await (await this.session(alias)).callTool(name, args2);
19527
- } catch (error2) {
19528
- if (!(error2 instanceof StreamableHTTPError && error2.code === 404)) throw error2;
19529
- await this.reset(alias);
19530
- return (await this.session(alias)).callTool(name, args2);
19531
- }
19532
- }
19533
- async reset(alias) {
19534
- const pending = this.sessions.get(alias);
19535
- this.sessions.delete(alias);
19536
- await pending?.then((s) => s.close()).catch(() => {
19537
- });
19538
- }
19539
- async closeAll() {
19540
- await Promise.all([...this.sessions.keys()].map((alias) => this.reset(alias)));
19541
- }
19542
- };
19543
- var LIVE_TIMEOUT_MS = 15e3;
19544
- async function loadCatalogs(workspaces, pool) {
19545
- const results = await Promise.all(
19546
- workspaces.map(async (w) => {
19547
- try {
19548
- const catalog = await withTimeout(fetchLive(w.alias, pool), LIVE_TIMEOUT_MS);
19549
- await writeCache(catalog);
19550
- return catalog;
19551
- } catch (error2) {
19552
- const cached2 = await readCache(w.alias);
19553
- return cached2 ?? { alias: w.alias, reason: error2.message };
19554
- }
19555
- })
19556
- );
19557
- return {
19558
- catalogs: results.filter((r) => "tools" in r),
19559
- failures: results.filter((r) => "reason" in r)
19560
- };
19561
- }
19562
- async function fetchLive(alias, pool) {
19563
- const session = await pool.session(alias);
19564
- return {
19565
- alias,
19566
- tools: await session.listTools(),
19567
- instructions: session.instructions,
19568
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
19569
- };
19570
- }
19571
- var cachePath = (alias) => join3(configDir(), "cache", `catalog-${alias}.json`);
19572
- async function writeCache(catalog) {
19573
- await mkdir3(join3(configDir(), "cache"), { recursive: true, mode: 448 });
19574
- const tmp = `${cachePath(catalog.alias)}.${process.pid}.tmp`;
19575
- await writeFile2(tmp, JSON.stringify(catalog), { mode: 384 });
19576
- await rename2(tmp, cachePath(catalog.alias));
19577
- }
19578
- async function readCache(alias) {
19579
- try {
19580
- return JSON.parse(await readFile2(cachePath(alias), "utf8"));
19581
- } catch {
19582
- return void 0;
19583
- }
19584
- }
19585
- function withTimeout(promise, ms) {
19586
- let timer;
19587
- return Promise.race([
19588
- promise,
19589
- new Promise((_, reject) => {
19590
- timer = setTimeout(() => reject(new Error(`${ms / 1e3}\uCD08 \uC548\uC5D0 \uC751\uB2F5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`)), ms);
19591
- })
19592
- ]).finally(() => clearTimeout(timer));
19593
- }
19594
-
19595
19518
  // src/connect.ts
19596
- var ALIAS_PATTERN = /^[a-z0-9][a-z0-9_-]{0,31}$/;
19597
19519
  var AUTH_TIMEOUT_MS = 10 * 60 * 1e3;
19520
+ var WORKSPACE_ID = /^[0-9a-f-]{32,36}$/i;
19598
19521
  async function connectWorkspace(options) {
19599
- const { alias, description } = options;
19600
- assertAlias(alias);
19601
- const existing = await loadWorkspaces();
19602
- if (existing.some((w) => w.alias === alias)) throw new Error(`\uC774\uBBF8 \uC4F0\uACE0 \uC788\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
19603
19522
  const callback = await startCallbackServer();
19604
- const provider = WorkspaceAuthProvider.forNewConnection(alias, callback.redirectUrl, (url2) => {
19523
+ const provider = WorkspaceAuthProvider.forNewConnection(callback.redirectUrl, (url2) => {
19605
19524
  options.onAuthorizationUrl(url2);
19606
19525
  openBrowser(url2);
19607
19526
  });
@@ -19613,23 +19532,26 @@ async function connectWorkspace(options) {
19613
19532
  }
19614
19533
  session = await NotionSession.open(provider);
19615
19534
  const self = await session.getSelf();
19616
- const duplicate = existing.find((w) => w.notionWorkspaceId === self.workspaceId);
19535
+ if (!WORKSPACE_ID.test(self.workspaceId)) throw new Error(`\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 ID\uC785\uB2C8\uB2E4: ${self.workspaceId}`);
19536
+ const duplicate = (await loadWorkspaces()).find((w) => w.notionWorkspaceId === self.workspaceId);
19617
19537
  if (duplicate) {
19618
19538
  throw new Error(
19619
- `"${self.workspaceName}" \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \uC774\uBBF8 "${duplicate.alias}"(\uC73C)\uB85C \uC5F0\uACB0\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. Notion \uC778\uC99D \uD654\uBA74\uC5D0\uC11C \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB790\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`
19539
+ `"${duplicate.name}" \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \uC774\uBBF8 \uC5F0\uACB0\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. Notion \uC778\uC99D \uD654\uBA74\uC5D0\uC11C \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB790\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`
19620
19540
  );
19621
19541
  }
19622
19542
  const tools = await session.listTools();
19623
- const entry = {
19624
- alias,
19625
- description,
19626
- notionWorkspaceId: self.workspaceId,
19627
- notionWorkspaceName: self.workspaceName,
19628
- status: "connected",
19629
- lastSuccessAt: (/* @__PURE__ */ new Date()).toISOString()
19630
- };
19631
- const latest = await loadWorkspaces();
19632
- await saveWorkspaces([...latest.filter((w) => w.alias !== alias), entry]);
19543
+ await provider.moveTo(credentialAccount(self.workspaceId));
19544
+ let entry;
19545
+ await updateWorkspaces((latest) => {
19546
+ entry = {
19547
+ name: uniqueName(self.workspaceName, self.workspaceId, latest),
19548
+ notionWorkspaceId: self.workspaceId,
19549
+ description: options.description,
19550
+ status: "connected",
19551
+ lastSuccessAt: (/* @__PURE__ */ new Date()).toISOString()
19552
+ };
19553
+ return [...latest, entry];
19554
+ });
19633
19555
  return { entry, tools };
19634
19556
  } catch (error2) {
19635
19557
  await provider.deleteStored();
@@ -19639,55 +19561,27 @@ async function connectWorkspace(options) {
19639
19561
  await session?.close();
19640
19562
  }
19641
19563
  }
19642
- async function openWorkspace(alias) {
19643
- const provider = await WorkspaceAuthProvider.forExisting(alias);
19644
- if (!provider) throw new Error(`"${alias}"\uC758 \uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`);
19564
+ async function findWorkspace(name) {
19565
+ const entry = (await loadWorkspaces()).find((w) => w.name === name);
19566
+ if (!entry) throw new Error(`\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC544\uB2D9\uB2C8\uB2E4: ${name}`);
19567
+ return entry;
19568
+ }
19569
+ async function openWorkspace(name) {
19570
+ const entry = await findWorkspace(name);
19571
+ const provider = await WorkspaceAuthProvider.forExisting(entry.notionWorkspaceId);
19572
+ if (!provider) throw new Error(`"${name}"\uC758 \uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`);
19645
19573
  return NotionSession.open(provider);
19646
19574
  }
19647
- async function disconnectWorkspace(alias) {
19648
- assertAlias(alias);
19649
- const workspaces = await loadWorkspaces();
19650
- const raw = await withFileLock(credentialAccount(alias), async () => {
19651
- const found = await keychain.get(credentialAccount(alias));
19652
- await keychain.delete(credentialAccount(alias));
19575
+ async function disconnectWorkspace(name) {
19576
+ const { notionWorkspaceId: id } = await findWorkspace(name);
19577
+ const raw = await withFileLock(credentialAccount(id), async () => {
19578
+ const found = await keychain.get(credentialAccount(id));
19579
+ await keychain.delete(credentialAccount(id));
19653
19580
  return found;
19654
19581
  });
19655
- await rm2(cachePath(alias), { force: true });
19656
- const remaining = workspaces.filter((w) => w.alias !== alias);
19657
- await saveWorkspaces(remaining);
19658
- const revoked = raw !== void 0 && await revokeTokens(JSON.parse(raw));
19659
- return { removed: remaining.length !== workspaces.length || raw !== void 0, revoked };
19660
- }
19661
- async function renameWorkspace(alias, changes) {
19662
- const workspaces = await loadWorkspaces();
19663
- const entry = workspaces.find((w) => w.alias === alias);
19664
- if (!entry) throw new Error(`\uC5F0\uACB0\uB41C \uC801 \uC5C6\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
19665
- const newAlias = changes.newAlias ?? alias;
19666
- const moving = newAlias !== alias;
19667
- if (moving) {
19668
- assertAlias(newAlias);
19669
- if (workspaces.some((w) => w.alias === newAlias)) throw new Error(`\uC774\uBBF8 \uC4F0\uACE0 \uC788\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${newAlias}`);
19670
- }
19671
- const updated = { ...entry, alias: newAlias, description: changes.description ?? entry.description };
19672
- const save = () => saveWorkspaces(workspaces.map((w) => w.alias === alias ? updated : w));
19673
- if (!moving) {
19674
- await save();
19675
- return updated;
19676
- }
19677
- await withFileLock(credentialAccount(alias), async () => {
19678
- const credentials = await keychain.get(credentialAccount(alias));
19679
- if (credentials === void 0) throw new Error(`"${alias}"\uC758 \uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`);
19680
- await keychain.set(credentialAccount(newAlias), credentials);
19681
- await save();
19682
- await keychain.delete(credentialAccount(alias));
19683
- });
19684
- await rm2(cachePath(alias), { force: true });
19685
- return updated;
19686
- }
19687
- function assertAlias(alias) {
19688
- if (!ALIAS_PATTERN.test(alias)) {
19689
- throw new Error("\uBCC4\uCE6D\uC740 \uC601\uC5B4 \uC18C\uBB38\uC790\xB7\uC22B\uC790\uB85C \uC2DC\uC791\uD558\uACE0 \uC18C\uBB38\uC790, \uC22B\uC790, -, _\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4(\uCD5C\uB300 32\uC790).");
19690
- }
19582
+ await rm3(cachePath(id), { force: true });
19583
+ await updateWorkspaces((workspaces) => workspaces.filter((w) => w.notionWorkspaceId !== id));
19584
+ return { revoked: raw !== void 0 && await revokeTokens(JSON.parse(raw)) };
19691
19585
  }
19692
19586
  function openBrowser(url2) {
19693
19587
  const child = spawn2("open", [url2.href], { stdio: "ignore", detached: true });
@@ -20322,10 +20216,11 @@ ${tool.description ?? ""}` : tool.description,
20322
20216
  inputSchema: {
20323
20217
  ...tool.inputSchema,
20324
20218
  properties: {
20219
+ // A plain string, not an enum: Claude keeps a conversation's tool schemas even after tools/list_changed,
20220
+ // so an enum would go stale as soon as a workspace is connected. The server checks the name.
20325
20221
  [WORKSPACE_PARAM]: {
20326
20222
  type: "string",
20327
- enum: aliases,
20328
- description: "\uC774 \uD638\uCD9C\uC744 \uBCF4\uB0BC Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uBCC4\uCE6D"
20223
+ description: `\uC774 \uD638\uCD9C\uC744 \uBCF4\uB0BC Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984. \uC774 \uC11C\uBC84\uB97C \uC2DC\uC791\uD560 \uB54C \uC5F0\uACB0\uB3FC \uC788\uB358 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4: ${aliases.join(", ")}. \uC774\uD6C4 \uC0C8\uB85C \uC5F0\uACB0\uD55C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB3C4 \uBC14\uB85C \uC4F8 \uC218 \uC788\uB2E4.`
20329
20224
  },
20330
20225
  ...tool.inputSchema.properties
20331
20226
  },
@@ -20417,108 +20312,103 @@ function errorResult(text2) {
20417
20312
  }
20418
20313
 
20419
20314
  // src/management.ts
20420
- var aliasParam = { type: "string", description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBCC4\uCE6D" };
20315
+ var workspaceParam = { type: "string", description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC774\uB984(workspace_list\uC758 name)" };
20421
20316
  var managementTools = [
20422
20317
  {
20423
20318
  name: "workspace_list",
20424
- description: "\uC5F0\uACB0\uB41C Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uBCC4\uCE6D, \uC124\uBA85, \uC2E4\uC81C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC774\uB984\uACFC ID, \uC0C1\uD0DC, \uC5F0\uACB0\uC744 \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uD655\uC778\uD55C \uC2DC\uAC01\uC744 \uBCF4\uC5EC\uC900\uB2E4.",
20319
+ description: "\uC5F0\uACB0\uB41C Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984, ID, \uC124\uBA85, \uC0C1\uD0DC, \uC5F0\uACB0\uC744 \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uD655\uC778\uD55C \uC2DC\uAC01\uC744 \uBCF4\uC5EC\uC900\uB2E4.",
20425
20320
  inputSchema: { type: "object", properties: {} },
20426
20321
  annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBAA9\uB85D", readOnlyHint: true }
20427
20322
  },
20428
20323
  {
20429
20324
  name: "workspace_connect",
20430
- description: "\uC0C8 Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC5F0\uACB0\uD55C\uB2E4. \uC0AC\uC6A9\uC790\uC758 \uBE0C\uB77C\uC6B0\uC800\uC5D0 Notion \uB85C\uADF8\uC778 \uD654\uBA74\uC774 \uC5F4\uB9AC\uACE0, \uC0AC\uC6A9\uC790\uAC00 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB77C \uC2B9\uC778\uD560 \uB54C\uAE4C\uC9C0(\uCD5C\uB300 10\uBD84) \uAE30\uB2E4\uB9B0\uB2E4. \uC2B9\uC778 \uD6C4 \uC2E4\uC81C\uB85C \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC774\uB984\uC744 \uB3CC\uB824\uC8FC\uBBC0\uB85C \uC0AC\uC6A9\uC790\uAC00 \uC758\uB3C4\uD55C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC778\uC9C0 \uD655\uC778\uC2DC\uD0A8\uB2E4. \uC0C8 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 Notion \uB3C4\uAD6C\uB294 \uC0C8 \uB300\uD654\uBD80\uD130 \uBCF4\uC778\uB2E4.",
20325
+ description: "\uC0C8 Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC5F0\uACB0\uD55C\uB2E4. \uCCAB \uD638\uCD9C\uC740 \uC0AC\uC6A9\uC790\uC758 \uBE0C\uB77C\uC6B0\uC800\uC5D0 Notion \uB85C\uADF8\uC778 \uD654\uBA74\uC744 \uC5F4\uACE0 \uADF8 \uB85C\uADF8\uC778 \uC8FC\uC18C(authorizationUrl)\uB97C \uBC14\uB85C \uB3CC\uB824\uC900\uB2E4. \uBE0C\uB77C\uC6B0\uC800\uAC00 \uC548 \uC5F4\uB838\uC744 \uC218 \uC788\uC73C\uB2C8 \uC8FC\uC18C\uB97C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBCF4\uC5EC\uC8FC\uACE0, \uC5F0\uACB0\uD560 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB77C \uC2B9\uC778\uD558\uB77C\uACE0 \uD55C\uB2E4(\uCD5C\uB300 10\uBD84). \uC0AC\uC6A9\uC790\uAC00 \uC2B9\uC778\uD588\uB2E4\uACE0 \uD558\uBA74 \uB2E4\uC2DC \uD638\uCD9C\uD574 \uACB0\uACFC\uB97C \uBC1B\uB294\uB2E4. \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 Notion\uC5D0\uC11C\uC758 \uC774\uB984\uC73C\uB85C \uBD88\uB9AC\uACE0, \uC774 \uB300\uD654\uC5D0\uC11C \uBC14\uB85C workspace \uAC12\uC73C\uB85C \uC4F8 \uC218 \uC788\uB2E4.",
20431
20326
  inputSchema: {
20432
20327
  type: "object",
20433
20328
  properties: {
20434
- alias: {
20435
- type: "string",
20436
- description: "\uC0C8 \uBCC4\uCE6D. \uC601\uC5B4 \uC18C\uBB38\uC790\uB098 \uC22B\uC790\uB85C \uC2DC\uC791\uD558\uACE0 \uC601\uC5B4 \uC18C\uBB38\uC790, \uC22B\uC790, -, _\uB9CC \uC4F4\uB2E4(\uCD5C\uB300 32\uC790)."
20437
- },
20438
20329
  description: { type: "string", description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uBB34\uC5C7\uC774 \uC788\uB294\uC9C0 \uC9E7\uC740 \uC124\uBA85. Claude\uAC00 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE0\uB97C \uB54C \uC4F4\uB2E4." }
20439
- },
20440
- required: ["alias"]
20330
+ }
20441
20331
  },
20442
20332
  annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0", readOnlyHint: false, destructiveHint: false, openWorldHint: true }
20443
20333
  },
20444
20334
  {
20445
20335
  name: "workspace_disconnect",
20446
20336
  description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0\uC744 \uD574\uC81C\uD55C\uB2E4. \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uC800\uC7A5\uB41C \uD1A0\uD070\uACFC \uC124\uC815\uC744 \uC9C0\uC6B0\uACE0 Notion \uCABD \uD1A0\uD070\uB3C4 \uD574\uC81C\uD55C\uB2E4. \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC73C\uBBC0\uB85C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uC744 \uBC1B\uC740 \uB4A4 \uD638\uCD9C\uD55C\uB2E4.",
20447
- inputSchema: { type: "object", properties: { alias: aliasParam }, required: ["alias"] },
20337
+ inputSchema: { type: "object", properties: { workspace: workspaceParam }, required: ["workspace"] },
20448
20338
  annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0 \uD574\uC81C", readOnlyHint: false, destructiveHint: true, idempotentHint: true }
20449
20339
  },
20450
- {
20451
- name: "workspace_rename",
20452
- description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uBCC4\uCE6D\uC774\uB098 \uC124\uBA85\uC744 \uBC14\uAFBC\uB2E4. \uB2E4\uC2DC \uB85C\uADF8\uC778\uD558\uC9C0 \uC54A\uB294\uB2E4. \uBC14\uB010 \uBCC4\uCE6D\uC740 \uC0C8 \uB300\uD654\uBD80\uD130 workspace \uAC12\uC73C\uB85C \uC4F8 \uC218 \uC788\uB2E4.",
20453
- inputSchema: {
20454
- type: "object",
20455
- properties: {
20456
- alias: { type: "string", description: "\uC9C0\uAE08 \uBCC4\uCE6D" },
20457
- new_alias: {
20458
- type: "string",
20459
- description: "\uC0C8 \uBCC4\uCE6D. \uC601\uC5B4 \uC18C\uBB38\uC790\uB098 \uC22B\uC790\uB85C \uC2DC\uC791\uD558\uACE0 \uC601\uC5B4 \uC18C\uBB38\uC790, \uC22B\uC790, -, _\uB9CC \uC4F4\uB2E4(\uCD5C\uB300 32\uC790). \uBC14\uAFB8\uC9C0 \uC54A\uC73C\uBA74 \uC0DD\uB7B5\uD55C\uB2E4."
20460
- },
20461
- description: { type: "string", description: "\uC0C8 \uC124\uBA85. \uBC14\uAFB8\uC9C0 \uC54A\uC73C\uBA74 \uC0DD\uB7B5\uD55C\uB2E4." }
20462
- },
20463
- required: ["alias"]
20464
- },
20465
- annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC774\uB984 \uBC14\uAFB8\uAE30", readOnlyHint: false, destructiveHint: false, idempotentHint: true }
20466
- },
20467
20340
  {
20468
20341
  name: "workspace_check",
20469
20342
  description: "\uC800\uC7A5\uB41C \uC778\uC99D\uC73C\uB85C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uB2E4\uC2DC \uBD99\uC5B4\uC11C \uC5F0\uACB0\uC774 \uC0B4\uC544 \uC788\uB294\uC9C0, \uCC98\uC74C \uC5F0\uACB0\uD55C \uADF8 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uB9DE\uB294\uC9C0 \uD655\uC778\uD558\uACE0 \uB3C4\uAD6C \uC218\uB97C \uC54C\uB824\uC900\uB2E4. \uACB0\uACFC\uB85C \uC0C1\uD0DC\uB97C \uAC31\uC2E0\uD55C\uB2E4.",
20470
- inputSchema: { type: "object", properties: { alias: aliasParam }, required: ["alias"] },
20343
+ inputSchema: { type: "object", properties: { workspace: workspaceParam }, required: ["workspace"] },
20471
20344
  annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0 \uD655\uC778", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
20472
20345
  }
20473
20346
  ];
20347
+ var pendingConnect;
20348
+ async function connectInBackground(description, onChanged, connect = connectWorkspace) {
20349
+ if (pendingConnect?.outcome) {
20350
+ const { outcome } = pendingConnect;
20351
+ pendingConnect = void 0;
20352
+ return outcome;
20353
+ }
20354
+ if (pendingConnect) {
20355
+ return json({ status: "waiting_for_approval", authorizationUrl: pendingConnect.url, note: "\uC544\uC9C1 \uC2B9\uC778\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911\uC785\uB2C8\uB2E4." });
20356
+ }
20357
+ const state = {};
20358
+ pendingConnect = state;
20359
+ let urlReady = () => {
20360
+ };
20361
+ const gotUrl = new Promise((resolve) => urlReady = resolve);
20362
+ const done = connect({
20363
+ description,
20364
+ onAuthorizationUrl: (url2) => {
20365
+ state.url = url2.href;
20366
+ urlReady();
20367
+ }
20368
+ }).then(
20369
+ async ({ entry, tools }) => {
20370
+ await onChanged().catch(() => {
20371
+ });
20372
+ state.outcome = json({ status: "connected", connected: entry, toolCount: tools.length });
20373
+ },
20374
+ (error2) => {
20375
+ state.outcome = errorResult(error2.message);
20376
+ }
20377
+ );
20378
+ await Promise.race([gotUrl, done]);
20379
+ if (state.outcome) {
20380
+ pendingConnect = void 0;
20381
+ return state.outcome;
20382
+ }
20383
+ return json({
20384
+ status: "waiting_for_approval",
20385
+ authorizationUrl: state.url,
20386
+ note: "\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC774 \uC8FC\uC18C\uB85C Notion\uC5D0 \uB85C\uADF8\uC778\uD574 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB77C \uC2B9\uC778\uD55C \uB4A4, workspace_connect\uB97C \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694."
20387
+ });
20388
+ }
20474
20389
  var text = (value) => ({ content: [{ type: "text", text: value }] });
20475
20390
  var json = (value) => text(JSON.stringify(value, null, 2));
20476
- async function callManagementTool(name, args2, onDisconnected) {
20477
- const alias = typeof args2.alias === "string" ? args2.alias : "";
20391
+ async function callManagementTool(name, args2, onChanged) {
20392
+ const workspace = typeof args2.workspace === "string" ? args2.workspace : "";
20478
20393
  try {
20479
20394
  switch (name) {
20480
20395
  case "workspace_list":
20481
20396
  return json(await loadWorkspaces());
20482
- case "workspace_connect": {
20483
- if (!alias) return errorResult("alias\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
20484
- const { entry, tools } = await connectWorkspace({
20485
- alias,
20486
- description: typeof args2.description === "string" ? args2.description : "",
20487
- onAuthorizationUrl: () => {
20488
- }
20489
- });
20490
- return json({
20491
- connected: entry,
20492
- toolCount: tools.length,
20493
- note: "\uC0C8 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 Notion \uB3C4\uAD6C\uB294 \uC0C8 \uB300\uD654\uBD80\uD130 \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
20494
- });
20495
- }
20397
+ case "workspace_connect":
20398
+ return await connectInBackground(typeof args2.description === "string" ? args2.description : "", onChanged);
20496
20399
  case "workspace_disconnect": {
20497
- if (!alias) return errorResult("alias\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
20498
- const { removed, revoked } = await disconnectWorkspace(alias);
20499
- if (!removed) return errorResult(`\uC5F0\uACB0\uB41C \uC801 \uC5C6\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
20500
- await onDisconnected(alias);
20400
+ if (!workspace) return errorResult("workspace\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
20401
+ const { revoked } = await disconnectWorkspace(workspace);
20402
+ await onChanged(workspace);
20501
20403
  return json({
20502
- disconnected: alias,
20404
+ disconnected: workspace,
20503
20405
  revokedOnNotion: revoked,
20504
20406
  note: revoked ? "\uC774 \uCEF4\uD4E8\uD130\uC758 \uD1A0\uD070\uACFC \uC124\uC815\uC744 \uC9C0\uC6B0\uACE0, Notion \uCABD \uD1A0\uD070\uB3C4 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4." : "\uC774 \uCEF4\uD4E8\uD130\uC758 \uD1A0\uD070\uACFC \uC124\uC815\uC744 \uC9C0\uC6E0\uC2B5\uB2C8\uB2E4. Notion \uCABD \uD1A0\uD070 \uD574\uC81C\uB294 \uC2E4\uD328\uD588\uC73C\uB2C8, Notion \uC124\uC815\uC758 \uC5F0\uACB0(Connections) \uBA54\uB274\uC5D0\uC11C \uD574\uC81C\uD558\uC138\uC694."
20505
20407
  });
20506
20408
  }
20507
- case "workspace_rename": {
20508
- if (!alias) return errorResult("alias\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
20509
- const newAlias = typeof args2.new_alias === "string" ? args2.new_alias : void 0;
20510
- const description = typeof args2.description === "string" ? args2.description : void 0;
20511
- if (newAlias === void 0 && description === void 0) return errorResult("new_alias\uB098 description\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
20512
- const entry = await renameWorkspace(alias, { newAlias, description });
20513
- if (entry.alias !== alias) await onDisconnected(alias);
20514
- return json({
20515
- renamed: entry,
20516
- note: entry.alias === alias ? void 0 : "\uC0C8 \uBCC4\uCE6D\uC740 \uC0C8 \uB300\uD654\uBD80\uD130 workspace \uAC12\uC73C\uB85C \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
20517
- });
20518
- }
20519
20409
  case "workspace_check":
20520
- if (!alias) return errorResult("alias\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
20521
- return await checkWorkspace(alias);
20410
+ if (!workspace) return errorResult("workspace\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
20411
+ return await checkWorkspace(workspace);
20522
20412
  default:
20523
20413
  return void 0;
20524
20414
  }
@@ -20526,81 +20416,177 @@ async function callManagementTool(name, args2, onDisconnected) {
20526
20416
  return errorResult(error2.message);
20527
20417
  }
20528
20418
  }
20529
- async function checkWorkspace(alias) {
20530
- const entry = (await loadWorkspaces()).find((w) => w.alias === alias);
20531
- if (!entry) return errorResult(`\uC5F0\uACB0\uB41C \uC801 \uC5C6\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
20419
+ async function checkWorkspace(name) {
20420
+ const entry = await findWorkspace(name);
20532
20421
  let status;
20533
20422
  let detail;
20534
20423
  try {
20535
- const session = await openWorkspace(alias);
20424
+ const session = await openWorkspace(name);
20536
20425
  try {
20537
20426
  const self = await session.getSelf();
20538
20427
  const tools = await session.listTools();
20539
20428
  const sameWorkspace = self.workspaceId === entry.notionWorkspaceId;
20540
20429
  status = sameWorkspace ? "connected" : "error";
20541
- detail = sameWorkspace ? { workspace: self.workspaceName, toolCount: tools.length } : {
20542
- problem: `\uCC98\uC74C \uC5F0\uACB0\uD55C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4("${entry.notionWorkspaceName}")\uAC00 \uC544\uB2C8\uB77C "${self.workspaceName}"\uC5D0 \uBD99\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC5F0\uACB0\uC744 \uD574\uC81C\uD558\uACE0 \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`
20543
- };
20430
+ detail = sameWorkspace ? { notionName: self.workspaceName, toolCount: tools.length } : { problem: `\uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4("${self.workspaceName}")\uC5D0 \uBD99\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC5F0\uACB0\uC744 \uD574\uC81C\uD558\uACE0 \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.` };
20544
20431
  } finally {
20545
20432
  await session.close();
20546
20433
  }
20547
20434
  } catch (error2) {
20548
20435
  const message = error2.message;
20549
- status = /인증|auth|401|invalid_grant/i.test(message) ? "needs_reauth" : "error";
20436
+ const signInDead = error2 instanceof InvalidGrantError || error2 instanceof UnauthorizedError || /저장된 인증/.test(message);
20437
+ status = signInDead ? "needs_reauth" : "error";
20550
20438
  detail = { problem: message };
20551
20439
  }
20552
- await updateEntry(alias, (w) => ({
20553
- ...w,
20554
- status,
20555
- lastSuccessAt: status === "connected" ? (/* @__PURE__ */ new Date()).toISOString() : w.lastSuccessAt
20556
- }));
20557
- const result = json({ alias, status, ...detail });
20440
+ await updateWorkspaces(
20441
+ (workspaces) => workspaces.map(
20442
+ (w) => w.notionWorkspaceId === entry.notionWorkspaceId ? { ...w, status, lastSuccessAt: status === "connected" ? (/* @__PURE__ */ new Date()).toISOString() : w.lastSuccessAt } : w
20443
+ )
20444
+ );
20445
+ const result = json({ workspace: name, status, ...detail });
20558
20446
  return status === "connected" ? result : { ...result, isError: true };
20559
20447
  }
20560
- async function updateEntry(alias, update) {
20561
- const workspaces = await loadWorkspaces();
20562
- await saveWorkspaces(workspaces.map((w) => w.alias === alias ? update(w) : w));
20448
+
20449
+ // src/upstream.ts
20450
+ import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
20451
+ import { join as join3 } from "node:path";
20452
+ var UpstreamPool = class {
20453
+ constructor(open2 = openWorkspace) {
20454
+ this.open = open2;
20455
+ }
20456
+ open;
20457
+ sessions = /* @__PURE__ */ new Map();
20458
+ session(alias) {
20459
+ const existing = this.sessions.get(alias);
20460
+ if (existing) return existing;
20461
+ const opened = this.open(alias);
20462
+ opened.catch(() => {
20463
+ if (this.sessions.get(alias) === opened) this.sessions.delete(alias);
20464
+ });
20465
+ this.sessions.set(alias, opened);
20466
+ return opened;
20467
+ }
20468
+ /**
20469
+ * An expired upstream MCP session gets a 404 and the request was not processed, so reopen and send it once more.
20470
+ * Other errors may come after the request was processed, so those are never resent.
20471
+ */
20472
+ async callTool(alias, name, args2) {
20473
+ try {
20474
+ return await (await this.session(alias)).callTool(name, args2);
20475
+ } catch (error2) {
20476
+ if (!(error2 instanceof StreamableHTTPError && error2.code === 404)) throw error2;
20477
+ await this.reset(alias);
20478
+ return (await this.session(alias)).callTool(name, args2);
20479
+ }
20480
+ }
20481
+ async reset(alias) {
20482
+ const pending = this.sessions.get(alias);
20483
+ this.sessions.delete(alias);
20484
+ await pending?.then((s) => s.close()).catch(() => {
20485
+ });
20486
+ }
20487
+ async closeAll() {
20488
+ await Promise.all([...this.sessions.keys()].map((alias) => this.reset(alias)));
20489
+ }
20490
+ };
20491
+ var LIVE_TIMEOUT_MS = 15e3;
20492
+ async function loadCatalogs(workspaces, pool) {
20493
+ const results = await Promise.all(
20494
+ workspaces.map(async (w) => {
20495
+ try {
20496
+ const catalog = await withTimeout(fetchLive(w.name, pool), LIVE_TIMEOUT_MS);
20497
+ await writeCache(catalog, w.notionWorkspaceId);
20498
+ return catalog;
20499
+ } catch (error2) {
20500
+ const cached2 = await readCache(w.notionWorkspaceId);
20501
+ return cached2 ? { ...cached2, alias: w.name } : { alias: w.name, reason: error2.message };
20502
+ }
20503
+ })
20504
+ );
20505
+ return {
20506
+ catalogs: results.filter((r) => "tools" in r),
20507
+ failures: results.filter((r) => "reason" in r)
20508
+ };
20509
+ }
20510
+ async function fetchLive(alias, pool) {
20511
+ const session = await pool.session(alias);
20512
+ return {
20513
+ alias,
20514
+ tools: await session.listTools(),
20515
+ instructions: session.instructions
20516
+ };
20517
+ }
20518
+ async function writeCache(catalog, workspaceId) {
20519
+ await mkdir3(join3(configDir(), "cache"), { recursive: true, mode: 448 });
20520
+ const tmp = `${cachePath(workspaceId)}.${process.pid}.tmp`;
20521
+ await writeFile2(tmp, JSON.stringify(catalog), { mode: 384 });
20522
+ await rename2(tmp, cachePath(workspaceId));
20523
+ }
20524
+ async function readCache(workspaceId) {
20525
+ try {
20526
+ return JSON.parse(await readFile2(cachePath(workspaceId), "utf8"));
20527
+ } catch {
20528
+ return void 0;
20529
+ }
20530
+ }
20531
+ function withTimeout(promise, ms) {
20532
+ let timer;
20533
+ return Promise.race([
20534
+ promise,
20535
+ new Promise((_, reject) => {
20536
+ timer = setTimeout(() => reject(new Error(`${ms / 1e3}\uCD08 \uC548\uC5D0 \uC751\uB2F5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`)), ms);
20537
+ })
20538
+ ]).finally(() => clearTimeout(timer));
20563
20539
  }
20564
20540
 
20565
20541
  // src/server.ts
20566
20542
  var log = (message) => console.error(`[${SERVER_NAME}] ${message}`);
20567
20543
  async function createServer2() {
20568
- const workspaces = await loadWorkspaces();
20569
20544
  const pool = new UpstreamPool();
20545
+ const workspaces = await loadWorkspaces();
20570
20546
  const { catalogs, failures } = await loadCatalogs(workspaces, pool);
20571
- for (const f of failures) log(`${f.alias}: \uB3C4\uAD6C \uBAA9\uB85D\uC744 \uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${f.reason})`);
20572
- const catalog = mergeCatalogs(catalogs, log);
20573
- const searchAll = searchAllTool(catalog);
20547
+ let current = toolsFor(catalogs, failures);
20574
20548
  const server = new Server(
20575
20549
  { name: SERVER_NAME, version: SERVER_VERSION },
20576
- { capabilities: { tools: {} }, instructions: buildInstructions(workspaces, catalogs, failures) }
20550
+ { capabilities: { tools: { listChanged: true } }, instructions: buildInstructions(workspaces, catalogs, failures) }
20577
20551
  );
20552
+ const refresh = async (staleAlias) => {
20553
+ if (staleAlias) await pool.reset(staleAlias);
20554
+ const latest = await loadCatalogs(await loadWorkspaces(), pool);
20555
+ current = toolsFor(latest.catalogs, latest.failures);
20556
+ await server.sendToolListChanged();
20557
+ };
20578
20558
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
20579
- tools: [...managementTools, ...searchAll ? [searchAll] : [], ...catalog.tools]
20559
+ tools: [...managementTools, ...current.searchAll ? [current.searchAll] : [], ...current.catalog.tools]
20580
20560
  }));
20581
20561
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
20582
20562
  const { name, arguments: args2 } = request.params;
20563
+ const { catalog, searchAll } = current;
20583
20564
  const call = (alias, tool, rest) => pool.callTool(alias, tool, rest);
20584
20565
  if (searchAll && name === SEARCH_ALL) return callSearchAll(catalog, args2, call);
20585
- const managed = await callManagementTool(name, args2 ?? {}, (alias) => pool.reset(alias));
20566
+ const managed = await callManagementTool(name, args2 ?? {}, refresh);
20586
20567
  return managed ?? callProxiedTool(catalog, name, args2, call);
20587
20568
  });
20588
20569
  server.onclose = () => void pool.closeAll();
20589
20570
  return server;
20590
20571
  }
20572
+ function toolsFor(catalogs, failures) {
20573
+ for (const f of failures) log(`${f.alias}: \uB3C4\uAD6C \uBAA9\uB85D\uC744 \uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${f.reason})`);
20574
+ const catalog = mergeCatalogs(catalogs, log);
20575
+ return { catalog, searchAll: searchAllTool(catalog) };
20576
+ }
20591
20577
  function buildInstructions(workspaces, catalogs, failures) {
20592
20578
  const failed = new Map(failures.map((f) => [f.alias, f.reason]));
20593
20579
  const lines = workspaces.length === 0 ? ["- (\uC5C6\uC74C) workspace_connect \uB3C4\uAD6C\uB85C \uBA3C\uC800 \uC5F0\uACB0\uD574\uC57C \uD55C\uB2E4."] : workspaces.map((w) => {
20594
- const label = `- ${w.alias}: Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 "${w.notionWorkspaceName}"${w.description ? ` (${w.description})` : ""}`;
20595
- return failed.has(w.alias) ? `${label} - \uC9C0\uAE08 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC74C: ${failed.get(w.alias)}` : label;
20580
+ const label = `- "${w.name}"${w.description ? `: ${w.description}` : ""}`;
20581
+ return failed.has(w.name) ? `${label} - \uC9C0\uAE08 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC74C: ${failed.get(w.name)}` : label;
20596
20582
  });
20597
20583
  const sections = [
20598
20584
  [
20599
20585
  "\uC774 \uC11C\uBC84(Multi-Workspace for Notion)\uB294 \uC5EC\uB7EC Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uB3D9\uC2DC\uC5D0 \uC5F0\uACB0\uD574 \uB454\uB2E4.",
20600
- `\uBAA8\uB4E0 Notion \uB3C4\uAD6C\uC5D0\uB294 ${WORKSPACE_PARAM} \uD30C\uB77C\uBBF8\uD130\uAC00 \uC788\uACE0, \uD638\uCD9C\uB9C8\uB2E4 \uB300\uC0C1 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uBCC4\uCE6D\uC744 \uB123\uC5B4\uC57C \uD55C\uB2E4.`,
20586
+ `\uBAA8\uB4E0 Notion \uB3C4\uAD6C\uC5D0\uB294 ${WORKSPACE_PARAM} \uD30C\uB77C\uBBF8\uD130\uAC00 \uC788\uACE0, \uD638\uCD9C\uB9C8\uB2E4 \uB300\uC0C1 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984(\uC544\uB798 \uBAA9\uB85D\uC758 \uC774\uB984 \uADF8\uB300\uB85C)\uC744 \uB123\uC5B4\uC57C \uD55C\uB2E4.`,
20601
20587
  "\uC0AC\uC6A9\uC790\uAC00 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uB9D0\uD558\uC9C0 \uC54A\uC558\uACE0 \uB9E5\uB77D\uC73C\uB85C\uB3C4 \uC54C \uC218 \uC5C6\uC744 \uB54C, \uC4F0\uAE30\xB7\uC218\uC815\xB7\uC0AD\uC81C\uB294 \uC2E4\uD589\uD558\uAE30 \uC804\uC5D0 \uC5B4\uB290 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC778\uC9C0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3B\uB294\uB2E4.",
20602
20588
  `\uC5EC\uB7EC \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uD55C\uAEBC\uBC88\uC5D0 \uCC3E\uC744 \uB54C\uB294 ${SEARCH_ALL} \uB3C4\uAD6C\uB97C \uC4F4\uB2E4(\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uB458 \uC774\uC0C1\uC77C \uB54C\uB9CC \uC788\uB2E4).`,
20603
- "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uAD00\uB9AC\uB294 workspace_list, workspace_connect, workspace_disconnect, workspace_rename, workspace_check \uB3C4\uAD6C\uB85C \uD55C\uB2E4.",
20589
+ "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uAD00\uB9AC\uB294 workspace_list, workspace_connect, workspace_disconnect, workspace_check \uB3C4\uAD6C\uB85C \uD55C\uB2E4.",
20604
20590
  "",
20605
20591
  "\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4:",
20606
20592
  ...lines
@@ -20627,10 +20613,10 @@ var [command, ...args] = process.argv.slice(2);
20627
20613
  var USAGE = `\uC0AC\uC6A9\uBC95:
20628
20614
  multi-workspace-for-notion MCP \uC11C\uBC84(stdio)\uB85C \uC2E4\uD589
20629
20615
  multi-workspace-for-notion list \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBAA9\uB85D
20630
- multi-workspace-for-notion add <\uBCC4\uCE6D> [\uC124\uBA85...] \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0(\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D)
20631
- multi-workspace-for-notion tools <\uBCC4\uCE6D> \uC800\uC7A5\uB41C \uC778\uC99D\uC73C\uB85C \uBD99\uC5B4\uC11C \uD655\uC778\uD558\uACE0 \uB3C4\uAD6C \uBAA9\uB85D \uCD9C\uB825
20632
- multi-workspace-for-notion rename <\uBCC4\uCE6D> <\uC0C8 \uBCC4\uCE6D> \uBCC4\uCE6D \uBC14\uAFB8\uAE30(\uB2E4\uC2DC \uB85C\uADF8\uC778\uD558\uC9C0 \uC54A\uC74C)
20633
- multi-workspace-for-notion remove <\uBCC4\uCE6D> \uC5F0\uACB0 \uD574\uC81C\uC640 \uD1A0\uD070 \uC0AD\uC81C`;
20616
+ multi-workspace-for-notion add [\uC124\uBA85...] \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0(\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uACE0\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984\uC73C\uB85C \uC5F0\uACB0)
20617
+ multi-workspace-for-notion tools <\uC774\uB984> \uC800\uC7A5\uB41C \uC778\uC99D\uC73C\uB85C \uBD99\uC5B4\uC11C \uD655\uC778\uD558\uACE0 \uB3C4\uAD6C \uBAA9\uB85D \uCD9C\uB825
20618
+ multi-workspace-for-notion remove <\uC774\uB984> \uC5F0\uACB0 \uD574\uC81C\uC640 \uD1A0\uD070 \uC0AD\uC81C
20619
+ (\uC774\uB984\uC5D0 \uACF5\uBC31\uC774 \uC788\uC73C\uBA74 \uB530\uC634\uD45C\uB85C \uAC10\uC2FC\uB2E4: remove "\uD5C8 \uC740\uC11C\uC758 Notion")`;
20634
20620
  try {
20635
20621
  switch (command) {
20636
20622
  case void 0: {
@@ -20644,7 +20630,7 @@ try {
20644
20630
  console.log("\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
20645
20631
  }
20646
20632
  for (const w of workspaces) {
20647
- console.log(`${w.alias} ${w.notionWorkspaceName} ${w.status} ${w.description}`);
20633
+ console.log(`${w.name} ${w.status} ${w.description}`);
20648
20634
  }
20649
20635
  break;
20650
20636
  }
@@ -20653,11 +20639,8 @@ try {
20653
20639
  break;
20654
20640
  }
20655
20641
  case "add": {
20656
- const [alias, ...descriptionWords] = args;
20657
- if (!alias) throw new UsageError();
20658
20642
  const { entry, tools } = await connectWorkspace({
20659
- alias,
20660
- description: descriptionWords.join(" "),
20643
+ description: args.join(" "),
20661
20644
  onAuthorizationUrl: (url2) => console.log(
20662
20645
  `\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C Notion \uC778\uC99D \uD654\uBA74\uC744 \uC5FD\uB2C8\uB2E4. \uC5F0\uACB0\uD560 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB77C \uC2B9\uC778\uD558\uC138\uC694.
20663
20646
  \uBE0C\uB77C\uC6B0\uC800\uAC00 \uC5F4\uB9AC\uC9C0 \uC54A\uC73C\uBA74 \uC774 \uC8FC\uC18C\uB85C \uB4E4\uC5B4\uAC00\uC138\uC694:
@@ -20665,38 +20648,30 @@ ${url2.href}
20665
20648
  `
20666
20649
  )
20667
20650
  });
20668
- console.log(`\uC5F0\uACB0\uD588\uC2B5\uB2C8\uB2E4: ${entry.alias} \u2192 "${entry.notionWorkspaceName}" (${entry.notionWorkspaceId})`);
20651
+ console.log(`\uC5F0\uACB0\uD588\uC2B5\uB2C8\uB2E4: "${entry.name}" (${entry.notionWorkspaceId})`);
20669
20652
  console.log(`\uACF5\uC2DD \uB3C4\uAD6C ${tools.length}\uAC1C\uB97C \uBC1B\uC558\uC2B5\uB2C8\uB2E4.`);
20670
20653
  break;
20671
20654
  }
20672
20655
  case "tools": {
20673
- const [alias] = args;
20674
- if (!alias) throw new UsageError();
20675
- const session = await openWorkspace(alias);
20656
+ const [name] = args;
20657
+ if (!name) throw new UsageError();
20658
+ const session = await openWorkspace(name);
20676
20659
  try {
20677
20660
  const self = await session.getSelf();
20678
20661
  const tools = await session.listTools();
20679
- console.log(`${alias} \u2192 "${self.workspaceName}" (${self.workspaceId}), \uB3C4\uAD6C ${tools.length}\uAC1C`);
20662
+ console.log(`"${name}" \u2192 Notion "${self.workspaceName}" (${self.workspaceId}), \uB3C4\uAD6C ${tools.length}\uAC1C`);
20680
20663
  for (const tool of tools) console.log(` ${tool.name}`);
20681
20664
  } finally {
20682
20665
  await session.close();
20683
20666
  }
20684
20667
  break;
20685
20668
  }
20686
- case "rename": {
20687
- const [alias, newAlias] = args;
20688
- if (!alias || !newAlias) throw new UsageError();
20689
- await renameWorkspace(alias, { newAlias });
20690
- console.log(`\uBCC4\uCE6D\uC744 \uBC14\uAFE8\uC2B5\uB2C8\uB2E4: ${alias} \u2192 ${newAlias}`);
20691
- break;
20692
- }
20693
20669
  case "remove": {
20694
- const [alias] = args;
20695
- if (!alias) throw new UsageError();
20696
- const { removed, revoked } = await disconnectWorkspace(alias);
20697
- if (!removed) console.log(`\uC5F0\uACB0\uB41C \uC801 \uC5C6\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
20698
- else if (revoked) console.log(`\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4: ${alias} (Notion \uCABD \uD1A0\uD070\uB3C4 \uD574\uC81C)`);
20699
- else console.log(`\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4: ${alias}
20670
+ const [name] = args;
20671
+ if (!name) throw new UsageError();
20672
+ const { revoked } = await disconnectWorkspace(name);
20673
+ if (revoked) console.log(`\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4: ${name} (Notion \uCABD \uD1A0\uD070\uB3C4 \uD574\uC81C)`);
20674
+ else console.log(`\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4: ${name}
20700
20675
  Notion \uCABD \uD1A0\uD070 \uD574\uC81C\uB294 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. Notion \uC124\uC815\uC758 \uC5F0\uACB0(Connections) \uBA54\uB274\uC5D0\uC11C \uD574\uC81C\uD558\uC138\uC694.`);
20701
20676
  break;
20702
20677
  }
@@ -20715,8 +20690,8 @@ ${USAGE}` : USAGE);
20715
20690
  process.exitCode = 1;
20716
20691
  }
20717
20692
  function sessionStatus(workspaces) {
20718
- if (workspaces.length === 0) return "Notion: \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. /multi-workspace:workspace-add <\uBCC4\uCE6D> \uC73C\uB85C \uC5F0\uACB0\uD558\uC138\uC694.";
20719
- const names = workspaces.map((w) => `${w.alias}(${w.notionWorkspaceName})`).join(", ");
20720
- const broken = workspaces.filter((w) => w.status !== "connected").map((w) => w.alias);
20693
+ if (workspaces.length === 0) return "Notion: \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. /multi-workspace:workspace-add \uB85C \uC5F0\uACB0\uD558\uC138\uC694.";
20694
+ const names = workspaces.map((w) => w.name).join(", ");
20695
+ const broken = workspaces.filter((w) => w.status !== "connected").map((w) => w.name);
20721
20696
  return broken.length === 0 ? `Notion: ${names}` : `Notion: ${names} \xB7 \uD655\uC778 \uD544\uC694: ${broken.join(", ")} \u2192 /multi-workspace:workspace-check`;
20722
20697
  }