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/README.ko.md +29 -32
- package/README.md +29 -32
- package/package.json +3 -2
- package/plugin/server.mjs +424 -449
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 ??
|
|
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
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
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
|
|
15743
|
+
await mkdir2(configDir(), { recursive: true, mode: 448 });
|
|
15630
15744
|
const tmp = `${configPath()}.${process.pid}.tmp`;
|
|
15631
|
-
const data = { version:
|
|
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
|
|
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.
|
|
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
|
|
19291
|
-
|
|
19292
|
-
|
|
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
|
|
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
|
|
19373
|
+
/** The keychain value was seen or saved. If it disappears later, the workspace was disconnected. */
|
|
19362
19374
|
persisted = false;
|
|
19363
|
-
/**
|
|
19364
|
-
|
|
19365
|
-
|
|
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(
|
|
19369
|
-
const raw = await keychain.get(credentialAccount(
|
|
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(
|
|
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
|
|
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 \
|
|
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(
|
|
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
|
-
|
|
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
|
-
`"${
|
|
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
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19629
|
-
|
|
19630
|
-
|
|
19631
|
-
|
|
19632
|
-
|
|
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
|
|
19643
|
-
const
|
|
19644
|
-
if (!
|
|
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(
|
|
19648
|
-
|
|
19649
|
-
const
|
|
19650
|
-
|
|
19651
|
-
|
|
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
|
|
19656
|
-
|
|
19657
|
-
await
|
|
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
|
-
|
|
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
|
|
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 \
|
|
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\
|
|
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: {
|
|
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: {
|
|
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,
|
|
20477
|
-
const
|
|
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
|
-
|
|
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 (!
|
|
20498
|
-
const {
|
|
20499
|
-
|
|
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:
|
|
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 (!
|
|
20521
|
-
return await checkWorkspace(
|
|
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(
|
|
20530
|
-
const entry =
|
|
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(
|
|
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 ? {
|
|
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
|
-
|
|
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
|
|
20553
|
-
|
|
20554
|
-
|
|
20555
|
-
|
|
20556
|
-
|
|
20557
|
-
const result = json({
|
|
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
|
-
|
|
20561
|
-
|
|
20562
|
-
|
|
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
|
-
|
|
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 ?? {},
|
|
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 = `-
|
|
20595
|
-
return failed.has(w.
|
|
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 \
|
|
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,
|
|
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
|
|
20631
|
-
multi-workspace-for-notion tools <\
|
|
20632
|
-
multi-workspace-for-notion
|
|
20633
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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 [
|
|
20674
|
-
if (!
|
|
20675
|
-
const session = await openWorkspace(
|
|
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(
|
|
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 [
|
|
20695
|
-
if (!
|
|
20696
|
-
const {
|
|
20697
|
-
if (
|
|
20698
|
-
else
|
|
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
|
|
20719
|
-
const names = workspaces.map((w) =>
|
|
20720
|
-
const broken = workspaces.filter((w) => w.status !== "connected").map((w) => w.
|
|
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
|
}
|