diff --git a/dist/cli.js b/dist/cli.js index 7404cd7..59faeeb 100644 --- a/dist/cli.js +++ b/dist/cli.js @@ -64,14 +64,25 @@ var DiscoveryStore = class { constructor(configDir = platformConfigDir()) { this.filePath = path.join(configDir, "discovery.json"); } + /** + * @param account - 稳定账户标识。 + * @returns 账户的缓存发现状态;不存在时返回 `undefined`。 + */ async get(account) { return (await this.read()).entries[account]; } + /** + * 使用原子文件替换持久化账户的发现状态。 + * + * @param account - 稳定账户标识。 + * @param state - MCP OAuth 客户端提供的发现状态。 + */ async set(account, state) { const document = await this.read(); document.entries[account] = state; await atomicWriteJson(this.filePath, document); } + /** @param account - 要删除其状态的稳定账户标识。 */ async delete(account) { const document = await this.read(); if (!(account in document.entries)) return; @@ -392,6 +403,12 @@ function safeCommandError(result) { return (result.stderr || result.stdout || `exit code ${result.code}`).trim(); } var SpawnCommandRunner = class { + /** + * @param command - 可执行文件名称或路径。 + * @param args - 不经 shell 解释而传递的原始参数列表。 + * @returns 捕获的退出码、标准输出与标准错误。 + * @throws 子进程无法启动时抛出。 + */ run(command, args) { return new Promise((resolve, reject) => { const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] }); @@ -443,6 +460,12 @@ var OAuthCallbackServer = class { this.resolveResult = resolve; this.rejectResult = reject; }); + /** + * 在配置的固定回调端口监听 `127.0.0.1`。 + * + * @returns 端口绑定完成后解析的 Promise。 + * @throws 服务已运行或端口无法绑定时抛出。 + */ async listen() { if (this.server) throw new Error("OAuth callback server is already running"); this.server = createServer((request, response) => { @@ -482,6 +505,13 @@ var OAuthCallbackServer = class { const address = this.server.address(); if (!address || address.port !== this.port) throw new Error(`OAuth callback server did not bind port ${this.port}`); } + /** + * 等待有效回调或达到配置的超时时间。 + * + * @param timeoutMs - 最大等待时间(毫秒)。 + * @returns 经过验证的回调中的授权码。 + * @throws 授权失败、状态验证失败、缺少授权码或等待超时时抛出。 + */ async wait(timeoutMs = 10 * 60 * 1e3) { let timeout; try { @@ -495,6 +525,11 @@ var OAuthCallbackServer = class { if (timeout) clearTimeout(timeout); } } + /** + * 停止接收回调;对已停止的服务调用此方法不会产生任何操作。 + * + * @returns HTTP 服务关闭后解析的 Promise。 + */ async close() { if (!this.server) return; const server = this.server; @@ -506,6 +541,13 @@ var OAuthCallbackServer = class { // src/oauthProvider.ts import { randomBytes, timingSafeEqual } from "node:crypto"; var CraftTableOAuthProvider = class { + /** + * @param options - 远端端点与已注册公共客户端设置。 + * @param tokenStore - OAuth 令牌安全持久化接口。 + * @param discoveryStore - 授权服务器发现状态缓存。 + * @param onRedirect - 打开或显示授权 URL 的处理器。 + * @param state - 预期回调状态,可注入以实现确定性测试。 + */ constructor(options, tokenStore, discoveryStore, onRedirect, state = randomBytes(32).toString("base64url")) { this.options = options; this.tokenStore = tokenStore; @@ -532,40 +574,63 @@ var CraftTableOAuthProvider = class { account; expectedState; codeVerifierValue; + /** @returns 授权回调必须携带的 state 值。 */ state() { return this.expectedState; } + /** + * 在长度一致时以常量时间比较回调 state。 + * + * @param value - 从 loopback 回调收到的 state。 + * @returns 回调是否属于本次授权尝试。 + */ validateState(value) { if (!value) return false; const expected = Buffer.from(this.expectedState); const actual = Buffer.from(value); return expected.length === actual.length && timingSafeEqual(expected, actual); } + /** @returns MCP SDK 使用的静态公共客户端注册信息。 */ clientInformation() { return { client_id: this.options.clientId }; } + /** @returns 当前服务/客户端组合已保存的 OAuth 令牌。 */ tokens() { return this.tokenStore.get(this.account); } + /** 持久化刷新或新签发的 OAuth 令牌集。 */ saveTokens(tokens) { return this.tokenStore.set(this.account, tokens); } + /** 将授权 URL 交给 CLI 的浏览器或手动登录处理器。 */ redirectToAuthorization(url) { return this.onRedirect(url); } + /** 在本次登录尝试期间将 PKCE verifier 保存在内存中。 */ saveCodeVerifier(codeVerifier) { this.codeVerifierValue = codeVerifier; } + /** + * @returns 为当前授权尝试保存的 PKCE verifier。 + * @throws 尚未生成 verifier 或其已失效时抛出。 + */ codeVerifier() { if (!this.codeVerifierValue) throw new Error("OAuth PKCE verifier is missing or expired"); return this.codeVerifierValue; } + /** @returns 当前服务/客户端组合的 OAuth 发现缓存。 */ discoveryState() { return this.discoveryStore.get(this.account); } + /** 在凭据存储之外持久化 OAuth 发现状态。 */ saveDiscoveryState(state) { return this.discoveryStore.set(this.account, state); } + /** + * 使 MCP OAuth 客户端指定的凭据材料失效。 + * + * @param scope - 要清理的凭据类别。 + */ async invalidateCredentials(scope) { if (scope === "all" || scope === "tokens") await this.tokenStore.delete(this.account); if (scope === "all" || scope === "discovery") await this.discoveryStore.delete(this.account); @@ -637,14 +702,19 @@ ${url.toString()}`); } async function logoutRemote(options, tokenStore, discoveryStore, localOnly, fetchFn = fetch) { const account = credentialAccount(options); + if (localOnly) { + const hadCredential = await tokenStore.delete(account); + await discoveryStore.delete(account); + return { hadCredential, revoked: false }; + } const tokens = await tokenStore.get(account); - if (tokens && !localOnly) { + if (tokens) { const endpoint = await discoverRevocationEndpoint(options.url, fetchFn); await revokeTokens(endpoint, options.clientId, tokens, fetchFn); } await tokenStore.delete(account); await discoveryStore.delete(account); - return { hadCredential: Boolean(tokens), revoked: Boolean(tokens && !localOnly) }; + return { hadCredential: Boolean(tokens), revoked: Boolean(tokens) }; } async function connectWithTransport(options, transport, authentication) { const client = new Client({ name: "crafttable-mcp-cli", version: "0.1.3" }); @@ -760,6 +830,11 @@ var KeyringTokenStore = class { this.entryFactory = entryFactory; } entryFactory; + /** + * @param account - 稳定凭据账户标识。 + * @returns 解码后的令牌集;不存在凭据时返回 `undefined`。 + * @throws 凭据存储不可用或已存数据无效时抛出。 + */ async get(account) { let serialized; try { @@ -787,6 +862,13 @@ var KeyringTokenStore = class { throw invalidCredentialError(); } } + /** + * 原子发布新一代分块,并删除上一代分块。 + * + * @param account - 稳定凭据账户标识。 + * @param tokens - 要安全存储的 OAuth 令牌。 + * @throws 任一凭据存储操作失败时抛出。 + */ async set(account, tokens) { const encoded = Buffer.from(JSON.stringify(tokens), "utf8").toString("base64"); const chunks = splitCredential(encoded); @@ -811,6 +893,12 @@ var KeyringTokenStore = class { await Promise.allSettled(Array.from({ length: previousManifest.chunks }, (_, index) => this.deletePassword(chunkAccount(account, previousManifest.generation, index)))); } } + /** + * 删除账户清单及其引用的全部分块。 + * + * @param account - 稳定凭据账户标识。 + * @throws 无法访问凭据存储时抛出。 + */ async delete(account) { try { const serialized = await this.readPassword(account); @@ -819,13 +907,14 @@ var KeyringTokenStore = class { if (manifest) { await Promise.all(Array.from({ length: manifest.chunks }, (_, index) => this.deletePassword(chunkAccount(account, manifest.generation, index)))); } + return serialized !== void 0; } catch (error) { throw keyringError(error); } } async readPassword(account) { try { - return await (await this.entryFactory(account)).getPassword(); + return await (await this.entryFactory(account)).getPassword() ?? void 0; } catch (error) { if (isMissingCredential(error)) return void 0; throw error; @@ -924,7 +1013,7 @@ withConnection(program.command("serve").description("Run the local stdio bridge" for (const operation of ["configure", "unconfigure"]) { withConnection(program.command(`${operation} `).description(`${operation === "configure" ? "Add" : "Remove"} the stdio bridge and CraftTable Skill in Codex, Claude Code, or OpenCode`)).option("--dry-run", "show the planned changes without writing").option("--force", "replace a conflicting entry without prompting").action(async (agent, flags) => { const cliEntry = fileURLToPath(import.meta.url); - if (!cliEntry.endsWith(".js")) throw new Error("Agent configuration requires the built CLI; run `npm --prefix apps/mcp-client run build` first"); + if (!cliEntry.endsWith(".js")) throw new Error("Agent configuration requires the built CLI; run `npm --prefix mcp-client run build` first"); const input = { target: agent, options: connectionOptions(flags), diff --git a/dist/cli.js.map b/dist/cli.js.map index 861f4ab..aaba7c7 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/cli.ts", "../src/agents.ts", "../src/config.ts", "../src/bridge.ts", "../src/remote.ts", "../src/callbackServer.ts", "../src/oauthProvider.ts", "../src/credentials.ts"], - "sourcesContent": ["#!/usr/bin/env node\n\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { configureAgents, type AgentTarget, unconfigureAgents } from \"./agents.js\";\nimport { serveBridge } from \"./bridge.js\";\nimport { type ClientOptions, credentialAccount, DiscoveryStore, resolveClientOptions } from \"./config.js\";\nimport { KeyringTokenStore } from \"./credentials.js\";\nimport { connectRemote, loginRemote, logoutRemote } from \"./remote.js\";\n\ntype ConnectionFlags = { url?: string; clientId?: string; callbackPort?: string };\n\nconst program = new Command()\n .name(\"crafttable-mcp\")\n .description(\"OAuth client and stdio bridge for CraftTable MCP\")\n .version(\"0.1.3\");\n\nwithConnection(program.command(\"login\").description(\"Log in through the system browser\"))\n .option(\"--no-browser\", \"print the authorization URL instead of opening it\")\n .option(\"--timeout \", \"OAuth callback timeout\", \"600000\")\n .action(async (flags: ConnectionFlags & { browser: boolean; timeout: string }) => {\n const options = connectionOptions(flags);\n const timeoutMs = positiveInteger(flags.timeout, \"OAuth callback timeout\");\n const result = await loginRemote(options, new KeyringTokenStore(), new DiscoveryStore(), { browser: flags.browser, timeoutMs });\n printJson({ loggedIn: true, alreadyAuthenticated: result.alreadyAuthenticated, server: options.url.toString(), tools: result.toolCount });\n });\n\nwithConnection(program.command(\"status\").description(\"Check saved login state and remote MCP connectivity\"))\n .action(async (flags: ConnectionFlags) => {\n const options = connectionOptions(flags);\n const tokens = await new KeyringTokenStore().get(credentialAccount(options));\n const connection = await connectRemote(options, new KeyringTokenStore(), new DiscoveryStore());\n try {\n const tools = await connection.client.listTools();\n printJson({\n loggedIn: Boolean(tokens),\n authentication: connection.authentication,\n connected: true,\n server: options.url.toString(),\n tools: tools.tools.length,\n });\n } finally {\n await connection.close();\n }\n });\n\nwithConnection(program.command(\"logout\").description(\"Revoke OAuth tokens and remove the local credential\"))\n .option(\"--local-only\", \"remove local credentials without contacting the authorization server\")\n .action(async (flags: ConnectionFlags & { localOnly?: boolean }) => {\n const options = connectionOptions(flags);\n const result = await logoutRemote(options, new KeyringTokenStore(), new DiscoveryStore(), Boolean(flags.localOnly));\n printJson({ loggedIn: false, credentialRemoved: result.hadCredential, revoked: result.revoked, localOnly: Boolean(flags.localOnly) });\n });\n\nwithConnection(program.command(\"tools\").description(\"List remote MCP tools\"))\n .action(async (flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => printJson(await client.listTools())));\n\nwithConnection(program.command(\"resources\").description(\"List remote MCP resources and resource templates\"))\n .action(async (flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => printJson({\n resources: (await client.listResources()).resources,\n resourceTemplates: (await client.listResourceTemplates()).resourceTemplates,\n })));\n\nwithConnection(program.command(\"read \").description(\"Read an MCP resource\"))\n .action(async (uri: string, flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => printJson(await client.readResource({ uri }))));\n\nwithConnection(program.command(\"call [json]\").description(\"Call an MCP tool\"))\n .action(async (tool: string, json: string | undefined, flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => {\n printJson(await client.callTool({ name: tool, arguments: parseObject(json ?? \"{}\") }));\n }));\n\nwithConnection(program.command(\"serve\").description(\"Run the local stdio bridge\"))\n .action(async (flags: ConnectionFlags) => {\n await serveBridge(connectionOptions(flags), new KeyringTokenStore(), new DiscoveryStore());\n });\n\nfor (const operation of [\"configure\", \"unconfigure\"] as const) {\n withConnection(program.command(`${operation} `).description(`${operation === \"configure\" ? \"Add\" : \"Remove\"} the stdio bridge and CraftTable Skill in Codex, Claude Code, or OpenCode`))\n .option(\"--dry-run\", \"show the planned changes without writing\")\n .option(\"--force\", \"replace a conflicting entry without prompting\")\n .action(async (agent: AgentTarget, flags: ConnectionFlags & { dryRun?: boolean; force?: boolean }) => {\n const cliEntry = fileURLToPath(import.meta.url);\n if (!cliEntry.endsWith(\".js\")) throw new Error(\"Agent configuration requires the built CLI; run `npm --prefix apps/mcp-client run build` first\");\n const input = {\n target: agent,\n options: connectionOptions(flags),\n cliEntry,\n dryRun: Boolean(flags.dryRun),\n force: Boolean(flags.force),\n };\n const result = operation === \"configure\" ? await configureAgents(input) : await unconfigureAgents(input);\n printJson({ result });\n });\n}\n\nfunction withConnection(command: Command): Command {\n return command\n .option(\"--url \", \"MCP Streamable HTTP URL\")\n .option(\"--client-id \", \"pre-registered OAuth public client ID\")\n .option(\"--callback-port \", \"fixed localhost OAuth callback port\");\n}\n\nfunction connectionOptions(flags: ConnectionFlags): ClientOptions {\n return resolveClientOptions(flags);\n}\n\nasync function withClient(options: ClientOptions, action: (client: Awaited>[\"client\"]) => Promise): Promise {\n const connection = await connectRemote(options, new KeyringTokenStore(), new DiscoveryStore());\n try {\n await action(connection.client);\n } finally {\n await connection.close();\n }\n}\n\nfunction parseObject(raw: string): Record {\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch {\n throw new Error(\"Tool arguments must be a JSON object\");\n }\n if (!value || Array.isArray(value) || typeof value !== \"object\") throw new Error(\"Tool arguments must be a JSON object\");\n return value as Record;\n}\n\nfunction positiveInteger(value: string, name: string): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${name} must be a positive integer`);\n return parsed;\n}\n\nfunction printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\n}\n\nprogram.parseAsync().catch((error: unknown) => {\n process.stderr.write(`CraftTable MCP failed: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n});\n", "import { randomUUID } from \"node:crypto\";\nimport { constants } from \"node:fs\";\nimport { copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport { applyEdits, modify, parse } from \"jsonc-parser\";\nimport spawn from \"cross-spawn\";\nimport type { ClientOptions } from \"./config.js\";\nimport { errorMessage } from \"./config.js\";\n\nexport const SERVER_NAME = \"crafttable\";\nexport type AgentName = \"codex\" | \"claude\" | \"opencode\";\nexport type AgentTarget = AgentName | \"all\";\nexport type SkillAction = \"installed\" | \"replaced\" | \"removed\" | \"unchanged\" | \"absent\" | \"preserved\" | \"would-install\" | \"would-replace\" | \"would-remove\" | \"would-preserve\";\n\nexport type CommandResult = { code: number; stdout: string; stderr: string };\nexport interface CommandRunner {\n run(command: string, args: string[]): Promise;\n}\n\nexport type ConfigureInput = {\n target: AgentTarget;\n options: ClientOptions;\n cliEntry: string;\n nodePath?: string;\n dryRun?: boolean;\n force?: boolean;\n env?: NodeJS.ProcessEnv;\n runner?: CommandRunner;\n confirm?: (message: string) => Promise;\n opencodePath?: string;\n skillSource?: string;\n skillPaths?: Partial>;\n};\n\nexport type ConfigureResult = {\n agent: AgentName;\n action: \"added\" | \"replaced\" | \"removed\" | \"unchanged\" | \"absent\" | \"would-add\" | \"would-replace\" | \"would-remove\";\n skillAction: SkillAction;\n};\n\nexport async function configureAgents(input: ConfigureInput): Promise {\n const agents = expandTarget(input.target);\n const command = launchCommand(input.options, input.cliEntry, input.nodePath ?? process.execPath);\n const runner = input.runner ?? new SpawnCommandRunner();\n const results: ConfigureResult[] = [];\n for (const agent of agents) {\n const mcpResult = agent === \"opencode\"\n ? await configureOpenCode(input, command)\n : await configureCliAgent(agent, command, input, runner);\n const skillAction = await configureAgentSkill(agent, input);\n results.push({ ...mcpResult, skillAction });\n }\n return results;\n}\n\nexport async function unconfigureAgents(input: ConfigureInput): Promise {\n const agents = expandTarget(input.target);\n const runner = input.runner ?? new SpawnCommandRunner();\n const results: ConfigureResult[] = [];\n for (const agent of agents) {\n let mcpResult: Omit;\n if (agent === \"opencode\") {\n mcpResult = await unconfigureOpenCode(input);\n } else {\n mcpResult = await unconfigureCliAgent(agent, input, runner);\n }\n const skillAction = await unconfigureAgentSkill(agent, input);\n results.push({ ...mcpResult, skillAction });\n }\n return results;\n}\n\nasync function unconfigureCliAgent(\n agent: \"codex\" | \"claude\",\n input: ConfigureInput,\n runner: CommandRunner,\n): Promise> {\n const executable = agent === \"codex\" ? \"codex\" : \"claude\";\n const existing = await probeCliAgent(agent, runner);\n if (!existing.exists) return { agent, action: \"absent\" };\n if (input.dryRun) return { agent, action: \"would-remove\" };\n const args = agent === \"codex\"\n ? [\"mcp\", \"remove\", SERVER_NAME]\n : [\"mcp\", \"remove\", \"--scope\", \"user\", SERVER_NAME];\n await requireSuccess(runner.run(executable, args), `${agent} MCP removal`);\n return { agent, action: \"removed\" };\n}\n\nexport function launchCommand(options: ClientOptions, cliEntry: string, nodePath: string): string[] {\n return [\n path.resolve(nodePath),\n path.resolve(cliEntry),\n \"serve\",\n \"--url\", options.url.toString(),\n \"--client-id\", options.clientId,\n \"--callback-port\", String(options.callbackPort),\n ];\n}\n\nasync function configureCliAgent(\n agent: \"codex\" | \"claude\",\n command: string[],\n input: ConfigureInput,\n runner: CommandRunner,\n): Promise> {\n const existing = await probeCliAgent(agent, runner);\n if (existing.exists && outputMatchesCommand(existing.output, command)) return { agent, action: \"unchanged\" };\n if (existing.exists && !input.force && !input.dryRun) {\n const confirm = input.confirm ?? terminalConfirm;\n if (!process.stdin.isTTY && !input.confirm) throw new Error(`${agent} already has a different ${SERVER_NAME} MCP entry; use --force to replace it`);\n if (!await confirm(`${agent} already has a different ${SERVER_NAME} MCP entry. Replace it?`)) {\n throw new Error(`${agent} MCP configuration was not changed`);\n }\n }\n const action = existing.exists ? \"replaced\" : \"added\";\n if (input.dryRun) return { agent, action: existing.exists ? \"would-replace\" : \"would-add\" };\n const executable = agent === \"codex\" ? \"codex\" : \"claude\";\n if (existing.exists) {\n const removeArgs = agent === \"codex\"\n ? [\"mcp\", \"remove\", SERVER_NAME]\n : [\"mcp\", \"remove\", \"--scope\", \"user\", SERVER_NAME];\n await requireSuccess(runner.run(executable, removeArgs), `${agent} MCP replacement cleanup`);\n }\n const addArgs = agent === \"codex\"\n ? [\"mcp\", \"add\", SERVER_NAME, \"--\", ...command]\n : [\"mcp\", \"add\", \"--scope\", \"user\", SERVER_NAME, \"--\", ...command];\n await requireSuccess(runner.run(executable, addArgs), `${agent} MCP registration`);\n return { agent, action };\n}\n\nasync function probeCliAgent(agent: \"codex\" | \"claude\", runner: CommandRunner): Promise<{ exists: boolean; output: string }> {\n const executable = agent === \"codex\" ? \"codex\" : \"claude\";\n const args = agent === \"codex\"\n ? [\"mcp\", \"get\", SERVER_NAME, \"--json\"]\n : [\"mcp\", \"get\", SERVER_NAME];\n const result = await runner.run(executable, args);\n if (result.code === 0) return { exists: true, output: result.stdout };\n const combined = `${result.stdout}\\n${result.stderr}`;\n if (/not found|does not exist|no mcp server|not configured|unknown server/i.test(combined)) return { exists: false, output: combined };\n throw new Error(`Could not inspect ${agent} MCP configuration: ${safeCommandError(result)}`);\n}\n\nfunction outputMatchesCommand(output: string, command: string[]): boolean {\n try {\n const document = JSON.parse(output) as unknown;\n if (findCommand(document, command)) return true;\n } catch {\n // Claude currently returns a human-readable record.\n }\n return command.every((part) => output.includes(part));\n}\n\nfunction findCommand(value: unknown, command: string[]): boolean {\n if (!value || typeof value !== \"object\") return false;\n if (Array.isArray(value)) return value.some((item) => findCommand(item, command));\n const record = value as Record;\n if (typeof record.command === \"string\" && Array.isArray(record.args)) {\n const candidate = [record.command, ...record.args.filter((item): item is string => typeof item === \"string\")];\n if (candidate.length === command.length && candidate.every((item, index) => item === command[index])) return true;\n }\n return Object.values(record).some((item) => findCommand(item, command));\n}\n\nasync function configureOpenCode(input: ConfigureInput, command: string[]): Promise> {\n const filePath = input.opencodePath ?? defaultOpenCodePath(input.env);\n const original = await readOptionalFile(filePath) ?? \"{}\\n\";\n const document = parse(original) as { mcp?: Record } | undefined;\n const existing = document?.mcp?.[SERVER_NAME] as { type?: unknown; command?: unknown; enabled?: unknown } | undefined;\n const desired = { type: \"local\", command, enabled: true };\n if (existing && existing.type === desired.type && existing.enabled === true && arraysEqual(existing.command, command)) {\n return { agent: \"opencode\", action: \"unchanged\" };\n }\n if (existing && !input.force && !input.dryRun) {\n const confirm = input.confirm ?? terminalConfirm;\n if (!process.stdin.isTTY && !input.confirm) throw new Error(`opencode already has a different ${SERVER_NAME} MCP entry; use --force to replace it`);\n if (!await confirm(`opencode already has a different ${SERVER_NAME} MCP entry. Replace it?`)) {\n throw new Error(\"opencode MCP configuration was not changed\");\n }\n }\n if (input.dryRun) return { agent: \"opencode\", action: existing ? \"would-replace\" : \"would-add\" };\n const updated = applyEdits(original, modify(original, [\"mcp\", SERVER_NAME], desired, {\n formattingOptions: { insertSpaces: true, tabSize: 2, eol: \"\\n\" },\n }));\n await backupAndAtomicWrite(filePath, updated, await readOptionalFile(filePath) !== undefined);\n return { agent: \"opencode\", action: existing ? \"replaced\" : \"added\" };\n}\n\nasync function unconfigureOpenCode(input: ConfigureInput): Promise> {\n const filePath = input.opencodePath ?? defaultOpenCodePath(input.env);\n const original = await readOptionalFile(filePath);\n if (original === undefined) return { agent: \"opencode\", action: \"absent\" };\n const document = parse(original) as { mcp?: Record } | undefined;\n if (!document?.mcp || !(SERVER_NAME in document.mcp)) return { agent: \"opencode\", action: \"absent\" };\n if (input.dryRun) return { agent: \"opencode\", action: \"would-remove\" };\n const updated = applyEdits(original, modify(original, [\"mcp\", SERVER_NAME], undefined, {\n formattingOptions: { insertSpaces: true, tabSize: 2, eol: \"\\n\" },\n }));\n await backupAndAtomicWrite(filePath, updated, true);\n return { agent: \"opencode\", action: \"removed\" };\n}\n\nexport function defaultOpenCodePath(env: NodeJS.ProcessEnv = process.env): string {\n const base = env.XDG_CONFIG_HOME || (process.platform === \"win32\"\n ? path.join(env.USERPROFILE || os.homedir(), \".config\")\n : path.join(os.homedir(), \".config\"));\n return path.join(base, \"opencode\", \"opencode.json\");\n}\n\nexport function bundledSkillPath(cliEntry: string): string {\n return path.resolve(path.dirname(cliEntry), \"..\", \"skills\", SERVER_NAME);\n}\n\nexport function defaultSkillPath(agent: AgentName, env: NodeJS.ProcessEnv = process.env): string {\n const home = env.USERPROFILE || env.HOME || os.homedir();\n if (agent === \"codex\") return path.join(env.CODEX_HOME || path.join(home, \".codex\"), \"skills\", SERVER_NAME);\n if (agent === \"claude\") return path.join(home, \".claude\", \"skills\", SERVER_NAME);\n return path.join(path.dirname(defaultOpenCodePath(env)), \"skills\", SERVER_NAME);\n}\n\nasync function configureAgentSkill(agent: AgentName, input: ConfigureInput): Promise {\n const source = input.skillSource ?? bundledSkillPath(input.cliEntry);\n const destination = input.skillPaths?.[agent] ?? defaultSkillPath(agent, input.env);\n const sourceFiles = await readSkillDirectory(source, true);\n if (!sourceFiles) throw new Error(`Bundled Skill directory is missing: ${source}`);\n const existingFiles = await readSkillDirectory(destination, false);\n if (existingFiles && skillFilesEqual(existingFiles, sourceFiles)) return \"unchanged\";\n\n if (existingFiles && !input.force && !input.dryRun) {\n const confirm = input.confirm ?? terminalConfirm;\n if (!process.stdin.isTTY && !input.confirm) throw new Error(`${agent} already has a different ${SERVER_NAME} Skill; use --force to replace it`);\n if (!await confirm(`${agent} already has a different ${SERVER_NAME} Skill. Replace it?`)) {\n throw new Error(`${agent} Skill configuration was not changed`);\n }\n }\n\n if (input.dryRun) return existingFiles ? \"would-replace\" : \"would-install\";\n if (existingFiles) await backupSkillDirectory(destination);\n await replaceSkillDirectory(source, destination, Boolean(existingFiles));\n return existingFiles ? \"replaced\" : \"installed\";\n}\n\nasync function unconfigureAgentSkill(agent: AgentName, input: ConfigureInput): Promise {\n const source = input.skillSource ?? bundledSkillPath(input.cliEntry);\n const destination = input.skillPaths?.[agent] ?? defaultSkillPath(agent, input.env);\n const existingFiles = await readSkillDirectory(destination, false);\n if (!existingFiles) return \"absent\";\n const sourceFiles = await readSkillDirectory(source, true);\n if (!sourceFiles) throw new Error(`Bundled Skill directory is missing: ${source}`);\n const managed = skillFilesEqual(existingFiles, sourceFiles);\n if (!managed && !input.force) return input.dryRun ? \"would-preserve\" : \"preserved\";\n if (input.dryRun) return \"would-remove\";\n if (!managed) await backupSkillDirectory(destination);\n await rm(destination, { recursive: true, force: true });\n return \"removed\";\n}\n\nasync function readSkillDirectory(directory: string, required: boolean): Promise | undefined> {\n const files = new Map();\n const walk = async (current: string, relative: string): Promise => {\n const entries = await readdir(current, { withFileTypes: true });\n entries.sort((left, right) => left.name.localeCompare(right.name));\n for (const entry of entries) {\n const entryPath = path.join(current, entry.name);\n const entryRelative = relative ? path.join(relative, entry.name) : entry.name;\n if (entry.isDirectory()) {\n await walk(entryPath, entryRelative);\n } else if (entry.isFile()) {\n files.set(entryRelative, await readFile(entryPath));\n } else {\n throw new Error(`Skill directory contains an unsupported entry: ${entryPath}`);\n }\n }\n };\n try {\n await walk(directory, \"\");\n } catch (error) {\n if (!required && (error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw error;\n }\n if (!files.has(\"SKILL.md\")) throw new Error(`Skill directory is missing SKILL.md: ${directory}`);\n return files;\n}\n\nfunction skillFilesEqual(left: Map, right: Map): boolean {\n if (left.size !== right.size) return false;\n return [...left].every(([name, value]) => right.get(name)?.equals(value) === true);\n}\n\nasync function backupSkillDirectory(directory: string): Promise {\n const backup = `${directory}.crafttable-mcp.backup`;\n try {\n await cp(directory, backup, { recursive: true, force: false, errorOnExist: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n }\n return backup;\n}\n\nasync function replaceSkillDirectory(source: string, destination: string, existed: boolean): Promise {\n await mkdir(path.dirname(destination), { recursive: true });\n const temporary = `${destination}.${process.pid}.${randomUUID()}.tmp`;\n await cp(source, temporary, { recursive: true, force: false, errorOnExist: true });\n try {\n if (existed) await rm(destination, { recursive: true, force: true });\n await rename(temporary, destination);\n } catch (error) {\n if (existed && !await pathExists(destination)) {\n const backup = `${destination}.crafttable-mcp.backup`;\n if (await pathExists(backup)) await cp(backup, destination, { recursive: true });\n }\n throw error;\n } finally {\n await rm(temporary, { recursive: true, force: true });\n }\n}\n\nasync function pathExists(filePath: string): Promise {\n try {\n await stat(filePath);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw error;\n }\n}\n\nasync function backupAndAtomicWrite(filePath: string, updated: string, existed: boolean): Promise {\n await mkdir(path.dirname(filePath), { recursive: true });\n if (existed) {\n try {\n await copyFile(filePath, `${filePath}.crafttable-mcp.backup`, constants.COPYFILE_EXCL);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n }\n }\n const temporary = `${filePath}.${process.pid}.tmp`;\n await writeFile(temporary, updated, \"utf8\");\n await rename(temporary, filePath);\n}\n\nasync function readOptionalFile(filePath: string): Promise {\n try {\n return await readFile(filePath, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw error;\n }\n}\n\nfunction arraysEqual(value: unknown, expected: string[]): boolean {\n return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]);\n}\n\nfunction expandTarget(target: AgentTarget): AgentName[] {\n if (target === \"all\") return [\"codex\", \"claude\", \"opencode\"];\n if ([\"codex\", \"claude\", \"opencode\"].includes(target)) return [target as AgentName];\n throw new Error(\"Agent must be one of: codex, claude, opencode, all\");\n}\n\nasync function terminalConfirm(message: string): Promise {\n const readline = createInterface({ input: process.stdin, output: process.stderr });\n try {\n return /^y(es)?$/i.test((await readline.question(`${message} [y/N] `)).trim());\n } finally {\n readline.close();\n }\n}\n\nasync function requireSuccess(resultPromise: Promise, operation: string): Promise {\n const result = await resultPromise;\n if (result.code !== 0) throw new Error(`${operation} failed: ${safeCommandError(result)}`);\n}\n\nfunction safeCommandError(result: CommandResult): string {\n return (result.stderr || result.stdout || `exit code ${result.code}`).trim();\n}\n\nexport class SpawnCommandRunner implements CommandRunner {\n run(command: string, args: string[]): Promise {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, { shell: false, windowsHide: true, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.setEncoding(\"utf8\").on(\"data\", (chunk) => { stdout += String(chunk); });\n child.stderr?.setEncoding(\"utf8\").on(\"data\", (chunk) => { stderr += String(chunk); });\n child.once(\"error\", (error) => reject(new Error(`Could not run ${command}: ${errorMessage(error)}`)));\n child.once(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\n });\n }\n}\n", "import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { OAuthDiscoveryState } from \"@modelcontextprotocol/sdk/client/auth.js\";\n\nexport const DEFAULT_MCP_URL = \"https://crafttable.crash.work/mcp\";\nexport const DEFAULT_CLIENT_ID = \"crafttable-mcp-cli\";\nexport const DEFAULT_CALLBACK_PORT = 48321;\nexport const OAUTH_SCOPES = \"openid profile email offline_access\";\n\nexport type ClientOptions = {\n url: URL;\n clientId: string;\n callbackPort: number;\n};\n\nexport function resolveClientOptions(input: {\n url?: string;\n clientId?: string;\n callbackPort?: string | number;\n env?: NodeJS.ProcessEnv;\n} = {}): ClientOptions {\n const env = input.env ?? process.env;\n const url = validateMcpUrl(input.url ?? env.CRAFTTABLE_MCP_URL ?? DEFAULT_MCP_URL);\n const clientId = (input.clientId ?? env.CRAFTTABLE_MCP_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID).trim();\n if (!clientId) throw new Error(\"OAuth client ID must not be empty\");\n const callbackPort = Number(input.callbackPort ?? env.CRAFTTABLE_MCP_OAUTH_CALLBACK_PORT ?? DEFAULT_CALLBACK_PORT);\n if (!Number.isInteger(callbackPort) || callbackPort < 1 || callbackPort > 65535) {\n throw new Error(\"OAuth callback port must be an integer between 1 and 65535\");\n }\n return { url, clientId, callbackPort };\n}\n\nexport function validateMcpUrl(value: string): URL {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new Error(\"MCP URL must be an absolute URL\");\n }\n if (url.username || url.password || url.search || url.hash) {\n throw new Error(\"MCP URL must not include credentials, query parameters, or a fragment\");\n }\n const local = url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\" || url.hostname === \"[::1]\";\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && local)) {\n throw new Error(\"MCP URL must use HTTPS unless it targets localhost\");\n }\n url.pathname = url.pathname.replace(/\\/+$/, \"\") || \"/\";\n return url;\n}\n\nexport function credentialAccount(options: Pick): string {\n return createHash(\"sha256\").update(`${options.url.toString()}\\0${options.clientId}`).digest(\"hex\");\n}\n\nexport function platformConfigDir(env: NodeJS.ProcessEnv = process.env, platform = process.platform): string {\n if (platform === \"win32\") return path.join(env.APPDATA || path.join(os.homedir(), \"AppData\", \"Roaming\"), \"GameCraftTable\", \"mcp-client\");\n if (platform === \"darwin\") return path.join(os.homedir(), \"Library\", \"Application Support\", \"GameCraftTable\", \"mcp-client\");\n return path.join(env.XDG_CONFIG_HOME || path.join(os.homedir(), \".config\"), \"gamecrafttable\", \"mcp-client\");\n}\n\ntype DiscoveryFile = {\n version: 1;\n entries: Record;\n};\n\nexport class DiscoveryStore {\n readonly filePath: string;\n\n constructor(configDir = platformConfigDir()) {\n this.filePath = path.join(configDir, \"discovery.json\");\n }\n\n async get(account: string): Promise {\n return (await this.read()).entries[account];\n }\n\n async set(account: string, state: OAuthDiscoveryState): Promise {\n const document = await this.read();\n document.entries[account] = state;\n await atomicWriteJson(this.filePath, document);\n }\n\n async delete(account: string): Promise {\n const document = await this.read();\n if (!(account in document.entries)) return;\n delete document.entries[account];\n await atomicWriteJson(this.filePath, document);\n }\n\n private async read(): Promise {\n try {\n const value = JSON.parse(await readFile(this.filePath, \"utf8\")) as Partial;\n return { version: 1, entries: value.entries && typeof value.entries === \"object\" ? value.entries : {} };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { version: 1, entries: {} };\n throw new Error(`Could not read OAuth discovery cache: ${errorMessage(error)}`);\n }\n }\n}\n\nexport async function atomicWriteJson(filePath: string, value: unknown): Promise {\n await mkdir(path.dirname(filePath), { recursive: true });\n const temporary = `${filePath}.${process.pid}.tmp`;\n await writeFile(temporary, `${JSON.stringify(value, null, 2)}\\n`, { encoding: \"utf8\", mode: 0o600 });\n await rename(temporary, filePath);\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n", "import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListResourceTemplatesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { ClientOptions } from \"./config.js\";\nimport { DiscoveryStore } from \"./config.js\";\nimport type { TokenStore } from \"./credentials.js\";\nimport { connectRemote } from \"./remote.js\";\n\nexport async function serveBridge(\n options: ClientOptions,\n tokenStore: TokenStore,\n discoveryStore: DiscoveryStore,\n serviceToken = process.env.CRAFTTABLE_MCP_TOKEN?.trim(),\n): Promise {\n const upstream = await connectRemote(options, tokenStore, discoveryStore, serviceToken);\n if (upstream.authentication === \"service-token\") {\n process.stderr.write(\"CraftTable MCP: using the legacy service-account token path.\\n\");\n }\n const server = createProxyServer(upstream.client);\n const transport = new StdioServerTransport();\n const close = async (): Promise => {\n await server.close().catch(() => undefined);\n await upstream.close().catch(() => undefined);\n };\n process.once(\"SIGINT\", () => void close());\n process.once(\"SIGTERM\", () => void close());\n try {\n await server.connect(transport);\n } catch (error) {\n await close();\n throw error;\n }\n}\n\nexport function createProxyServer(upstream: {\n listTools: (params?: { cursor?: string }) => Promise;\n callTool: (params: { name: string; arguments?: Record }) => Promise;\n listResources: (params?: { cursor?: string }) => Promise;\n listResourceTemplates: (params?: { cursor?: string }) => Promise;\n readResource: (params: { uri: string }) => Promise;\n}): Server {\n const server = new Server({ name: \"crafttable-mcp-stdio-bridge\", version: \"0.1.3\" }, {\n capabilities: {\n tools: {},\n resources: {},\n },\n });\n server.setRequestHandler(ListToolsRequestSchema, (request) => upstream.listTools(request.params) as never);\n server.setRequestHandler(CallToolRequestSchema, (request) => upstream.callTool(request.params) as never);\n server.setRequestHandler(ListResourcesRequestSchema, (request) => upstream.listResources(request.params) as never);\n server.setRequestHandler(ListResourceTemplatesRequestSchema, (request) => upstream.listResourceTemplates(request.params) as never);\n server.setRequestHandler(ReadResourceRequestSchema, (request) => upstream.readResource(request.params) as never);\n return server;\n}\n", "import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport open from \"open\";\nimport { OAuthCallbackServer } from \"./callbackServer.js\";\nimport type { ClientOptions } from \"./config.js\";\nimport { credentialAccount, DiscoveryStore, errorMessage } from \"./config.js\";\nimport type { TokenStore } from \"./credentials.js\";\nimport { CraftTableOAuthProvider } from \"./oauthProvider.js\";\n\nexport type RemoteConnection = {\n client: Client;\n transport: StreamableHTTPClientTransport;\n authentication: \"oauth\" | \"service-token\";\n close: () => Promise;\n};\n\nexport async function connectRemote(\n options: ClientOptions,\n tokenStore: TokenStore,\n discoveryStore: DiscoveryStore,\n serviceToken = process.env.CRAFTTABLE_MCP_TOKEN?.trim(),\n): Promise {\n const tokens = await tokenStore.get(credentialAccount(options));\n if (tokens) {\n const provider = new CraftTableOAuthProvider(options, tokenStore, discoveryStore, () => {\n throw new Error(\"OAuth login is required; run `crafttable-mcp login`\");\n });\n return connectWithTransport(options, new StreamableHTTPClientTransport(options.url, { authProvider: provider }), \"oauth\");\n }\n if (serviceToken) {\n return connectWithTransport(options, new StreamableHTTPClientTransport(options.url, {\n requestInit: { headers: { authorization: `Bearer ${serviceToken}` } },\n }), \"service-token\");\n }\n throw new Error(\"Not logged in; run `crafttable-mcp login` or set CRAFTTABLE_MCP_TOKEN for the legacy service-account path\");\n}\n\nexport async function loginRemote(\n options: ClientOptions,\n tokenStore: TokenStore,\n discoveryStore: DiscoveryStore,\n input: { browser: boolean; timeoutMs?: number; writeLine?: (value: string) => void } = { browser: true },\n): Promise<{ alreadyAuthenticated: boolean; toolCount: number }> {\n const writeLine = input.writeLine ?? ((value) => process.stderr.write(`${value}\\n`));\n await requireOAuthProtectedResourceMetadata(options.url);\n let authorizationUrl: URL | undefined;\n const provider = new CraftTableOAuthProvider(options, tokenStore, discoveryStore, async (url) => {\n authorizationUrl = url;\n if (!input.browser) {\n writeLine(`Open this URL to log in:\\n${url.toString()}`);\n return;\n }\n try {\n await open(url.toString(), { wait: false });\n writeLine(\"Opened the system browser for CraftTable login.\");\n } catch (error) {\n writeLine(`Could not open the browser (${errorMessage(error)}). Open this URL manually:\\n${url.toString()}`);\n }\n });\n const callback = new OAuthCallbackServer(options.callbackPort, (state) => provider.validateState(state));\n await callback.listen();\n const firstClient = new Client({ name: \"crafttable-mcp-cli\", version: \"0.1.3\" });\n const firstTransport = new StreamableHTTPClientTransport(options.url, { authProvider: provider });\n try {\n try {\n await firstClient.connect(firstTransport);\n const tools = await firstClient.listTools();\n return { alreadyAuthenticated: true, toolCount: tools.tools.length };\n } catch (error) {\n if (!(error instanceof UnauthorizedError) && !authorizationUrl) throw error;\n const { code } = await callback.wait(input.timeoutMs);\n await firstTransport.finishAuth(code);\n }\n } finally {\n await firstClient.close().catch(() => undefined);\n await callback.close().catch(() => undefined);\n }\n\n const connection = await connectRemote(options, tokenStore, discoveryStore, \"\");\n try {\n const tools = await connection.client.listTools();\n return { alreadyAuthenticated: false, toolCount: tools.tools.length };\n } finally {\n await connection.close();\n }\n}\n\nexport async function logoutRemote(\n options: ClientOptions,\n tokenStore: TokenStore,\n discoveryStore: DiscoveryStore,\n localOnly: boolean,\n fetchFn: typeof fetch = fetch,\n): Promise<{ hadCredential: boolean; revoked: boolean }> {\n const account = credentialAccount(options);\n const tokens = await tokenStore.get(account);\n if (tokens && !localOnly) {\n const endpoint = await discoverRevocationEndpoint(options.url, fetchFn);\n await revokeTokens(endpoint, options.clientId, tokens, fetchFn);\n }\n await tokenStore.delete(account);\n await discoveryStore.delete(account);\n return { hadCredential: Boolean(tokens), revoked: Boolean(tokens && !localOnly) };\n}\n\nasync function connectWithTransport(\n options: ClientOptions,\n transport: StreamableHTTPClientTransport,\n authentication: RemoteConnection[\"authentication\"],\n): Promise {\n const client = new Client({ name: \"crafttable-mcp-cli\", version: \"0.1.3\" });\n try {\n await client.connect(transport);\n } catch (error) {\n await client.close().catch(() => undefined);\n if (error instanceof UnauthorizedError) throw new Error(\"OAuth login is required; run `crafttable-mcp login`\");\n throw error;\n }\n return {\n client,\n transport,\n authentication,\n close: () => client.close(),\n };\n}\n\nasync function discoverRevocationEndpoint(resource: URL, fetchFn: typeof fetch): Promise {\n const protectedMetadata = await requireOAuthProtectedResourceMetadata(resource, fetchFn);\n const issuer = Array.isArray(protectedMetadata.authorization_servers)\n ? protectedMetadata.authorization_servers.find((value): value is string => typeof value === \"string\")\n : undefined;\n if (!issuer) throw new Error(\"OAuth protected-resource metadata has no authorization server\");\n const issuerUrl = new URL(issuer);\n const candidates = [\n new URL(`${issuerUrl.toString().replace(/\\/$/, \"\")}/.well-known/openid-configuration`),\n new URL(`/.well-known/openid-configuration${issuerUrl.pathname === \"/\" ? \"\" : issuerUrl.pathname}`, issuerUrl.origin),\n ];\n for (const candidate of candidates) {\n const response = await fetchFn(candidate, { headers: { accept: \"application/json\" } });\n if (!response.ok) continue;\n try {\n const metadata = await response.json() as { revocation_endpoint?: unknown };\n if (typeof metadata.revocation_endpoint === \"string\") return new URL(metadata.revocation_endpoint);\n } catch {\n // Try the next standards-compatible discovery location.\n }\n }\n throw new Error(\"OAuth authorization server does not advertise a revocation endpoint\");\n}\n\nexport async function requireOAuthProtectedResourceMetadata(\n resource: URL,\n fetchFn: typeof fetch = fetch,\n): Promise<{ authorization_servers?: unknown }> {\n const metadataUrl = new URL(`/.well-known/oauth-protected-resource${resource.pathname === \"/\" ? \"\" : resource.pathname}`, resource.origin);\n let response: Response;\n try {\n response = await fetchFn(metadataUrl, { headers: { accept: \"application/json\" } });\n } catch (error) {\n throw new Error(`Could not reach OAuth protected-resource metadata at ${metadataUrl}: ${errorMessage(error)}`);\n }\n const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (!response.ok) {\n throw new Error(`OAuth is not enabled for ${resource}; protected-resource metadata at ${metadataUrl} returned HTTP ${response.status}`);\n }\n if (!contentType.includes(\"application/json\")) {\n throw new Error(`OAuth is not enabled for ${resource}; protected-resource metadata at ${metadataUrl} returned ${contentType || \"an unknown content type\"} instead of JSON`);\n }\n try {\n return await response.json() as { authorization_servers?: unknown };\n } catch {\n throw new Error(`OAuth protected-resource metadata at ${metadataUrl} is not valid JSON`);\n }\n}\n\nasync function revokeTokens(endpoint: URL, clientId: string, tokens: OAuthTokens, fetchFn: typeof fetch): Promise {\n const candidates = [tokens.refresh_token\n ? { token: tokens.refresh_token, hint: \"refresh_token\" }\n : { token: tokens.access_token, hint: \"access_token\" }];\n for (const value of candidates) {\n const response = await fetchFn(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\n body: new URLSearchParams({ token: value.token, token_type_hint: value.hint, client_id: clientId }),\n });\n if (!response.ok) throw new Error(`OAuth token revocation returned HTTP ${response.status}; local credentials were retained`);\n }\n}\n", "import { createServer, type Server } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nexport type OAuthCallback = { code: string };\n\nexport class OAuthCallbackServer {\n private server?: Server;\n private resolveResult?: (value: OAuthCallback) => void;\n private rejectResult?: (reason: Error) => void;\n private readonly result = new Promise((resolve, reject) => {\n this.resolveResult = resolve;\n this.rejectResult = reject;\n });\n\n constructor(\n private readonly port: number,\n private readonly validateState: (state: string | null) => boolean,\n ) {\n // A very fast browser callback can arrive before loginRemote starts awaiting it.\n // Keep rejection handled while preserving the original promise for wait().\n void this.result.catch(() => undefined);\n }\n\n async listen(): Promise {\n if (this.server) throw new Error(\"OAuth callback server is already running\");\n this.server = createServer((request, response) => {\n const url = new URL(request.url ?? \"/\", `http://127.0.0.1:${this.port}`);\n if (request.method !== \"GET\" || url.pathname !== \"/oauth/callback\") {\n response.writeHead(404, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"Not found\");\n return;\n }\n const oauthError = url.searchParams.get(\"error\");\n if (oauthError) {\n response.writeHead(400, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"OAuth login failed. Return to the terminal.\");\n this.rejectResult?.(new Error(`OAuth authorization failed: ${oauthError}`));\n return;\n }\n if (!this.validateState(url.searchParams.get(\"state\"))) {\n response.writeHead(400, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"OAuth state did not match. Return to the terminal.\");\n this.rejectResult?.(new Error(\"OAuth callback state did not match\"));\n return;\n }\n const code = url.searchParams.get(\"code\");\n if (!code) {\n response.writeHead(400, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"OAuth authorization code is missing.\");\n this.rejectResult?.(new Error(\"OAuth callback did not include an authorization code\"));\n return;\n }\n response.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" }).end(\"CraftTable MCP

Login complete. You can close this window.

\");\n this.resolveResult?.({ code });\n });\n await new Promise((resolve, reject) => {\n const onError = (error: Error) => reject(new Error(`Could not listen on OAuth callback port ${this.port}: ${error.message}`));\n this.server!.once(\"error\", onError);\n this.server!.listen(this.port, \"127.0.0.1\", () => {\n this.server!.off(\"error\", onError);\n resolve();\n });\n });\n const address = this.server.address() as AddressInfo | null;\n if (!address || address.port !== this.port) throw new Error(`OAuth callback server did not bind port ${this.port}`);\n }\n\n async wait(timeoutMs = 10 * 60 * 1000): Promise {\n let timeout: NodeJS.Timeout | undefined;\n try {\n return await Promise.race([\n this.result,\n new Promise((_resolve, reject) => {\n timeout = setTimeout(() => reject(new Error(\"OAuth callback timed out\")), timeoutMs);\n }),\n ]);\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n }\n\n async close(): Promise {\n if (!this.server) return;\n const server = this.server;\n this.server = undefined;\n await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));\n }\n}\n", "import { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport type { OAuthClientProvider, OAuthDiscoveryState } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport type { ClientOptions } from \"./config.js\";\nimport { credentialAccount, DiscoveryStore, OAUTH_SCOPES } from \"./config.js\";\nimport type { TokenStore } from \"./credentials.js\";\n\nexport class CraftTableOAuthProvider implements OAuthClientProvider {\n readonly redirectUrl: URL;\n readonly clientMetadata: OAuthClientMetadata;\n private readonly account: string;\n private readonly expectedState: string;\n private codeVerifierValue?: string;\n\n constructor(\n private readonly options: ClientOptions,\n private readonly tokenStore: TokenStore,\n private readonly discoveryStore: DiscoveryStore,\n private readonly onRedirect: (url: URL) => void | Promise,\n state = randomBytes(32).toString(\"base64url\"),\n ) {\n this.redirectUrl = new URL(`http://127.0.0.1:${options.callbackPort}/oauth/callback`);\n this.clientMetadata = {\n client_name: \"CraftTable MCP CLI\",\n redirect_uris: [this.redirectUrl.toString()],\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\",\n scope: OAUTH_SCOPES,\n };\n this.account = credentialAccount(options);\n this.expectedState = state;\n }\n\n state(): string {\n return this.expectedState;\n }\n\n validateState(value: string | null): boolean {\n if (!value) return false;\n const expected = Buffer.from(this.expectedState);\n const actual = Buffer.from(value);\n return expected.length === actual.length && timingSafeEqual(expected, actual);\n }\n\n clientInformation(): OAuthClientInformationMixed {\n return { client_id: this.options.clientId };\n }\n\n tokens(): Promise {\n return this.tokenStore.get(this.account);\n }\n\n saveTokens(tokens: OAuthTokens): Promise {\n return this.tokenStore.set(this.account, tokens);\n }\n\n redirectToAuthorization(url: URL): void | Promise {\n return this.onRedirect(url);\n }\n\n saveCodeVerifier(codeVerifier: string): void {\n this.codeVerifierValue = codeVerifier;\n }\n\n codeVerifier(): string {\n if (!this.codeVerifierValue) throw new Error(\"OAuth PKCE verifier is missing or expired\");\n return this.codeVerifierValue;\n }\n\n discoveryState(): Promise {\n return this.discoveryStore.get(this.account);\n }\n\n saveDiscoveryState(state: OAuthDiscoveryState): Promise {\n return this.discoveryStore.set(this.account, state);\n }\n\n async invalidateCredentials(scope: \"all\" | \"client\" | \"tokens\" | \"verifier\" | \"discovery\"): Promise {\n if (scope === \"all\" || scope === \"tokens\") await this.tokenStore.delete(this.account);\n if (scope === \"all\" || scope === \"discovery\") await this.discoveryStore.delete(this.account);\n if (scope === \"all\" || scope === \"verifier\") this.codeVerifierValue = undefined;\n }\n}\n", "import { randomUUID } from \"node:crypto\";\nimport type { OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\n\nexport const KEYRING_SERVICE = \"CraftTable MCP\";\nconst KEYRING_CHUNK_SIZE = 1000;\n\ntype CredentialEntry = {\n getPassword(): Promise;\n setPassword(password: string): Promise;\n deleteCredential(): Promise;\n};\n\ntype CredentialEntryFactory = (account: string) => Promise;\n\ntype ChunkManifest = {\n version: 2;\n generation: string;\n chunks: number;\n};\n\nexport interface TokenStore {\n get(account: string): Promise;\n set(account: string, tokens: OAuthTokens): Promise;\n delete(account: string): Promise;\n}\n\nexport class KeyringTokenStore implements TokenStore {\n constructor(private readonly entryFactory: CredentialEntryFactory = keyringEntry) {}\n\n async get(account: string): Promise {\n let serialized: string | undefined;\n try {\n serialized = await this.readPassword(account);\n } catch (error) {\n throw keyringError(error);\n }\n if (!serialized) return undefined;\n\n const manifest = parseChunkManifest(serialized);\n if (manifest) {\n try {\n const chunks = await Promise.all(Array.from({ length: manifest.chunks }, (_, index) =>\n this.readPassword(chunkAccount(account, manifest.generation, index))));\n if (chunks.some((chunk) => chunk === undefined)) throw new Error(\"missing credential chunk\");\n serialized = Buffer.from(chunks.join(\"\"), \"base64\").toString(\"utf8\");\n } catch (error) {\n if (error instanceof Error && error.message === \"missing credential chunk\") throw invalidCredentialError();\n throw keyringError(error);\n }\n }\n\n try {\n const value = JSON.parse(serialized) as OAuthTokens;\n if (!value.access_token || !value.token_type) throw new Error(\"missing token fields\");\n return value;\n } catch {\n throw invalidCredentialError();\n }\n }\n\n async set(account: string, tokens: OAuthTokens): Promise {\n const encoded = Buffer.from(JSON.stringify(tokens), \"utf8\").toString(\"base64\");\n const chunks = splitCredential(encoded);\n const generation = randomUUID().replaceAll(\"-\", \"\");\n const manifest: ChunkManifest = { version: 2, generation, chunks: chunks.length };\n let previous: string | undefined;\n const writtenAccounts: string[] = [];\n\n try {\n previous = await this.readPassword(account);\n for (const [index, chunk] of chunks.entries()) {\n const partAccount = chunkAccount(account, generation, index);\n await (await this.entryFactory(partAccount)).setPassword(chunk);\n writtenAccounts.push(partAccount);\n }\n await (await this.entryFactory(account)).setPassword(JSON.stringify(manifest));\n } catch (error) {\n await Promise.allSettled(writtenAccounts.map((partAccount) => this.deletePassword(partAccount)));\n throw keyringError(error);\n }\n\n const previousManifest = previous && parseChunkManifest(previous);\n if (previousManifest) {\n await Promise.allSettled(Array.from({ length: previousManifest.chunks }, (_, index) =>\n this.deletePassword(chunkAccount(account, previousManifest.generation, index))));\n }\n }\n\n async delete(account: string): Promise {\n try {\n const serialized = await this.readPassword(account);\n const manifest = serialized ? parseChunkManifest(serialized) : undefined;\n await this.deletePassword(account);\n if (manifest) {\n await Promise.all(Array.from({ length: manifest.chunks }, (_, index) =>\n this.deletePassword(chunkAccount(account, manifest.generation, index))));\n }\n } catch (error) {\n throw keyringError(error);\n }\n }\n\n private async readPassword(account: string): Promise {\n try {\n return await (await this.entryFactory(account)).getPassword();\n } catch (error) {\n if (isMissingCredential(error)) return undefined;\n throw error;\n }\n }\n\n private async deletePassword(account: string): Promise {\n try {\n await (await this.entryFactory(account)).deleteCredential();\n } catch (error) {\n if (!isMissingCredential(error)) throw error;\n }\n }\n}\n\nfunction splitCredential(value: string): string[] {\n const chunks: string[] = [];\n for (let offset = 0; offset < value.length; offset += KEYRING_CHUNK_SIZE) {\n chunks.push(value.slice(offset, offset + KEYRING_CHUNK_SIZE));\n }\n return chunks.length ? chunks : [\"\"];\n}\n\nfunction chunkAccount(account: string, generation: string, index: number): string {\n return `${account}:v2:${generation}:${index}`;\n}\n\nfunction parseChunkManifest(value: string): ChunkManifest | undefined {\n try {\n const parsed = JSON.parse(value) as Partial;\n if (parsed.version !== 2 || typeof parsed.generation !== \"string\" || !/^[0-9a-f]{32}$/.test(parsed.generation)\n || !Number.isInteger(parsed.chunks) || (parsed.chunks ?? 0) < 1 || (parsed.chunks ?? 0) > 100) return undefined;\n return parsed as ChunkManifest;\n } catch {\n return undefined;\n }\n}\n\nfunction isMissingCredential(error: unknown): boolean {\n const message = String((error as Error)?.message ?? error).toLowerCase();\n return message.includes(\"no entry\") || message.includes(\"not found\");\n}\n\nfunction invalidCredentialError(): Error {\n return new Error(\"The saved CraftTable MCP credential is invalid; run `crafttable-mcp logout --local-only` and log in again\");\n}\n\nasync function keyringEntry(account: string): Promise {\n try {\n const { AsyncEntry } = await import(\"@napi-rs/keyring\");\n return new AsyncEntry(KEYRING_SERVICE, account);\n } catch (error) {\n throw keyringError(error);\n }\n}\n\nfunction keyringError(error: unknown): Error {\n if (error instanceof Error && error.message.startsWith(\"The operating-system credential store is unavailable\")) return error;\n const detail = error instanceof Error ? error.message : String(error);\n return new Error(`The operating-system credential store is unavailable (${detail}). Enable Windows Credential Manager, macOS Keychain, or a Secret Service provider, then retry; plaintext token storage is not supported.`);\n}\n"], - "mappings": "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", + "sourcesContent": ["#!/usr/bin/env node\r\n\r\n/**\r\n * \u5B9A\u4E49 CraftTable MCP \u5BA2\u6237\u7AEF\u7684\u547D\u4EE4\u884C\u5165\u53E3\u4E0E\u5B50\u547D\u4EE4\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { Command } from \"commander\";\r\nimport { configureAgents, type AgentTarget, unconfigureAgents } from \"./agents.js\";\r\nimport { serveBridge } from \"./bridge.js\";\r\nimport { type ClientOptions, credentialAccount, DiscoveryStore, resolveClientOptions } from \"./config.js\";\r\nimport { KeyringTokenStore } from \"./credentials.js\";\r\nimport { connectRemote, loginRemote, logoutRemote } from \"./remote.js\";\r\n\r\ntype ConnectionFlags = { url?: string; clientId?: string; callbackPort?: string };\r\n\r\n/** \u4EA4\u4E92\u5F0F OAuth\u3001\u68C0\u67E5\u4E0E stdio \u6865\u63A5\u6D41\u7A0B\u5171\u7528\u7684\u6839\u547D\u4EE4\u3002 */\r\nconst program = new Command()\r\n .name(\"crafttable-mcp\")\r\n .description(\"OAuth client and stdio bridge for CraftTable MCP\")\r\n .version(\"0.1.3\");\r\n\r\nwithConnection(program.command(\"login\").description(\"Log in through the system browser\"))\r\n .option(\"--no-browser\", \"print the authorization URL instead of opening it\")\r\n .option(\"--timeout \", \"OAuth callback timeout\", \"600000\")\r\n .action(async (flags: ConnectionFlags & { browser: boolean; timeout: string }) => {\r\n const options = connectionOptions(flags);\r\n const timeoutMs = positiveInteger(flags.timeout, \"OAuth callback timeout\");\r\n const result = await loginRemote(options, new KeyringTokenStore(), new DiscoveryStore(), { browser: flags.browser, timeoutMs });\r\n printJson({ loggedIn: true, alreadyAuthenticated: result.alreadyAuthenticated, server: options.url.toString(), tools: result.toolCount });\r\n });\r\n\r\nwithConnection(program.command(\"status\").description(\"Check saved login state and remote MCP connectivity\"))\r\n .action(async (flags: ConnectionFlags) => {\r\n const options = connectionOptions(flags);\r\n const tokens = await new KeyringTokenStore().get(credentialAccount(options));\r\n const connection = await connectRemote(options, new KeyringTokenStore(), new DiscoveryStore());\r\n try {\r\n const tools = await connection.client.listTools();\r\n printJson({\r\n loggedIn: Boolean(tokens),\r\n authentication: connection.authentication,\r\n connected: true,\r\n server: options.url.toString(),\r\n tools: tools.tools.length,\r\n });\r\n } finally {\r\n await connection.close();\r\n }\r\n });\r\n\r\nwithConnection(program.command(\"logout\").description(\"Revoke OAuth tokens and remove the local credential\"))\r\n .option(\"--local-only\", \"remove local credentials without contacting the authorization server\")\r\n .action(async (flags: ConnectionFlags & { localOnly?: boolean }) => {\r\n const options = connectionOptions(flags);\r\n const result = await logoutRemote(options, new KeyringTokenStore(), new DiscoveryStore(), Boolean(flags.localOnly));\r\n printJson({ loggedIn: false, credentialRemoved: result.hadCredential, revoked: result.revoked, localOnly: Boolean(flags.localOnly) });\r\n });\r\n\r\nwithConnection(program.command(\"tools\").description(\"List remote MCP tools\"))\r\n .action(async (flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => printJson(await client.listTools())));\r\n\r\nwithConnection(program.command(\"resources\").description(\"List remote MCP resources and resource templates\"))\r\n .action(async (flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => printJson({\r\n resources: (await client.listResources()).resources,\r\n resourceTemplates: (await client.listResourceTemplates()).resourceTemplates,\r\n })));\r\n\r\nwithConnection(program.command(\"read \").description(\"Read an MCP resource\"))\r\n .action(async (uri: string, flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => printJson(await client.readResource({ uri }))));\r\n\r\nwithConnection(program.command(\"call [json]\").description(\"Call an MCP tool\"))\r\n .action(async (tool: string, json: string | undefined, flags: ConnectionFlags) => withClient(connectionOptions(flags), async (client) => {\r\n printJson(await client.callTool({ name: tool, arguments: parseObject(json ?? \"{}\") }));\r\n }));\r\n\r\nwithConnection(program.command(\"serve\").description(\"Run the local stdio bridge\"))\r\n .action(async (flags: ConnectionFlags) => {\r\n await serveBridge(connectionOptions(flags), new KeyringTokenStore(), new DiscoveryStore());\r\n });\r\n\r\nfor (const operation of [\"configure\", \"unconfigure\"] as const) {\r\n withConnection(program.command(`${operation} `).description(`${operation === \"configure\" ? \"Add\" : \"Remove\"} the stdio bridge and CraftTable Skill in Codex, Claude Code, or OpenCode`))\r\n .option(\"--dry-run\", \"show the planned changes without writing\")\r\n .option(\"--force\", \"replace a conflicting entry without prompting\")\r\n .action(async (agent: AgentTarget, flags: ConnectionFlags & { dryRun?: boolean; force?: boolean }) => {\r\n const cliEntry = fileURLToPath(import.meta.url);\r\n if (!cliEntry.endsWith(\".js\")) throw new Error(\"Agent configuration requires the built CLI; run `npm --prefix mcp-client run build` first\");\r\n const input = {\r\n target: agent,\r\n options: connectionOptions(flags),\r\n cliEntry,\r\n dryRun: Boolean(flags.dryRun),\r\n force: Boolean(flags.force),\r\n };\r\n const result = operation === \"configure\" ? await configureAgents(input) : await unconfigureAgents(input);\r\n printJson({ result });\r\n });\r\n}\r\n\r\n/**\r\n * \u4E3A\u547D\u4EE4\u6DFB\u52A0\u901A\u7528\u7684\u8FDC\u7AEF\u7AEF\u70B9\u4E0E OAuth \u5BA2\u6237\u7AEF\u9009\u9879\u3002\r\n *\r\n * @param command - \u8981\u6269\u5C55\u7684 Commander \u547D\u4EE4\u3002\r\n * @returns \u5DF2\u6CE8\u518C\u8FDE\u63A5\u9009\u9879\u7684\u540C\u4E00\u547D\u4EE4\u5B9E\u4F8B\u3002\r\n */\r\nfunction withConnection(command: Command): Command {\r\n return command\r\n .option(\"--url \", \"MCP Streamable HTTP URL\")\r\n .option(\"--client-id \", \"pre-registered OAuth public client ID\")\r\n .option(\"--callback-port \", \"fixed localhost OAuth callback port\");\r\n}\r\n\r\n/** \u89E3\u6790\u5E76\u9A8C\u8BC1\u4E00\u6B21\u547D\u4EE4\u8C03\u7528\u7684\u8FDE\u63A5\u9009\u9879\u3002 */\r\nfunction connectionOptions(flags: ConnectionFlags): ClientOptions {\r\n return resolveClientOptions(flags);\r\n}\r\n\r\n/**\r\n * \u4F7F\u7528\u5DF2\u8FDE\u63A5\u7684\u8FDC\u7AEF\u5BA2\u6237\u7AEF\u6267\u884C\u64CD\u4F5C\uFF0C\u5E76\u786E\u4FDD\u8FDE\u63A5\u59CB\u7EC8\u5173\u95ED\u3002\r\n *\r\n * @param options - \u5DF2\u9A8C\u8BC1\u7684\u8FDC\u7AEF\u8FDE\u63A5\u8BBE\u7F6E\u3002\r\n * @param action - \u4F7F\u7528\u5DF2\u8FDE\u63A5 MCP \u5BA2\u6237\u7AEF\u6267\u884C\u7684\u64CD\u4F5C\u3002\r\n */\r\nasync function withClient(options: ClientOptions, action: (client: Awaited>[\"client\"]) => Promise): Promise {\r\n const connection = await connectRemote(options, new KeyringTokenStore(), new DiscoveryStore());\r\n try {\r\n await action(connection.client);\r\n } finally {\r\n await connection.close();\r\n }\r\n}\r\n\r\n/**\r\n * \u89E3\u6790 CLI \u5DE5\u5177\u53C2\u6570\uFF0C\u5E76\u8981\u6C42 JSON \u503C\u5FC5\u987B\u4E3A\u5BF9\u8C61\u3002\r\n *\r\n * @param raw - \u547D\u4EE4\u884C\u63D0\u4F9B\u7684 JSON\u3002\r\n * @returns \u89E3\u6790\u540E\u7684\u5DE5\u5177\u53C2\u6570\u3002\r\n * @throws JSON \u65E0\u6548\u6216\u503C\u4E0D\u662F\u5BF9\u8C61\u65F6\u629B\u51FA\u3002\r\n */\r\nfunction parseObject(raw: string): Record {\r\n let value: unknown;\r\n try {\r\n value = JSON.parse(raw);\r\n } catch {\r\n throw new Error(\"Tool arguments must be a JSON object\");\r\n }\r\n if (!value || Array.isArray(value) || typeof value !== \"object\") throw new Error(\"Tool arguments must be a JSON object\");\r\n return value as Record;\r\n}\r\n\r\n/**\r\n * \u89E3\u6790\u6B63\u6574\u6570\u547D\u4EE4\u9009\u9879\u3002\r\n *\r\n * @param value - \u539F\u59CB CLI \u9009\u9879\u503C\u3002\r\n * @param name - \u9519\u8BEF\u6D88\u606F\u4E2D\u4F7F\u7528\u7684\u53EF\u8BFB\u9009\u9879\u540D\u79F0\u3002\r\n * @returns \u7ECF\u8FC7\u9A8C\u8BC1\u7684\u6574\u6570\u3002\r\n * @throws \u503C\u4E0D\u662F\u6B63\u6574\u6570\u65F6\u629B\u51FA\u3002\r\n */\r\nfunction positiveInteger(value: string, name: string): number {\r\n const parsed = Number(value);\r\n if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${name} must be a positive integer`);\r\n return parsed;\r\n}\r\n\r\n/** \u5C06\u7A33\u5B9A\u4E14\u4FBF\u4E8E\u9605\u8BFB\u7684 JSON \u7ED3\u679C\u5199\u5165\u6807\u51C6\u8F93\u51FA\u3002 */\r\nfunction printJson(value: unknown): void {\r\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\r\n}\r\n\r\nprogram.parseAsync().catch((error: unknown) => {\r\n process.stderr.write(`CraftTable MCP failed: ${error instanceof Error ? error.message : String(error)}\\n`);\r\n process.exitCode = 1;\r\n});\r\n", "/**\r\n * \u7BA1\u7406 CraftTable MCP \u5728 Codex\u3001Claude Code \u4E0E OpenCode \u4E2D\u7684\u6CE8\u518C\u548C Skill \u5B89\u88C5\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { randomUUID } from \"node:crypto\";\r\nimport { constants } from \"node:fs\";\r\nimport { copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\r\nimport os from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { createInterface } from \"node:readline/promises\";\r\nimport { applyEdits, modify, parse } from \"jsonc-parser\";\r\nimport spawn from \"cross-spawn\";\r\nimport type { ClientOptions } from \"./config.js\";\r\nimport { errorMessage } from \"./config.js\";\r\n\r\n/** CraftTable MCP \u670D\u52A1\u53CA\u968F\u9644 Skill \u4F7F\u7528\u7684\u6CE8\u518C\u540D\u79F0\u3002 */\r\nexport const SERVER_NAME = \"crafttable\";\r\n/** \u652F\u6301\u81EA\u52A8\u914D\u7F6E\u7684 Agent CLI\u3002 */\r\nexport type AgentName = \"codex\" | \"claude\" | \"opencode\";\r\n/** \u5355\u4E2A\u53D7\u652F\u6301 Agent \u6216\u5168\u90E8\u53D7\u652F\u6301 Agent\u3002 */\r\nexport type AgentTarget = AgentName | \"all\";\r\n/** \u5B89\u88C5\u6216\u79FB\u9664\u6258\u7BA1 CraftTable Skill \u7684\u7ED3\u679C\u3002 */\r\nexport type SkillAction = \"installed\" | \"replaced\" | \"removed\" | \"unchanged\" | \"absent\" | \"preserved\" | \"would-install\" | \"would-replace\" | \"would-remove\" | \"would-preserve\";\r\n\r\n/** \u5916\u90E8 Agent CLI \u8C03\u7528\u7684\u6355\u83B7\u7ED3\u679C\u3002 */\r\nexport type CommandResult = { code: number; stdout: string; stderr: string };\r\n/** Agent \u914D\u7F6E\u4F7F\u7528\u7684\u53EF\u6CE8\u5165\u547D\u4EE4\u6267\u884C\u8FB9\u754C\u3002 */\r\nexport interface CommandRunner {\r\n /** \u4E0D\u7ECF\u8FC7 shell \u6267\u884C\u547D\u4EE4\u5E76\u6355\u83B7\u5176\u8F93\u51FA\u3002 */\r\n run(command: string, args: string[]): Promise;\r\n}\r\n\r\n/** \u63A7\u5236 MCP \u6CE8\u518C\u548C\u968F\u9644 Skill \u5B89\u88C5\u7684\u8F93\u5165\u3002 */\r\nexport type ConfigureInput = {\r\n target: AgentTarget;\r\n options: ClientOptions;\r\n cliEntry: string;\r\n nodePath?: string;\r\n dryRun?: boolean;\r\n force?: boolean;\r\n env?: NodeJS.ProcessEnv;\r\n runner?: CommandRunner;\r\n confirm?: (message: string) => Promise;\r\n opencodePath?: string;\r\n skillSource?: string;\r\n skillPaths?: Partial>;\r\n};\r\n\r\n/** \u914D\u7F6E\u6216\u53D6\u6D88\u914D\u7F6E\u64CD\u4F5C\u4E2D\u6BCF\u4E2A Agent \u7684\u7ED3\u679C\u3002 */\r\nexport type ConfigureResult = {\r\n agent: AgentName;\r\n action: \"added\" | \"replaced\" | \"removed\" | \"unchanged\" | \"absent\" | \"would-add\" | \"would-replace\" | \"would-remove\";\r\n skillAction: SkillAction;\r\n};\r\n\r\n/**\r\n * \u4E3A\u9009\u5B9A Agent \u6CE8\u518C stdio \u6865\u63A5\u5E76\u5B89\u88C5\u968F\u9644 Skill\u3002\r\n *\r\n * \u9664\u975E\u73B0\u6709\u914D\u7F6E\u4E0E\u6258\u7BA1\u5185\u5BB9\u4E00\u81F4\u3001\u7528\u6237\u786E\u8BA4\u66FF\u6362\u6216\u542F\u7528 `force`\uFF0C\u5426\u5219\u4FDD\u7559\u73B0\u6709\u914D\u7F6E\u3002\r\n *\r\n * @param input - \u76EE\u6807 Agent\u3001\u542F\u52A8\u8BBE\u7F6E\u4E0E\u53D8\u66F4\u7B56\u7565\u3002\r\n * @returns \u6BCF\u4E2A\u9009\u5B9A Agent \u7684 MCP \u4E0E Skill \u64CD\u4F5C\u7ED3\u679C\u3002\r\n * @throws \u65E0\u6CD5\u68C0\u67E5\u914D\u7F6E\u3001\u65E0\u6CD5\u53D6\u5F97\u786E\u8BA4\u6216\u5199\u5165/CLI \u547D\u4EE4\u5931\u8D25\u65F6\u629B\u51FA\u3002\r\n */\r\nexport async function configureAgents(input: ConfigureInput): Promise {\r\n const agents = expandTarget(input.target);\r\n const command = launchCommand(input.options, input.cliEntry, input.nodePath ?? process.execPath);\r\n const runner = input.runner ?? new SpawnCommandRunner();\r\n const results: ConfigureResult[] = [];\r\n for (const agent of agents) {\r\n const mcpResult = agent === \"opencode\"\r\n ? await configureOpenCode(input, command)\r\n : await configureCliAgent(agent, command, input, runner);\r\n const skillAction = await configureAgentSkill(agent, input);\r\n results.push({ ...mcpResult, skillAction });\r\n }\r\n return results;\r\n}\r\n\r\n/**\r\n * \u4ECE\u9009\u5B9A Agent \u4E2D\u79FB\u9664\u6258\u7BA1\u7684 MCP \u6CE8\u518C\u4E0E\u968F\u9644 Skill\u3002\r\n *\r\n * \u9664\u975E\u542F\u7528 `force`\uFF0C\u5426\u5219\u4FDD\u7559\u7528\u6237\u4FEE\u6539\u8FC7\u7684 Skill\u3002\r\n *\r\n * @param input - \u76EE\u6807 Agent\u3001\u8DEF\u5F84\u4E0E\u53D8\u66F4\u7B56\u7565\u3002\r\n * @returns \u6BCF\u4E2A\u9009\u5B9A Agent \u7684 MCP \u4E0E Skill \u64CD\u4F5C\u7ED3\u679C\u3002\r\n * @throws \u65E0\u6CD5\u68C0\u67E5\u914D\u7F6E\u6216\u5199\u5165/CLI \u547D\u4EE4\u5931\u8D25\u65F6\u629B\u51FA\u3002\r\n */\r\nexport async function unconfigureAgents(input: ConfigureInput): Promise {\r\n const agents = expandTarget(input.target);\r\n const runner = input.runner ?? new SpawnCommandRunner();\r\n const results: ConfigureResult[] = [];\r\n for (const agent of agents) {\r\n let mcpResult: Omit;\r\n if (agent === \"opencode\") {\r\n mcpResult = await unconfigureOpenCode(input);\r\n } else {\r\n mcpResult = await unconfigureCliAgent(agent, input, runner);\r\n }\r\n const skillAction = await unconfigureAgentSkill(agent, input);\r\n results.push({ ...mcpResult, skillAction });\r\n }\r\n return results;\r\n}\r\n\r\nasync function unconfigureCliAgent(\r\n agent: \"codex\" | \"claude\",\r\n input: ConfigureInput,\r\n runner: CommandRunner,\r\n): Promise> {\r\n const executable = agent === \"codex\" ? \"codex\" : \"claude\";\r\n const existing = await probeCliAgent(agent, runner);\r\n if (!existing.exists) return { agent, action: \"absent\" };\r\n if (input.dryRun) return { agent, action: \"would-remove\" };\r\n const args = agent === \"codex\"\r\n ? [\"mcp\", \"remove\", SERVER_NAME]\r\n : [\"mcp\", \"remove\", \"--scope\", \"user\", SERVER_NAME];\r\n await requireSuccess(runner.run(executable, args), `${agent} MCP removal`);\r\n return { agent, action: \"removed\" };\r\n}\r\n\r\n/**\r\n * \u6784\u9020\u6CE8\u518C\u5230 Agent CLI \u7684\u5B8C\u6574\u53EF\u6267\u884C\u7A0B\u5E8F\u4E0E\u53C2\u6570\u3002\r\n *\r\n * @param options - \u8FDC\u7AEF MCP \u4E0E OAuth \u56DE\u8C03\u8BBE\u7F6E\u3002\r\n * @param cliEntry - CraftTable MCP CLI \u5165\u53E3\u6A21\u5757\u8DEF\u5F84\u3002\r\n * @param nodePath - \u7528\u4E8E\u542F\u52A8\u5165\u53E3\u6A21\u5757\u7684 Node.js \u53EF\u6267\u884C\u6587\u4EF6\u3002\r\n * @returns \u53EF\u7528\u4E8E Agent \u914D\u7F6E\u7684 argv \u98CE\u683C\u547D\u4EE4\u6570\u7EC4\u3002\r\n */\r\nexport function launchCommand(options: ClientOptions, cliEntry: string, nodePath: string): string[] {\r\n return [\r\n path.resolve(nodePath),\r\n path.resolve(cliEntry),\r\n \"serve\",\r\n \"--url\", options.url.toString(),\r\n \"--client-id\", options.clientId,\r\n \"--callback-port\", String(options.callbackPort),\r\n ];\r\n}\r\n\r\nasync function configureCliAgent(\r\n agent: \"codex\" | \"claude\",\r\n command: string[],\r\n input: ConfigureInput,\r\n runner: CommandRunner,\r\n): Promise> {\r\n const existing = await probeCliAgent(agent, runner);\r\n if (existing.exists && outputMatchesCommand(existing.output, command)) return { agent, action: \"unchanged\" };\r\n if (existing.exists && !input.force && !input.dryRun) {\r\n const confirm = input.confirm ?? terminalConfirm;\r\n if (!process.stdin.isTTY && !input.confirm) throw new Error(`${agent} already has a different ${SERVER_NAME} MCP entry; use --force to replace it`);\r\n if (!await confirm(`${agent} already has a different ${SERVER_NAME} MCP entry. Replace it?`)) {\r\n throw new Error(`${agent} MCP configuration was not changed`);\r\n }\r\n }\r\n const action = existing.exists ? \"replaced\" : \"added\";\r\n if (input.dryRun) return { agent, action: existing.exists ? \"would-replace\" : \"would-add\" };\r\n const executable = agent === \"codex\" ? \"codex\" : \"claude\";\r\n if (existing.exists) {\r\n const removeArgs = agent === \"codex\"\r\n ? [\"mcp\", \"remove\", SERVER_NAME]\r\n : [\"mcp\", \"remove\", \"--scope\", \"user\", SERVER_NAME];\r\n await requireSuccess(runner.run(executable, removeArgs), `${agent} MCP replacement cleanup`);\r\n }\r\n const addArgs = agent === \"codex\"\r\n ? [\"mcp\", \"add\", SERVER_NAME, \"--\", ...command]\r\n : [\"mcp\", \"add\", \"--scope\", \"user\", SERVER_NAME, \"--\", ...command];\r\n await requireSuccess(runner.run(executable, addArgs), `${agent} MCP registration`);\r\n return { agent, action };\r\n}\r\n\r\nasync function probeCliAgent(agent: \"codex\" | \"claude\", runner: CommandRunner): Promise<{ exists: boolean; output: string }> {\r\n const executable = agent === \"codex\" ? \"codex\" : \"claude\";\r\n const args = agent === \"codex\"\r\n ? [\"mcp\", \"get\", SERVER_NAME, \"--json\"]\r\n : [\"mcp\", \"get\", SERVER_NAME];\r\n const result = await runner.run(executable, args);\r\n if (result.code === 0) return { exists: true, output: result.stdout };\r\n const combined = `${result.stdout}\\n${result.stderr}`;\r\n if (/not found|does not exist|no mcp server|not configured|unknown server/i.test(combined)) return { exists: false, output: combined };\r\n throw new Error(`Could not inspect ${agent} MCP configuration: ${safeCommandError(result)}`);\r\n}\r\n\r\nfunction outputMatchesCommand(output: string, command: string[]): boolean {\r\n try {\r\n const document = JSON.parse(output) as unknown;\r\n if (findCommand(document, command)) return true;\r\n } catch {\r\n // Claude \u5F53\u524D\u8FD4\u56DE\u4FBF\u4E8E\u9605\u8BFB\u7684\u6587\u672C\u8BB0\u5F55\u3002\r\n }\r\n return command.every((part) => output.includes(part));\r\n}\r\n\r\nfunction findCommand(value: unknown, command: string[]): boolean {\r\n if (!value || typeof value !== \"object\") return false;\r\n if (Array.isArray(value)) return value.some((item) => findCommand(item, command));\r\n const record = value as Record;\r\n if (typeof record.command === \"string\" && Array.isArray(record.args)) {\r\n const candidate = [record.command, ...record.args.filter((item): item is string => typeof item === \"string\")];\r\n if (candidate.length === command.length && candidate.every((item, index) => item === command[index])) return true;\r\n }\r\n return Object.values(record).some((item) => findCommand(item, command));\r\n}\r\n\r\nasync function configureOpenCode(input: ConfigureInput, command: string[]): Promise> {\r\n const filePath = input.opencodePath ?? defaultOpenCodePath(input.env);\r\n const original = await readOptionalFile(filePath) ?? \"{}\\n\";\r\n const document = parse(original) as { mcp?: Record } | undefined;\r\n const existing = document?.mcp?.[SERVER_NAME] as { type?: unknown; command?: unknown; enabled?: unknown } | undefined;\r\n const desired = { type: \"local\", command, enabled: true };\r\n if (existing && existing.type === desired.type && existing.enabled === true && arraysEqual(existing.command, command)) {\r\n return { agent: \"opencode\", action: \"unchanged\" };\r\n }\r\n if (existing && !input.force && !input.dryRun) {\r\n const confirm = input.confirm ?? terminalConfirm;\r\n if (!process.stdin.isTTY && !input.confirm) throw new Error(`opencode already has a different ${SERVER_NAME} MCP entry; use --force to replace it`);\r\n if (!await confirm(`opencode already has a different ${SERVER_NAME} MCP entry. Replace it?`)) {\r\n throw new Error(\"opencode MCP configuration was not changed\");\r\n }\r\n }\r\n if (input.dryRun) return { agent: \"opencode\", action: existing ? \"would-replace\" : \"would-add\" };\r\n const updated = applyEdits(original, modify(original, [\"mcp\", SERVER_NAME], desired, {\r\n formattingOptions: { insertSpaces: true, tabSize: 2, eol: \"\\n\" },\r\n }));\r\n await backupAndAtomicWrite(filePath, updated, await readOptionalFile(filePath) !== undefined);\r\n return { agent: \"opencode\", action: existing ? \"replaced\" : \"added\" };\r\n}\r\n\r\nasync function unconfigureOpenCode(input: ConfigureInput): Promise> {\r\n const filePath = input.opencodePath ?? defaultOpenCodePath(input.env);\r\n const original = await readOptionalFile(filePath);\r\n if (original === undefined) return { agent: \"opencode\", action: \"absent\" };\r\n const document = parse(original) as { mcp?: Record } | undefined;\r\n if (!document?.mcp || !(SERVER_NAME in document.mcp)) return { agent: \"opencode\", action: \"absent\" };\r\n if (input.dryRun) return { agent: \"opencode\", action: \"would-remove\" };\r\n const updated = applyEdits(original, modify(original, [\"mcp\", SERVER_NAME], undefined, {\r\n formattingOptions: { insertSpaces: true, tabSize: 2, eol: \"\\n\" },\r\n }));\r\n await backupAndAtomicWrite(filePath, updated, true);\r\n return { agent: \"opencode\", action: \"removed\" };\r\n}\r\n\r\n/**\r\n * @param env - \u7528\u4E8E\u89E3\u6790\u7528\u6237\u914D\u7F6E\u6839\u76EE\u5F55\u7684\u73AF\u5883\u53D8\u91CF\u3002\r\n * @returns \u9ED8\u8BA4 OpenCode JSONC \u914D\u7F6E\u8DEF\u5F84\u3002\r\n */\r\nexport function defaultOpenCodePath(env: NodeJS.ProcessEnv = process.env): string {\r\n const base = env.XDG_CONFIG_HOME || (process.platform === \"win32\"\r\n ? path.join(env.USERPROFILE || os.homedir(), \".config\")\r\n : path.join(os.homedir(), \".config\"));\r\n return path.join(base, \"opencode\", \"opencode.json\");\r\n}\r\n\r\n/**\r\n * @param cliEntry - \u5DF2\u5B89\u88C5 CLI \u5165\u53E3\u6A21\u5757\u7684\u8DEF\u5F84\u3002\r\n * @returns \u4E0E\u5165\u53E3\u76F8\u90BB\u7684\u968F\u9644 CraftTable Skill \u76EE\u5F55\u3002\r\n */\r\nexport function bundledSkillPath(cliEntry: string): string {\r\n return path.resolve(path.dirname(cliEntry), \"..\", \"skills\", SERVER_NAME);\r\n}\r\n\r\n/**\r\n * @param agent - \u9700\u8981\u89E3\u6790\u7528\u6237\u7EA7 Skill \u76EE\u5F55\u7684 Agent\u3002\r\n * @param env - \u7528\u4E8E\u89E3\u6790\u7528\u6237\u914D\u7F6E\u6839\u76EE\u5F55\u7684\u73AF\u5883\u53D8\u91CF\u3002\r\n * @returns CraftTable Skill \u7684\u9ED8\u8BA4\u76EE\u6807\u76EE\u5F55\u3002\r\n */\r\nexport function defaultSkillPath(agent: AgentName, env: NodeJS.ProcessEnv = process.env): string {\r\n const home = env.USERPROFILE || env.HOME || os.homedir();\r\n if (agent === \"codex\") return path.join(env.CODEX_HOME || path.join(home, \".codex\"), \"skills\", SERVER_NAME);\r\n if (agent === \"claude\") return path.join(home, \".claude\", \"skills\", SERVER_NAME);\r\n return path.join(path.dirname(defaultOpenCodePath(env)), \"skills\", SERVER_NAME);\r\n}\r\n\r\nasync function configureAgentSkill(agent: AgentName, input: ConfigureInput): Promise {\r\n const source = input.skillSource ?? bundledSkillPath(input.cliEntry);\r\n const destination = input.skillPaths?.[agent] ?? defaultSkillPath(agent, input.env);\r\n const sourceFiles = await readSkillDirectory(source, true);\r\n if (!sourceFiles) throw new Error(`Bundled Skill directory is missing: ${source}`);\r\n const existingFiles = await readSkillDirectory(destination, false);\r\n if (existingFiles && skillFilesEqual(existingFiles, sourceFiles)) return \"unchanged\";\r\n\r\n if (existingFiles && !input.force && !input.dryRun) {\r\n const confirm = input.confirm ?? terminalConfirm;\r\n if (!process.stdin.isTTY && !input.confirm) throw new Error(`${agent} already has a different ${SERVER_NAME} Skill; use --force to replace it`);\r\n if (!await confirm(`${agent} already has a different ${SERVER_NAME} Skill. Replace it?`)) {\r\n throw new Error(`${agent} Skill configuration was not changed`);\r\n }\r\n }\r\n\r\n if (input.dryRun) return existingFiles ? \"would-replace\" : \"would-install\";\r\n if (existingFiles) await backupSkillDirectory(destination);\r\n await replaceSkillDirectory(source, destination, Boolean(existingFiles));\r\n return existingFiles ? \"replaced\" : \"installed\";\r\n}\r\n\r\nasync function unconfigureAgentSkill(agent: AgentName, input: ConfigureInput): Promise {\r\n const source = input.skillSource ?? bundledSkillPath(input.cliEntry);\r\n const destination = input.skillPaths?.[agent] ?? defaultSkillPath(agent, input.env);\r\n const existingFiles = await readSkillDirectory(destination, false);\r\n if (!existingFiles) return \"absent\";\r\n const sourceFiles = await readSkillDirectory(source, true);\r\n if (!sourceFiles) throw new Error(`Bundled Skill directory is missing: ${source}`);\r\n const managed = skillFilesEqual(existingFiles, sourceFiles);\r\n if (!managed && !input.force) return input.dryRun ? \"would-preserve\" : \"preserved\";\r\n if (input.dryRun) return \"would-remove\";\r\n if (!managed) await backupSkillDirectory(destination);\r\n await rm(destination, { recursive: true, force: true });\r\n return \"removed\";\r\n}\r\n\r\nasync function readSkillDirectory(directory: string, required: boolean): Promise | undefined> {\r\n const files = new Map();\r\n const walk = async (current: string, relative: string): Promise => {\r\n const entries = await readdir(current, { withFileTypes: true });\r\n entries.sort((left, right) => left.name.localeCompare(right.name));\r\n for (const entry of entries) {\r\n const entryPath = path.join(current, entry.name);\r\n const entryRelative = relative ? path.join(relative, entry.name) : entry.name;\r\n if (entry.isDirectory()) {\r\n await walk(entryPath, entryRelative);\r\n } else if (entry.isFile()) {\r\n files.set(entryRelative, await readFile(entryPath));\r\n } else {\r\n throw new Error(`Skill directory contains an unsupported entry: ${entryPath}`);\r\n }\r\n }\r\n };\r\n try {\r\n await walk(directory, \"\");\r\n } catch (error) {\r\n if (!required && (error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\r\n throw error;\r\n }\r\n if (!files.has(\"SKILL.md\")) throw new Error(`Skill directory is missing SKILL.md: ${directory}`);\r\n return files;\r\n}\r\n\r\nfunction skillFilesEqual(left: Map, right: Map): boolean {\r\n if (left.size !== right.size) return false;\r\n return [...left].every(([name, value]) => right.get(name)?.equals(value) === true);\r\n}\r\n\r\nasync function backupSkillDirectory(directory: string): Promise {\r\n const backup = `${directory}.crafttable-mcp.backup`;\r\n try {\r\n await cp(directory, backup, { recursive: true, force: false, errorOnExist: true });\r\n } catch (error) {\r\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\r\n }\r\n return backup;\r\n}\r\n\r\nasync function replaceSkillDirectory(source: string, destination: string, existed: boolean): Promise {\r\n await mkdir(path.dirname(destination), { recursive: true });\r\n const temporary = `${destination}.${process.pid}.${randomUUID()}.tmp`;\r\n await cp(source, temporary, { recursive: true, force: false, errorOnExist: true });\r\n try {\r\n if (existed) await rm(destination, { recursive: true, force: true });\r\n await rename(temporary, destination);\r\n } catch (error) {\r\n if (existed && !await pathExists(destination)) {\r\n const backup = `${destination}.crafttable-mcp.backup`;\r\n if (await pathExists(backup)) await cp(backup, destination, { recursive: true });\r\n }\r\n throw error;\r\n } finally {\r\n await rm(temporary, { recursive: true, force: true });\r\n }\r\n}\r\n\r\nasync function pathExists(filePath: string): Promise {\r\n try {\r\n await stat(filePath);\r\n return true;\r\n } catch (error) {\r\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\r\n throw error;\r\n }\r\n}\r\n\r\nasync function backupAndAtomicWrite(filePath: string, updated: string, existed: boolean): Promise {\r\n await mkdir(path.dirname(filePath), { recursive: true });\r\n if (existed) {\r\n try {\r\n await copyFile(filePath, `${filePath}.crafttable-mcp.backup`, constants.COPYFILE_EXCL);\r\n } catch (error) {\r\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\r\n }\r\n }\r\n const temporary = `${filePath}.${process.pid}.tmp`;\r\n await writeFile(temporary, updated, \"utf8\");\r\n await rename(temporary, filePath);\r\n}\r\n\r\nasync function readOptionalFile(filePath: string): Promise {\r\n try {\r\n return await readFile(filePath, \"utf8\");\r\n } catch (error) {\r\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\r\n throw error;\r\n }\r\n}\r\n\r\nfunction arraysEqual(value: unknown, expected: string[]): boolean {\r\n return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]);\r\n}\r\n\r\nfunction expandTarget(target: AgentTarget): AgentName[] {\r\n if (target === \"all\") return [\"codex\", \"claude\", \"opencode\"];\r\n if ([\"codex\", \"claude\", \"opencode\"].includes(target)) return [target as AgentName];\r\n throw new Error(\"Agent must be one of: codex, claude, opencode, all\");\r\n}\r\n\r\nasync function terminalConfirm(message: string): Promise {\r\n const readline = createInterface({ input: process.stdin, output: process.stderr });\r\n try {\r\n return /^y(es)?$/i.test((await readline.question(`${message} [y/N] `)).trim());\r\n } finally {\r\n readline.close();\r\n }\r\n}\r\n\r\nasync function requireSuccess(resultPromise: Promise, operation: string): Promise {\r\n const result = await resultPromise;\r\n if (result.code !== 0) throw new Error(`${operation} failed: ${safeCommandError(result)}`);\r\n}\r\n\r\nfunction safeCommandError(result: CommandResult): string {\r\n return (result.stderr || result.stdout || `exit code ${result.code}`).trim();\r\n}\r\n\r\n/** \u4EE5\u9690\u85CF\u4E14\u4E0D\u7ECF\u8FC7 shell \u7684\u5B50\u8FDB\u7A0B\u8FD0\u884C Agent CLI \u914D\u7F6E\u547D\u4EE4\u3002 */\r\nexport class SpawnCommandRunner implements CommandRunner {\r\n /**\r\n * @param command - \u53EF\u6267\u884C\u6587\u4EF6\u540D\u79F0\u6216\u8DEF\u5F84\u3002\r\n * @param args - \u4E0D\u7ECF shell \u89E3\u91CA\u800C\u4F20\u9012\u7684\u539F\u59CB\u53C2\u6570\u5217\u8868\u3002\r\n * @returns \u6355\u83B7\u7684\u9000\u51FA\u7801\u3001\u6807\u51C6\u8F93\u51FA\u4E0E\u6807\u51C6\u9519\u8BEF\u3002\r\n * @throws \u5B50\u8FDB\u7A0B\u65E0\u6CD5\u542F\u52A8\u65F6\u629B\u51FA\u3002\r\n */\r\n run(command: string, args: string[]): Promise {\r\n return new Promise((resolve, reject) => {\r\n const child = spawn(command, args, { shell: false, windowsHide: true, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n child.stdout?.setEncoding(\"utf8\").on(\"data\", (chunk) => { stdout += String(chunk); });\r\n child.stderr?.setEncoding(\"utf8\").on(\"data\", (chunk) => { stderr += String(chunk); });\r\n child.once(\"error\", (error) => reject(new Error(`Could not run ${command}: ${errorMessage(error)}`)));\r\n child.once(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\r\n });\r\n }\r\n}\r\n", "/**\r\n * \u89E3\u6790 MCP \u8FDE\u63A5\u914D\u7F6E\u5E76\u7BA1\u7406 OAuth \u53D1\u73B0\u7F13\u5B58\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { createHash } from \"node:crypto\";\r\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\r\nimport os from \"node:os\";\r\nimport path from \"node:path\";\r\nimport type { OAuthDiscoveryState } from \"@modelcontextprotocol/sdk/client/auth.js\";\r\n\r\n/** CLI \u4F7F\u7528\u7684\u9ED8\u8BA4\u516C\u5F00 Streamable HTTP \u7AEF\u70B9\u3002 */\r\nexport const DEFAULT_MCP_URL = \"https://crafttable.crash.work/mcp\";\r\n/** \u4E3A CLI \u6CE8\u518C\u7684 OAuth \u516C\u5171\u5BA2\u6237\u7AEF\u6807\u8BC6\u3002 */\r\nexport const DEFAULT_CLIENT_ID = \"crafttable-mcp-cli\";\r\n/** \u6CE8\u518C\u4E3A OAuth \u91CD\u5B9A\u5411 URI \u7684\u56FA\u5B9A loopback \u7AEF\u53E3\u3002 */\r\nexport const DEFAULT_CALLBACK_PORT = 48321;\r\n/** \u4EA4\u4E92\u5F0F\u767B\u5F55\u65F6\u8BF7\u6C42\u7684 OAuth \u4F5C\u7528\u57DF\u3002 */\r\nexport const OAUTH_SCOPES = \"openid profile email offline_access\";\r\n\r\n/** MCP \u64CD\u4F5C\u4F7F\u7528\u7684\u5DF2\u89E3\u6790\u8FDE\u63A5\u4E0E OAuth \u56DE\u8C03\u8BBE\u7F6E\u3002 */\r\nexport type ClientOptions = {\r\n url: URL;\r\n clientId: string;\r\n callbackPort: number;\r\n};\r\n\r\n/**\r\n * \u5C06 CLI \u8986\u76D6\u9879\u3001\u73AF\u5883\u53D8\u91CF\u4E0E\u9ED8\u8BA4\u503C\u89E3\u6790\u4E3A\u7ECF\u8FC7\u9A8C\u8BC1\u7684\u8BBE\u7F6E\u3002\r\n *\r\n * @param input - \u53EF\u9009\u547D\u4EE4\u6807\u5FD7\u4E0E\u73AF\u5883\u53D8\u91CF\u6765\u6E90\u3002\r\n * @returns \u89C4\u8303\u5316\u7684\u5BA2\u6237\u7AEF\u8BBE\u7F6E\u3002\r\n * @throws URL\u3001\u5BA2\u6237\u7AEF ID \u6216\u56DE\u8C03\u7AEF\u53E3\u65E0\u6548\u65F6\u629B\u51FA\u3002\r\n */\r\nexport function resolveClientOptions(input: {\r\n url?: string;\r\n clientId?: string;\r\n callbackPort?: string | number;\r\n env?: NodeJS.ProcessEnv;\r\n} = {}): ClientOptions {\r\n const env = input.env ?? process.env;\r\n const url = validateMcpUrl(input.url ?? env.CRAFTTABLE_MCP_URL ?? DEFAULT_MCP_URL);\r\n const clientId = (input.clientId ?? env.CRAFTTABLE_MCP_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID).trim();\r\n if (!clientId) throw new Error(\"OAuth client ID must not be empty\");\r\n const callbackPort = Number(input.callbackPort ?? env.CRAFTTABLE_MCP_OAUTH_CALLBACK_PORT ?? DEFAULT_CALLBACK_PORT);\r\n if (!Number.isInteger(callbackPort) || callbackPort < 1 || callbackPort > 65535) {\r\n throw new Error(\"OAuth callback port must be an integer between 1 and 65535\");\r\n }\r\n return { url, clientId, callbackPort };\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1\u5E76\u89C4\u8303\u5316\u8FDC\u7AEF MCP URL\u3002\r\n *\r\n * \u9664 loopback \u5F00\u53D1\u7AEF\u70B9\u5916\u5FC5\u987B\u4F7F\u7528 HTTPS\uFF1B\u4E3A\u907F\u514D\u6CC4\u9732\u8BA4\u8BC1\u6570\u636E\uFF0C\r\n * URL \u4E2D\u4E0D\u5141\u8BB8\u5305\u542B\u51ED\u636E\u3001\u67E5\u8BE2\u5B57\u7B26\u4E32\u6216\u7247\u6BB5\u3002\r\n *\r\n * @param value - \u7528\u6237\u6216\u73AF\u5883\u53D8\u91CF\u63D0\u4F9B\u7684\u7EDD\u5BF9 URL\u3002\r\n * @returns \u53BB\u9664\u8DEF\u5F84\u672B\u5C3E\u659C\u6760\u540E\u7684\u89C4\u8303\u5316 URL\u3002\r\n * @throws URL \u683C\u5F0F\u9519\u8BEF\u6216\u8FDD\u53CD\u4F20\u8F93\u7B56\u7565\u65F6\u629B\u51FA\u3002\r\n */\r\nexport function validateMcpUrl(value: string): URL {\r\n let url: URL;\r\n try {\r\n url = new URL(value);\r\n } catch {\r\n throw new Error(\"MCP URL must be an absolute URL\");\r\n }\r\n if (url.username || url.password || url.search || url.hash) {\r\n throw new Error(\"MCP URL must not include credentials, query parameters, or a fragment\");\r\n }\r\n const local = url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\" || url.hostname === \"[::1]\";\r\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && local)) {\r\n throw new Error(\"MCP URL must use HTTPS unless it targets localhost\");\r\n }\r\n url.pathname = url.pathname.replace(/\\/+$/, \"\") || \"/\";\r\n return url;\r\n}\r\n\r\n/**\r\n * \u6D3E\u751F\u7A33\u5B9A\u4E14\u4E0D\u5305\u542B\u79D8\u5BC6\u7684\u51ED\u636E\u5B58\u50A8\u8D26\u6237\u952E\u3002\r\n *\r\n * @param options - \u5B9A\u4E49\u8D26\u6237\u7684\u670D\u52A1 URL \u4E0E OAuth \u5BA2\u6237\u7AEF\u6807\u8BC6\u3002\r\n * @returns SHA-256 \u8D26\u6237\u6807\u8BC6\u3002\r\n */\r\nexport function credentialAccount(options: Pick): string {\r\n return createHash(\"sha256\").update(`${options.url.toString()}\\0${options.clientId}`).digest(\"hex\");\r\n}\r\n\r\n/**\r\n * \u89E3\u6790\u4FDD\u5B58\u975E\u654F\u611F\u5BA2\u6237\u7AEF\u72B6\u6001\u7684\u5E73\u53F0\u76EE\u5F55\u3002\r\n *\r\n * @param env - \u7528\u4E8E\u8986\u76D6\u5E73\u53F0\u76EE\u5F55\u7684\u73AF\u5883\u53D8\u91CF\u3002\r\n * @param platform - Node \u5E73\u53F0\u6807\u8BC6\uFF0C\u53EF\u6CE8\u5165\u4EE5\u4FBF\u6D4B\u8BD5\u3002\r\n * @returns \u7EDD\u5BF9\u914D\u7F6E\u76EE\u5F55\u8DEF\u5F84\u3002\r\n */\r\nexport function platformConfigDir(env: NodeJS.ProcessEnv = process.env, platform = process.platform): string {\r\n if (platform === \"win32\") return path.join(env.APPDATA || path.join(os.homedir(), \"AppData\", \"Roaming\"), \"GameCraftTable\", \"mcp-client\");\r\n if (platform === \"darwin\") return path.join(os.homedir(), \"Library\", \"Application Support\", \"GameCraftTable\", \"mcp-client\");\r\n return path.join(env.XDG_CONFIG_HOME || path.join(os.homedir(), \".config\"), \"gamecrafttable\", \"mcp-client\");\r\n}\r\n\r\ntype DiscoveryFile = {\r\n version: 1;\r\n entries: Record;\r\n};\r\n\r\n/** \u5728\u7528\u6237\u975E\u654F\u611F\u914D\u7F6E\u76EE\u5F55\u4E2D\u5B58\u50A8 OAuth \u53D1\u73B0\u72B6\u6001\u3002 */\r\nexport class DiscoveryStore {\r\n readonly filePath: string;\r\n\r\n constructor(configDir = platformConfigDir()) {\r\n this.filePath = path.join(configDir, \"discovery.json\");\r\n }\r\n\r\n /**\r\n * @param account - \u7A33\u5B9A\u8D26\u6237\u6807\u8BC6\u3002\r\n * @returns \u8D26\u6237\u7684\u7F13\u5B58\u53D1\u73B0\u72B6\u6001\uFF1B\u4E0D\u5B58\u5728\u65F6\u8FD4\u56DE `undefined`\u3002\r\n */\r\n async get(account: string): Promise {\r\n return (await this.read()).entries[account];\r\n }\r\n\r\n /**\r\n * \u4F7F\u7528\u539F\u5B50\u6587\u4EF6\u66FF\u6362\u6301\u4E45\u5316\u8D26\u6237\u7684\u53D1\u73B0\u72B6\u6001\u3002\r\n *\r\n * @param account - \u7A33\u5B9A\u8D26\u6237\u6807\u8BC6\u3002\r\n * @param state - MCP OAuth \u5BA2\u6237\u7AEF\u63D0\u4F9B\u7684\u53D1\u73B0\u72B6\u6001\u3002\r\n */\r\n async set(account: string, state: OAuthDiscoveryState): Promise {\r\n const document = await this.read();\r\n document.entries[account] = state;\r\n await atomicWriteJson(this.filePath, document);\r\n }\r\n\r\n /** @param account - \u8981\u5220\u9664\u5176\u72B6\u6001\u7684\u7A33\u5B9A\u8D26\u6237\u6807\u8BC6\u3002 */\r\n async delete(account: string): Promise {\r\n const document = await this.read();\r\n if (!(account in document.entries)) return;\r\n delete document.entries[account];\r\n await atomicWriteJson(this.filePath, document);\r\n }\r\n\r\n private async read(): Promise {\r\n try {\r\n const value = JSON.parse(await readFile(this.filePath, \"utf8\")) as Partial;\r\n return { version: 1, entries: value.entries && typeof value.entries === \"object\" ? value.entries : {} };\r\n } catch (error) {\r\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { version: 1, entries: {} };\r\n throw new Error(`Could not read OAuth discovery cache: ${errorMessage(error)}`);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * \u5148\u5199\u5165\u79C1\u6709\u4E34\u65F6\u6587\u4EF6\uFF0C\u518D\u901A\u8FC7\u539F\u5B50\u91CD\u547D\u540D\u66FF\u6362 JSON \u6587\u6863\u3002\r\n *\r\n * @param filePath - \u76EE\u6807\u6587\u4EF6\u8DEF\u5F84\u3002\r\n * @param value - \u53EF\u5E8F\u5217\u5316\u4E3A JSON \u7684\u503C\u3002\r\n * @returns \u66FF\u6362\u5B8C\u6210\u540E\u89E3\u6790\u7684 Promise\u3002\r\n */\r\nexport async function atomicWriteJson(filePath: string, value: unknown): Promise {\r\n await mkdir(path.dirname(filePath), { recursive: true });\r\n const temporary = `${filePath}.${process.pid}.tmp`;\r\n await writeFile(temporary, `${JSON.stringify(value, null, 2)}\\n`, { encoding: \"utf8\", mode: 0o600 });\r\n await rename(temporary, filePath);\r\n}\r\n\r\n/** \u5C06\u672A\u77E5\u7684\u629B\u51FA\u503C\u8F6C\u6362\u4E3A\u53EF\u663E\u793A\u7684\u9519\u8BEF\u6D88\u606F\u3002 */\r\nexport function errorMessage(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error);\r\n}\r\n", "/**\r\n * \u5C06\u8FDC\u7AEF CraftTable MCP \u670D\u52A1\u6865\u63A5\u4E3A\u672C\u5730 stdio MCP \u670D\u52A1\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\r\nimport {\r\n CallToolRequestSchema,\r\n ListResourcesRequestSchema,\r\n ListResourceTemplatesRequestSchema,\r\n ListToolsRequestSchema,\r\n ReadResourceRequestSchema,\r\n} from \"@modelcontextprotocol/sdk/types.js\";\r\nimport type { ClientOptions } from \"./config.js\";\r\nimport { DiscoveryStore } from \"./config.js\";\r\nimport type { TokenStore } from \"./credentials.js\";\r\nimport { connectRemote } from \"./remote.js\";\r\n\r\n/**\r\n * \u8FDE\u63A5\u8FDC\u7AEF CraftTable MCP \u7AEF\u70B9\uFF0C\u5E76\u901A\u8FC7 stdio \u63D0\u4F9B\u670D\u52A1\u3002\r\n *\r\n * \u6865\u63A5\u4F1A\u4E00\u76F4\u8FD0\u884C\u5230 stdio \u4F20\u8F93\u5173\u95ED\u6216\u8FDB\u7A0B\u6536\u5230 SIGINT/SIGTERM\uFF1B\r\n * \u5173\u95ED\u65F6\u4F1A\u540C\u65F6\u91CA\u653E\u672C\u5730\u548C\u8FDC\u7AEF\u8FDE\u63A5\u3002\r\n *\r\n * @param options - \u8FDC\u7AEF MCP \u4E0E OAuth \u5BA2\u6237\u7AEF\u8BBE\u7F6E\u3002\r\n * @param tokenStore - OAuth \u4EE4\u724C\u7684\u5B89\u5168\u5B58\u50A8\u3002\r\n * @param discoveryStore - \u6301\u4E45\u5316\u7684 OAuth \u53D1\u73B0\u7F13\u5B58\u3002\r\n * @param serviceToken - OAuth \u4EE4\u724C\u4E0D\u5B58\u5728\u65F6\u53EF\u4F7F\u7528\u7684\u65E7\u7248 Bearer \u4EE4\u724C\u3002\r\n * @returns \u6865\u63A5\u505C\u6B62\u540E\u5B8C\u6210\u7684 Promise\u3002\r\n * @throws \u65E0\u6CD5\u5B8C\u6210\u9274\u6743\u6216\u5EFA\u7ACB\u4EFB\u4E00 MCP \u4F20\u8F93\u65F6\u629B\u51FA\u3002\r\n */\r\nexport async function serveBridge(\r\n options: ClientOptions,\r\n tokenStore: TokenStore,\r\n discoveryStore: DiscoveryStore,\r\n serviceToken = process.env.CRAFTTABLE_MCP_TOKEN?.trim(),\r\n): Promise {\r\n const upstream = await connectRemote(options, tokenStore, discoveryStore, serviceToken);\r\n if (upstream.authentication === \"service-token\") {\r\n process.stderr.write(\"CraftTable MCP: using the legacy service-account token path.\\n\");\r\n }\r\n const server = createProxyServer(upstream.client);\r\n const transport = new StdioServerTransport();\r\n const close = async (): Promise => {\r\n await server.close().catch(() => undefined);\r\n await upstream.close().catch(() => undefined);\r\n };\r\n process.once(\"SIGINT\", () => void close());\r\n process.once(\"SIGTERM\", () => void close());\r\n try {\r\n await server.connect(transport);\r\n } catch (error) {\r\n await close();\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u4E00\u4E2A\u5C06\u652F\u6301\u7684\u8BF7\u6C42\u8F6C\u53D1\u5230\u8FDC\u7AEF\u7684\u672C\u5730 MCP \u670D\u52A1\u3002\r\n *\r\n * @param upstream - \u4EE3\u7406\u6240\u9700\u7684\u6700\u5C0F\u8FDC\u7AEF MCP \u5BA2\u6237\u7AEF\u63A5\u53E3\u3002\r\n * @returns \u5C1A\u672A\u8FDE\u63A5\u3001\u53EF\u7ED1\u5B9A\u670D\u52A1\u4F20\u8F93\u7684 MCP \u670D\u52A1\u5B9E\u4F8B\u3002\r\n */\r\nexport function createProxyServer(upstream: {\r\n listTools: (params?: { cursor?: string }) => Promise;\r\n callTool: (params: { name: string; arguments?: Record }) => Promise;\r\n listResources: (params?: { cursor?: string }) => Promise;\r\n listResourceTemplates: (params?: { cursor?: string }) => Promise;\r\n readResource: (params: { uri: string }) => Promise;\r\n}): Server {\r\n const server = new Server({ name: \"crafttable-mcp-stdio-bridge\", version: \"0.1.3\" }, {\r\n capabilities: {\r\n tools: {},\r\n resources: {},\r\n },\r\n });\r\n server.setRequestHandler(ListToolsRequestSchema, (request) => upstream.listTools(request.params) as never);\r\n server.setRequestHandler(CallToolRequestSchema, (request) => upstream.callTool(request.params) as never);\r\n server.setRequestHandler(ListResourcesRequestSchema, (request) => upstream.listResources(request.params) as never);\r\n server.setRequestHandler(ListResourceTemplatesRequestSchema, (request) => upstream.listResourceTemplates(request.params) as never);\r\n server.setRequestHandler(ReadResourceRequestSchema, (request) => upstream.readResource(request.params) as never);\r\n return server;\r\n}\r\n", "/**\r\n * \u5EFA\u7ACB\u8FDC\u7AEF MCP \u8FDE\u63A5\uFF0C\u5E76\u7F16\u6392 OAuth \u767B\u5F55\u3001\u6CE8\u9500\u4E0E\u5143\u6570\u636E\u53D1\u73B0\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\r\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\r\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\r\nimport type { OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\r\nimport open from \"open\";\r\nimport { OAuthCallbackServer } from \"./callbackServer.js\";\r\nimport type { ClientOptions } from \"./config.js\";\r\nimport { credentialAccount, DiscoveryStore, errorMessage } from \"./config.js\";\r\nimport type { TokenStore } from \"./credentials.js\";\r\nimport { CraftTableOAuthProvider } from \"./oauthProvider.js\";\r\n\r\n/** \u5DF2\u8FDE\u63A5\u7684\u8FDC\u7AEF MCP \u5BA2\u6237\u7AEF\u53CA\u5176\u6240\u9009\u9274\u6743\u6A21\u5F0F\u3002 */\r\nexport type RemoteConnection = {\r\n client: Client;\r\n transport: StreamableHTTPClientTransport;\r\n authentication: \"oauth\" | \"service-token\";\r\n close: () => Promise;\r\n};\r\n\r\n/**\r\n * \u4F7F\u7528\u5DF2\u4FDD\u5B58 OAuth \u4EE4\u724C\u6216\u65E7\u7248\u4EE4\u724C\u8FDE\u63A5\u8FDC\u7AEF MCP \u670D\u52A1\u3002\r\n *\r\n * \u5DF2\u4FDD\u5B58\u7684 OAuth \u51ED\u636E\u4F18\u5148\u4E8E\u670D\u52A1\u4EE4\u724C\u3002\r\n *\r\n * @param options - \u8FDC\u7AEF MCP \u4E0E OAuth \u5BA2\u6237\u7AEF\u8BBE\u7F6E\u3002\r\n * @param tokenStore - OAuth \u4EE4\u724C\u5B89\u5168\u6301\u4E45\u5316\u63A5\u53E3\u3002\r\n * @param discoveryStore - OAuth \u53D1\u73B0\u7F13\u5B58\u3002\r\n * @param serviceToken - \u53EF\u9009\u7684\u65E7\u7248 Bearer \u4EE4\u724C\u540E\u5907\u9879\u3002\r\n * @returns \u5DF2\u8FDE\u63A5\u5BA2\u6237\u7AEF\u4E0E\u53EF\u91CD\u590D\u8C03\u7528\u7684\u5173\u95ED\u8FB9\u754C\u3002\r\n * @throws \u6CA1\u6709\u53EF\u7528\u9274\u6743\u65B9\u5F0F\u6216\u8FDC\u7AEF\u8FDE\u63A5\u5931\u8D25\u65F6\u629B\u51FA\u3002\r\n */\r\nexport async function connectRemote(\r\n options: ClientOptions,\r\n tokenStore: TokenStore,\r\n discoveryStore: DiscoveryStore,\r\n serviceToken = process.env.CRAFTTABLE_MCP_TOKEN?.trim(),\r\n): Promise {\r\n const tokens = await tokenStore.get(credentialAccount(options));\r\n if (tokens) {\r\n const provider = new CraftTableOAuthProvider(options, tokenStore, discoveryStore, () => {\r\n throw new Error(\"OAuth login is required; run `crafttable-mcp login`\");\r\n });\r\n return connectWithTransport(options, new StreamableHTTPClientTransport(options.url, { authProvider: provider }), \"oauth\");\r\n }\r\n if (serviceToken) {\r\n return connectWithTransport(options, new StreamableHTTPClientTransport(options.url, {\r\n requestInit: { headers: { authorization: `Bearer ${serviceToken}` } },\r\n }), \"service-token\");\r\n }\r\n throw new Error(\"Not logged in; run `crafttable-mcp login` or set CRAFTTABLE_MCP_TOKEN for the legacy service-account path\");\r\n}\r\n\r\n/**\r\n * \u5B8C\u6210 OAuth \u6388\u6743\u7801\u6D41\u7A0B\u5E76\u9A8C\u8BC1 MCP \u8FDE\u901A\u6027\u3002\r\n *\r\n * @param options - \u8FDC\u7AEF MCP \u4E0E loopback \u56DE\u8C03\u8BBE\u7F6E\u3002\r\n * @param tokenStore - OAuth \u4EE4\u724C\u7684\u5B89\u5168\u76EE\u6807\u5B58\u50A8\u3002\r\n * @param discoveryStore - \u6301\u4E45\u5316 OAuth \u53D1\u73B0\u7F13\u5B58\u3002\r\n * @param input - \u6D4F\u89C8\u5668\u884C\u4E3A\u3001\u56DE\u8C03\u8D85\u65F6\u4E0E\u72B6\u6001\u8F93\u51FA\u94A9\u5B50\u3002\r\n * @returns \u51ED\u636E\u6B64\u524D\u662F\u5426\u6709\u6548\uFF0C\u4EE5\u53CA\u5F53\u524D\u53EF\u89C1\u5DE5\u5177\u6570\u91CF\u3002\r\n * @throws OAuth \u5143\u6570\u636E\u3001\u6D4F\u89C8\u5668\u56DE\u8C03\u3001\u4EE4\u724C\u4EA4\u6362\u6216 MCP \u8FDE\u63A5\u5931\u8D25\u65F6\u629B\u51FA\u3002\r\n */\r\nexport async function loginRemote(\r\n options: ClientOptions,\r\n tokenStore: TokenStore,\r\n discoveryStore: DiscoveryStore,\r\n input: { browser: boolean; timeoutMs?: number; writeLine?: (value: string) => void } = { browser: true },\r\n): Promise<{ alreadyAuthenticated: boolean; toolCount: number }> {\r\n const writeLine = input.writeLine ?? ((value) => process.stderr.write(`${value}\\n`));\r\n await requireOAuthProtectedResourceMetadata(options.url);\r\n let authorizationUrl: URL | undefined;\r\n const provider = new CraftTableOAuthProvider(options, tokenStore, discoveryStore, async (url) => {\r\n authorizationUrl = url;\r\n if (!input.browser) {\r\n writeLine(`Open this URL to log in:\\n${url.toString()}`);\r\n return;\r\n }\r\n try {\r\n await open(url.toString(), { wait: false });\r\n writeLine(\"Opened the system browser for CraftTable login.\");\r\n } catch (error) {\r\n writeLine(`Could not open the browser (${errorMessage(error)}). Open this URL manually:\\n${url.toString()}`);\r\n }\r\n });\r\n const callback = new OAuthCallbackServer(options.callbackPort, (state) => provider.validateState(state));\r\n await callback.listen();\r\n const firstClient = new Client({ name: \"crafttable-mcp-cli\", version: \"0.1.3\" });\r\n const firstTransport = new StreamableHTTPClientTransport(options.url, { authProvider: provider });\r\n try {\r\n try {\r\n await firstClient.connect(firstTransport);\r\n const tools = await firstClient.listTools();\r\n return { alreadyAuthenticated: true, toolCount: tools.tools.length };\r\n } catch (error) {\r\n if (!(error instanceof UnauthorizedError) && !authorizationUrl) throw error;\r\n const { code } = await callback.wait(input.timeoutMs);\r\n await firstTransport.finishAuth(code);\r\n }\r\n } finally {\r\n await firstClient.close().catch(() => undefined);\r\n await callback.close().catch(() => undefined);\r\n }\r\n\r\n const connection = await connectRemote(options, tokenStore, discoveryStore, \"\");\r\n try {\r\n const tools = await connection.client.listTools();\r\n return { alreadyAuthenticated: false, toolCount: tools.tools.length };\r\n } finally {\r\n await connection.close();\r\n }\r\n}\r\n\r\n/**\r\n * \u6309\u9700\u64A4\u9500\u5DF2\u4FDD\u5B58\u7684 OAuth \u51ED\u636E\uFF0C\u7136\u540E\u6E05\u7406\u672C\u5730 OAuth \u72B6\u6001\u3002\r\n *\r\n * \u8FDC\u7AEF\u64A4\u9500\u5931\u8D25\u65F6\u4F1A\u4FDD\u7559\u672C\u5730\u51ED\u636E\uFF0C\u4EE5\u4FBF\u91CD\u8BD5\u3002\r\n *\r\n * @param options - \u8FDC\u7AEF MCP \u4E0E OAuth \u5BA2\u6237\u7AEF\u8BBE\u7F6E\u3002\r\n * @param tokenStore - OAuth \u4EE4\u724C\u5B89\u5168\u6301\u4E45\u5316\u63A5\u53E3\u3002\r\n * @param discoveryStore - OAuth \u53D1\u73B0\u7F13\u5B58\u3002\r\n * @param localOnly - \u4E3A `true` \u65F6\u8DF3\u8FC7\u8FDC\u7AEF\u64A4\u9500\u3002\r\n * @param fetchFn - HTTP \u5B9E\u73B0\uFF0C\u53EF\u6CE8\u5165\u4EE5\u4FBF\u6D4B\u8BD5\u3002\r\n * @returns \u51ED\u636E\u662F\u5426\u5B58\u5728\uFF0C\u4EE5\u53CA\u662F\u5426\u6210\u529F\u6267\u884C\u64A4\u9500\u3002\r\n * @throws \u53D1\u73B0\u6D41\u7A0B\u6216\u8FDC\u7AEF\u4EE4\u724C\u64A4\u9500\u5931\u8D25\u65F6\u629B\u51FA\u3002\r\n */\r\nexport async function logoutRemote(\r\n options: ClientOptions,\r\n tokenStore: TokenStore,\r\n discoveryStore: DiscoveryStore,\r\n localOnly: boolean,\r\n fetchFn: typeof fetch = fetch,\r\n): Promise<{ hadCredential: boolean; revoked: boolean }> {\r\n const account = credentialAccount(options);\n if (localOnly) {\n const hadCredential = await tokenStore.delete(account);\n await discoveryStore.delete(account);\n return { hadCredential, revoked: false };\n }\n const tokens = await tokenStore.get(account);\n if (tokens) {\n const endpoint = await discoverRevocationEndpoint(options.url, fetchFn);\r\n await revokeTokens(endpoint, options.clientId, tokens, fetchFn);\r\n }\r\n await tokenStore.delete(account);\r\n await discoveryStore.delete(account);\r\n return { hadCredential: Boolean(tokens), revoked: Boolean(tokens) };\n}\r\n\r\nasync function connectWithTransport(\r\n options: ClientOptions,\r\n transport: StreamableHTTPClientTransport,\r\n authentication: RemoteConnection[\"authentication\"],\r\n): Promise {\r\n const client = new Client({ name: \"crafttable-mcp-cli\", version: \"0.1.3\" });\r\n try {\r\n await client.connect(transport);\r\n } catch (error) {\r\n await client.close().catch(() => undefined);\r\n if (error instanceof UnauthorizedError) throw new Error(\"OAuth login is required; run `crafttable-mcp login`\");\r\n throw error;\r\n }\r\n return {\r\n client,\r\n transport,\r\n authentication,\r\n close: () => client.close(),\r\n };\r\n}\r\n\r\nasync function discoverRevocationEndpoint(resource: URL, fetchFn: typeof fetch): Promise {\r\n const protectedMetadata = await requireOAuthProtectedResourceMetadata(resource, fetchFn);\r\n const issuer = Array.isArray(protectedMetadata.authorization_servers)\r\n ? protectedMetadata.authorization_servers.find((value): value is string => typeof value === \"string\")\r\n : undefined;\r\n if (!issuer) throw new Error(\"OAuth protected-resource metadata has no authorization server\");\r\n const issuerUrl = new URL(issuer);\r\n const candidates = [\r\n new URL(`${issuerUrl.toString().replace(/\\/$/, \"\")}/.well-known/openid-configuration`),\r\n new URL(`/.well-known/openid-configuration${issuerUrl.pathname === \"/\" ? \"\" : issuerUrl.pathname}`, issuerUrl.origin),\r\n ];\r\n for (const candidate of candidates) {\r\n const response = await fetchFn(candidate, { headers: { accept: \"application/json\" } });\r\n if (!response.ok) continue;\r\n try {\r\n const metadata = await response.json() as { revocation_endpoint?: unknown };\r\n if (typeof metadata.revocation_endpoint === \"string\") return new URL(metadata.revocation_endpoint);\r\n } catch {\r\n // \u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u7B26\u5408\u6807\u51C6\u7684\u53D1\u73B0\u4F4D\u7F6E\u3002\r\n }\r\n }\r\n throw new Error(\"OAuth authorization server does not advertise a revocation endpoint\");\r\n}\r\n\r\n/**\r\n * \u52A0\u8F7D\u5E76\u9A8C\u8BC1 MCP \u7AEF\u70B9\u7684 RFC 9728 \u53D7\u4FDD\u62A4\u8D44\u6E90\u5143\u6570\u636E\u3002\r\n *\r\n * @param resource - \u8981\u53D1\u73B0\u5176\u5143\u6570\u636E\u7684 MCP \u8D44\u6E90 URL\u3002\r\n * @param fetchFn - HTTP \u5B9E\u73B0\uFF0C\u53EF\u6CE8\u5165\u4EE5\u4FBF\u6D4B\u8BD5\u3002\r\n * @returns \u5305\u542B\u5DF2\u516C\u5E03\u6388\u6743\u670D\u52A1\u5668\u7684\u89E3\u6790\u540E\u5143\u6570\u636E\u3002\r\n * @throws \u5143\u6570\u636E\u4E0D\u53EF\u8FBE\u3001\u54CD\u5E94\u5931\u8D25\u3001\u4E0D\u662F JSON \u6216\u683C\u5F0F\u9519\u8BEF\u65F6\u629B\u51FA\u3002\r\n */\r\nexport async function requireOAuthProtectedResourceMetadata(\r\n resource: URL,\r\n fetchFn: typeof fetch = fetch,\r\n): Promise<{ authorization_servers?: unknown }> {\r\n const metadataUrl = new URL(`/.well-known/oauth-protected-resource${resource.pathname === \"/\" ? \"\" : resource.pathname}`, resource.origin);\r\n let response: Response;\r\n try {\r\n response = await fetchFn(metadataUrl, { headers: { accept: \"application/json\" } });\r\n } catch (error) {\r\n throw new Error(`Could not reach OAuth protected-resource metadata at ${metadataUrl}: ${errorMessage(error)}`);\r\n }\r\n const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";\r\n if (!response.ok) {\r\n throw new Error(`OAuth is not enabled for ${resource}; protected-resource metadata at ${metadataUrl} returned HTTP ${response.status}`);\r\n }\r\n if (!contentType.includes(\"application/json\")) {\r\n throw new Error(`OAuth is not enabled for ${resource}; protected-resource metadata at ${metadataUrl} returned ${contentType || \"an unknown content type\"} instead of JSON`);\r\n }\r\n try {\r\n return await response.json() as { authorization_servers?: unknown };\r\n } catch {\r\n throw new Error(`OAuth protected-resource metadata at ${metadataUrl} is not valid JSON`);\r\n }\r\n}\r\n\r\nasync function revokeTokens(endpoint: URL, clientId: string, tokens: OAuthTokens, fetchFn: typeof fetch): Promise {\r\n const candidates = [tokens.refresh_token\r\n ? { token: tokens.refresh_token, hint: \"refresh_token\" }\r\n : { token: tokens.access_token, hint: \"access_token\" }];\r\n for (const value of candidates) {\r\n const response = await fetchFn(endpoint, {\r\n method: \"POST\",\r\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\r\n body: new URLSearchParams({ token: value.token, token_type_hint: value.hint, client_id: clientId }),\r\n });\r\n if (!response.ok) throw new Error(`OAuth token revocation returned HTTP ${response.status}; local credentials were retained`);\r\n }\r\n}\r\n", "/**\r\n * \u5728 loopback \u5730\u5740\u63A5\u6536 OAuth \u6388\u6743\u7801\u56DE\u8C03\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { createServer, type Server } from \"node:http\";\r\nimport type { AddressInfo } from \"node:net\";\r\n\r\n/** loopback \u56DE\u8C03\u6536\u5230\u7684\u6210\u529F\u6388\u6743\u54CD\u5E94\u3002 */\r\nexport type OAuthCallback = { code: string };\r\n\r\n/**\r\n * \u5728 loopback \u63A5\u53E3\u63A5\u6536\u4E00\u6B21 OAuth \u6388\u6743\u7801\u56DE\u8C03\u3002\r\n *\r\n * \u670D\u52A1\u4F1A\u62D2\u7EDD\u5305\u542B OAuth \u9519\u8BEF\u3001\u72B6\u6001\u65E0\u6548\u6216\u7F3A\u5C11\u6388\u6743\u7801\u7684\u56DE\u8C03\u3002\r\n * \u767B\u5F55\u5C1D\u8BD5\u5B8C\u6210\u540E\u5E94\u8C03\u7528 {@link close}\u3002\r\n */\r\nexport class OAuthCallbackServer {\r\n private server?: Server;\r\n private resolveResult?: (value: OAuthCallback) => void;\r\n private rejectResult?: (reason: Error) => void;\r\n private readonly result = new Promise((resolve, reject) => {\r\n this.resolveResult = resolve;\r\n this.rejectResult = reject;\r\n });\r\n\r\n constructor(\r\n private readonly port: number,\r\n private readonly validateState: (state: string | null) => boolean,\r\n ) {\r\n // \u6D4F\u89C8\u5668\u56DE\u8C03\u53EF\u80FD\u65E9\u4E8E loginRemote \u5F00\u59CB\u7B49\u5F85\uFF1B\u9884\u5148\u5904\u7406\u62D2\u7EDD\uFF0C\r\n // \u540C\u65F6\u4E3A wait() \u4FDD\u7559\u539F\u59CB Promise\u3002\r\n void this.result.catch(() => undefined);\r\n }\r\n\r\n /**\r\n * \u5728\u914D\u7F6E\u7684\u56FA\u5B9A\u56DE\u8C03\u7AEF\u53E3\u76D1\u542C `127.0.0.1`\u3002\r\n *\r\n * @returns \u7AEF\u53E3\u7ED1\u5B9A\u5B8C\u6210\u540E\u89E3\u6790\u7684 Promise\u3002\r\n * @throws \u670D\u52A1\u5DF2\u8FD0\u884C\u6216\u7AEF\u53E3\u65E0\u6CD5\u7ED1\u5B9A\u65F6\u629B\u51FA\u3002\r\n */\r\n async listen(): Promise {\r\n if (this.server) throw new Error(\"OAuth callback server is already running\");\r\n this.server = createServer((request, response) => {\r\n const url = new URL(request.url ?? \"/\", `http://127.0.0.1:${this.port}`);\r\n if (request.method !== \"GET\" || url.pathname !== \"/oauth/callback\") {\r\n response.writeHead(404, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"Not found\");\r\n return;\r\n }\r\n const oauthError = url.searchParams.get(\"error\");\r\n if (oauthError) {\r\n response.writeHead(400, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"OAuth login failed. Return to the terminal.\");\r\n this.rejectResult?.(new Error(`OAuth authorization failed: ${oauthError}`));\r\n return;\r\n }\r\n if (!this.validateState(url.searchParams.get(\"state\"))) {\r\n response.writeHead(400, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"OAuth state did not match. Return to the terminal.\");\r\n this.rejectResult?.(new Error(\"OAuth callback state did not match\"));\r\n return;\r\n }\r\n const code = url.searchParams.get(\"code\");\r\n if (!code) {\r\n response.writeHead(400, { \"content-type\": \"text/plain; charset=utf-8\" }).end(\"OAuth authorization code is missing.\");\r\n this.rejectResult?.(new Error(\"OAuth callback did not include an authorization code\"));\r\n return;\r\n }\r\n response.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" }).end(\"CraftTable MCP

Login complete. You can close this window.

\");\r\n this.resolveResult?.({ code });\r\n });\r\n await new Promise((resolve, reject) => {\r\n const onError = (error: Error) => reject(new Error(`Could not listen on OAuth callback port ${this.port}: ${error.message}`));\r\n this.server!.once(\"error\", onError);\r\n this.server!.listen(this.port, \"127.0.0.1\", () => {\r\n this.server!.off(\"error\", onError);\r\n resolve();\r\n });\r\n });\r\n const address = this.server.address() as AddressInfo | null;\r\n if (!address || address.port !== this.port) throw new Error(`OAuth callback server did not bind port ${this.port}`);\r\n }\r\n\r\n /**\r\n * \u7B49\u5F85\u6709\u6548\u56DE\u8C03\u6216\u8FBE\u5230\u914D\u7F6E\u7684\u8D85\u65F6\u65F6\u95F4\u3002\r\n *\r\n * @param timeoutMs - \u6700\u5927\u7B49\u5F85\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\u3002\r\n * @returns \u7ECF\u8FC7\u9A8C\u8BC1\u7684\u56DE\u8C03\u4E2D\u7684\u6388\u6743\u7801\u3002\r\n * @throws \u6388\u6743\u5931\u8D25\u3001\u72B6\u6001\u9A8C\u8BC1\u5931\u8D25\u3001\u7F3A\u5C11\u6388\u6743\u7801\u6216\u7B49\u5F85\u8D85\u65F6\u65F6\u629B\u51FA\u3002\r\n */\r\n async wait(timeoutMs = 10 * 60 * 1000): Promise {\r\n let timeout: NodeJS.Timeout | undefined;\r\n try {\r\n return await Promise.race([\r\n this.result,\r\n new Promise((_resolve, reject) => {\r\n timeout = setTimeout(() => reject(new Error(\"OAuth callback timed out\")), timeoutMs);\r\n }),\r\n ]);\r\n } finally {\r\n if (timeout) clearTimeout(timeout);\r\n }\r\n }\r\n\r\n /**\r\n * \u505C\u6B62\u63A5\u6536\u56DE\u8C03\uFF1B\u5BF9\u5DF2\u505C\u6B62\u7684\u670D\u52A1\u8C03\u7528\u6B64\u65B9\u6CD5\u4E0D\u4F1A\u4EA7\u751F\u4EFB\u4F55\u64CD\u4F5C\u3002\r\n *\r\n * @returns HTTP \u670D\u52A1\u5173\u95ED\u540E\u89E3\u6790\u7684 Promise\u3002\r\n */\r\n async close(): Promise {\r\n if (!this.server) return;\r\n const server = this.server;\r\n this.server = undefined;\r\n await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));\r\n }\r\n}\r\n", "/**\r\n * \u5B9E\u73B0 MCP SDK \u6240\u9700\u7684 CraftTable OAuth \u63D0\u4F9B\u8005\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\r\nimport type { OAuthClientProvider, OAuthDiscoveryState } from \"@modelcontextprotocol/sdk/client/auth.js\";\r\nimport type { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\r\nimport type { ClientOptions } from \"./config.js\";\r\nimport { credentialAccount, DiscoveryStore, OAUTH_SCOPES } from \"./config.js\";\r\nimport type { TokenStore } from \"./credentials.js\";\r\n\r\n/**\r\n * \u7531\u5B89\u5168\u4EE4\u724C\u5B58\u50A8\u548C\u53D1\u73B0\u7F13\u5B58\u652F\u6301\u7684 MCP SDK OAuth \u63D0\u4F9B\u8005\u3002\r\n *\r\n * \u6BCF\u4E2A\u5B9E\u4F8B\u62E5\u6709\u72EC\u7ACB\u7684\u968F\u673A OAuth state \u548C\u5185\u5B58 PKCE verifier\uFF0C\r\n * \u9632\u6B62\u8FD9\u4E9B\u503C\u5728\u4E0D\u540C\u767B\u5F55\u5C1D\u8BD5\u95F4\u5171\u4EAB\u3002\r\n */\r\nexport class CraftTableOAuthProvider implements OAuthClientProvider {\r\n readonly redirectUrl: URL;\r\n readonly clientMetadata: OAuthClientMetadata;\r\n private readonly account: string;\r\n private readonly expectedState: string;\r\n private codeVerifierValue?: string;\r\n\r\n /**\r\n * @param options - \u8FDC\u7AEF\u7AEF\u70B9\u4E0E\u5DF2\u6CE8\u518C\u516C\u5171\u5BA2\u6237\u7AEF\u8BBE\u7F6E\u3002\r\n * @param tokenStore - OAuth \u4EE4\u724C\u5B89\u5168\u6301\u4E45\u5316\u63A5\u53E3\u3002\r\n * @param discoveryStore - \u6388\u6743\u670D\u52A1\u5668\u53D1\u73B0\u72B6\u6001\u7F13\u5B58\u3002\r\n * @param onRedirect - \u6253\u5F00\u6216\u663E\u793A\u6388\u6743 URL \u7684\u5904\u7406\u5668\u3002\r\n * @param state - \u9884\u671F\u56DE\u8C03\u72B6\u6001\uFF0C\u53EF\u6CE8\u5165\u4EE5\u5B9E\u73B0\u786E\u5B9A\u6027\u6D4B\u8BD5\u3002\r\n */\r\n constructor(\r\n private readonly options: ClientOptions,\r\n private readonly tokenStore: TokenStore,\r\n private readonly discoveryStore: DiscoveryStore,\r\n private readonly onRedirect: (url: URL) => void | Promise,\r\n state = randomBytes(32).toString(\"base64url\"),\r\n ) {\r\n this.redirectUrl = new URL(`http://127.0.0.1:${options.callbackPort}/oauth/callback`);\r\n this.clientMetadata = {\r\n client_name: \"CraftTable MCP CLI\",\r\n redirect_uris: [this.redirectUrl.toString()],\r\n grant_types: [\"authorization_code\", \"refresh_token\"],\r\n response_types: [\"code\"],\r\n token_endpoint_auth_method: \"none\",\r\n scope: OAUTH_SCOPES,\r\n };\r\n this.account = credentialAccount(options);\r\n this.expectedState = state;\r\n }\r\n\r\n /** @returns \u6388\u6743\u56DE\u8C03\u5FC5\u987B\u643A\u5E26\u7684 state \u503C\u3002 */\r\n state(): string {\r\n return this.expectedState;\r\n }\r\n\r\n /**\r\n * \u5728\u957F\u5EA6\u4E00\u81F4\u65F6\u4EE5\u5E38\u91CF\u65F6\u95F4\u6BD4\u8F83\u56DE\u8C03 state\u3002\r\n *\r\n * @param value - \u4ECE loopback \u56DE\u8C03\u6536\u5230\u7684 state\u3002\r\n * @returns \u56DE\u8C03\u662F\u5426\u5C5E\u4E8E\u672C\u6B21\u6388\u6743\u5C1D\u8BD5\u3002\r\n */\r\n validateState(value: string | null): boolean {\r\n if (!value) return false;\r\n const expected = Buffer.from(this.expectedState);\r\n const actual = Buffer.from(value);\r\n return expected.length === actual.length && timingSafeEqual(expected, actual);\r\n }\r\n\r\n /** @returns MCP SDK \u4F7F\u7528\u7684\u9759\u6001\u516C\u5171\u5BA2\u6237\u7AEF\u6CE8\u518C\u4FE1\u606F\u3002 */\r\n clientInformation(): OAuthClientInformationMixed {\r\n return { client_id: this.options.clientId };\r\n }\r\n\r\n /** @returns \u5F53\u524D\u670D\u52A1/\u5BA2\u6237\u7AEF\u7EC4\u5408\u5DF2\u4FDD\u5B58\u7684 OAuth \u4EE4\u724C\u3002 */\r\n tokens(): Promise {\r\n return this.tokenStore.get(this.account);\r\n }\r\n\r\n /** \u6301\u4E45\u5316\u5237\u65B0\u6216\u65B0\u7B7E\u53D1\u7684 OAuth \u4EE4\u724C\u96C6\u3002 */\r\n saveTokens(tokens: OAuthTokens): Promise {\r\n return this.tokenStore.set(this.account, tokens);\r\n }\r\n\r\n /** \u5C06\u6388\u6743 URL \u4EA4\u7ED9 CLI \u7684\u6D4F\u89C8\u5668\u6216\u624B\u52A8\u767B\u5F55\u5904\u7406\u5668\u3002 */\r\n redirectToAuthorization(url: URL): void | Promise {\r\n return this.onRedirect(url);\r\n }\r\n\r\n /** \u5728\u672C\u6B21\u767B\u5F55\u5C1D\u8BD5\u671F\u95F4\u5C06 PKCE verifier \u4FDD\u5B58\u5728\u5185\u5B58\u4E2D\u3002 */\r\n saveCodeVerifier(codeVerifier: string): void {\r\n this.codeVerifierValue = codeVerifier;\r\n }\r\n\r\n /**\r\n * @returns \u4E3A\u5F53\u524D\u6388\u6743\u5C1D\u8BD5\u4FDD\u5B58\u7684 PKCE verifier\u3002\r\n * @throws \u5C1A\u672A\u751F\u6210 verifier \u6216\u5176\u5DF2\u5931\u6548\u65F6\u629B\u51FA\u3002\r\n */\r\n codeVerifier(): string {\r\n if (!this.codeVerifierValue) throw new Error(\"OAuth PKCE verifier is missing or expired\");\r\n return this.codeVerifierValue;\r\n }\r\n\r\n /** @returns \u5F53\u524D\u670D\u52A1/\u5BA2\u6237\u7AEF\u7EC4\u5408\u7684 OAuth \u53D1\u73B0\u7F13\u5B58\u3002 */\r\n discoveryState(): Promise {\r\n return this.discoveryStore.get(this.account);\r\n }\r\n\r\n /** \u5728\u51ED\u636E\u5B58\u50A8\u4E4B\u5916\u6301\u4E45\u5316 OAuth \u53D1\u73B0\u72B6\u6001\u3002 */\r\n saveDiscoveryState(state: OAuthDiscoveryState): Promise {\r\n return this.discoveryStore.set(this.account, state);\r\n }\r\n\r\n /**\r\n * \u4F7F MCP OAuth \u5BA2\u6237\u7AEF\u6307\u5B9A\u7684\u51ED\u636E\u6750\u6599\u5931\u6548\u3002\r\n *\r\n * @param scope - \u8981\u6E05\u7406\u7684\u51ED\u636E\u7C7B\u522B\u3002\r\n */\r\n async invalidateCredentials(scope: \"all\" | \"client\" | \"tokens\" | \"verifier\" | \"discovery\"): Promise {\r\n if (scope === \"all\" || scope === \"tokens\") await this.tokenStore.delete(this.account);\r\n if (scope === \"all\" || scope === \"discovery\") await this.discoveryStore.delete(this.account);\r\n if (scope === \"all\" || scope === \"verifier\") this.codeVerifierValue = undefined;\r\n }\r\n}\r\n", "/**\r\n * \u4F7F\u7528\u64CD\u4F5C\u7CFB\u7EDF\u51ED\u636E\u7BA1\u7406\u5668\u5B89\u5168\u6301\u4E45\u5316 OAuth \u4EE4\u724C\u3002\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport { randomUUID } from \"node:crypto\";\r\nimport type { OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\r\n\r\n/** \u64CD\u4F5C\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u4E2D\u4F7F\u7528\u7684\u670D\u52A1\u540D\u79F0\u3002 */\r\nexport const KEYRING_SERVICE = \"CraftTable MCP\";\r\nconst KEYRING_CHUNK_SIZE = 1000;\r\n\r\ntype CredentialEntry = {\r\n getPassword(): Promise;\n setPassword(password: string): Promise;\r\n deleteCredential(): Promise;\r\n};\r\n\r\ntype CredentialEntryFactory = (account: string) => Promise;\r\n\r\ntype ChunkManifest = {\r\n version: 2;\r\n generation: string;\r\n chunks: number;\r\n};\r\n\r\n/** \u4EE5\u8D26\u6237\u54C8\u5E0C\u4E3A\u952E\u5B89\u5168\u6301\u4E45\u5316 OAuth \u4EE4\u724C\u96C6\u7684\u5951\u7EA6\u3002 */\r\nexport interface TokenStore {\r\n /** \u8FD4\u56DE\u8D26\u6237\u7684\u4EE4\u724C\u96C6\uFF1B\u4E0D\u5B58\u5728\u65F6\u8FD4\u56DE `undefined`\u3002 */\r\n get(account: string): Promise;\r\n /** \u66FF\u6362\u8D26\u6237\u7684\u4EE4\u724C\u96C6\u3002 */\r\n set(account: string, tokens: OAuthTokens): Promise;\r\n /** \u5220\u9664\u8D26\u6237\u7684\u5168\u90E8\u5DF2\u5B58\u50A8\u4EE4\u724C\u6570\u636E\uFF0C\u8FD4\u56DE\u662F\u5426\u5B58\u5728\u51ED\u636E\u3002 */\n delete(account: string): Promise;\n}\r\n\r\n/**\r\n * \u5728\u64CD\u4F5C\u7CFB\u7EDF\u51ED\u636E\u7BA1\u7406\u5668\u4E2D\u5B58\u50A8 OAuth \u4EE4\u724C\u3002\r\n *\r\n * \u90E8\u5206\u51ED\u636E\u7BA1\u7406\u5668\u7684\u5355\u6761\u8BB0\u5F55\u4E0A\u9650\u8F83\u5C0F\uFF0C\u56E0\u6B64\u4EE4\u724C\u8D1F\u8F7D\u4F1A\u88AB\u5206\u5757\u3002\r\n * \u6B64\u5B9E\u73B0\u6709\u610F\u4E0D\u652F\u6301\u660E\u6587\u540E\u5907\u5B58\u50A8\u3002\r\n */\r\nexport class KeyringTokenStore implements TokenStore {\r\n constructor(private readonly entryFactory: CredentialEntryFactory = keyringEntry) {}\r\n\r\n /**\r\n * @param account - \u7A33\u5B9A\u51ED\u636E\u8D26\u6237\u6807\u8BC6\u3002\r\n * @returns \u89E3\u7801\u540E\u7684\u4EE4\u724C\u96C6\uFF1B\u4E0D\u5B58\u5728\u51ED\u636E\u65F6\u8FD4\u56DE `undefined`\u3002\r\n * @throws \u51ED\u636E\u5B58\u50A8\u4E0D\u53EF\u7528\u6216\u5DF2\u5B58\u6570\u636E\u65E0\u6548\u65F6\u629B\u51FA\u3002\r\n */\r\n async get(account: string): Promise {\r\n let serialized: string | undefined;\r\n try {\r\n serialized = await this.readPassword(account);\r\n } catch (error) {\r\n throw keyringError(error);\r\n }\r\n if (!serialized) return undefined;\r\n\r\n const manifest = parseChunkManifest(serialized);\r\n if (manifest) {\r\n try {\r\n const chunks = await Promise.all(Array.from({ length: manifest.chunks }, (_, index) =>\r\n this.readPassword(chunkAccount(account, manifest.generation, index))));\r\n if (chunks.some((chunk) => chunk === undefined)) throw new Error(\"missing credential chunk\");\r\n serialized = Buffer.from(chunks.join(\"\"), \"base64\").toString(\"utf8\");\r\n } catch (error) {\r\n if (error instanceof Error && error.message === \"missing credential chunk\") throw invalidCredentialError();\r\n throw keyringError(error);\r\n }\r\n }\r\n\r\n try {\r\n const value = JSON.parse(serialized) as OAuthTokens;\r\n if (!value.access_token || !value.token_type) throw new Error(\"missing token fields\");\r\n return value;\r\n } catch {\r\n throw invalidCredentialError();\r\n }\r\n }\r\n\r\n /**\r\n * \u539F\u5B50\u53D1\u5E03\u65B0\u4E00\u4EE3\u5206\u5757\uFF0C\u5E76\u5220\u9664\u4E0A\u4E00\u4EE3\u5206\u5757\u3002\r\n *\r\n * @param account - \u7A33\u5B9A\u51ED\u636E\u8D26\u6237\u6807\u8BC6\u3002\r\n * @param tokens - \u8981\u5B89\u5168\u5B58\u50A8\u7684 OAuth \u4EE4\u724C\u3002\r\n * @throws \u4EFB\u4E00\u51ED\u636E\u5B58\u50A8\u64CD\u4F5C\u5931\u8D25\u65F6\u629B\u51FA\u3002\r\n */\r\n async set(account: string, tokens: OAuthTokens): Promise {\r\n const encoded = Buffer.from(JSON.stringify(tokens), \"utf8\").toString(\"base64\");\r\n const chunks = splitCredential(encoded);\r\n const generation = randomUUID().replaceAll(\"-\", \"\");\r\n const manifest: ChunkManifest = { version: 2, generation, chunks: chunks.length };\r\n let previous: string | undefined;\r\n const writtenAccounts: string[] = [];\r\n\r\n try {\r\n previous = await this.readPassword(account);\r\n for (const [index, chunk] of chunks.entries()) {\r\n const partAccount = chunkAccount(account, generation, index);\r\n await (await this.entryFactory(partAccount)).setPassword(chunk);\r\n writtenAccounts.push(partAccount);\r\n }\r\n await (await this.entryFactory(account)).setPassword(JSON.stringify(manifest));\r\n } catch (error) {\r\n await Promise.allSettled(writtenAccounts.map((partAccount) => this.deletePassword(partAccount)));\r\n throw keyringError(error);\r\n }\r\n\r\n const previousManifest = previous && parseChunkManifest(previous);\r\n if (previousManifest) {\r\n await Promise.allSettled(Array.from({ length: previousManifest.chunks }, (_, index) =>\r\n this.deletePassword(chunkAccount(account, previousManifest.generation, index))));\r\n }\r\n }\r\n\r\n /**\r\n * \u5220\u9664\u8D26\u6237\u6E05\u5355\u53CA\u5176\u5F15\u7528\u7684\u5168\u90E8\u5206\u5757\u3002\r\n *\r\n * @param account - \u7A33\u5B9A\u51ED\u636E\u8D26\u6237\u6807\u8BC6\u3002\r\n * @throws \u65E0\u6CD5\u8BBF\u95EE\u51ED\u636E\u5B58\u50A8\u65F6\u629B\u51FA\u3002\r\n */\r\n async delete(account: string): Promise {\n try {\r\n const serialized = await this.readPassword(account);\r\n const manifest = serialized ? parseChunkManifest(serialized) : undefined;\r\n await this.deletePassword(account);\r\n if (manifest) {\n await Promise.all(Array.from({ length: manifest.chunks }, (_, index) =>\n this.deletePassword(chunkAccount(account, manifest.generation, index))));\n }\n return serialized !== undefined;\n } catch (error) {\r\n throw keyringError(error);\r\n }\r\n }\r\n\r\n private async readPassword(account: string): Promise {\r\n try {\r\n return (await (await this.entryFactory(account)).getPassword()) ?? undefined;\n } catch (error) {\r\n if (isMissingCredential(error)) return undefined;\r\n throw error;\r\n }\r\n }\r\n\r\n private async deletePassword(account: string): Promise {\r\n try {\r\n await (await this.entryFactory(account)).deleteCredential();\r\n } catch (error) {\r\n if (!isMissingCredential(error)) throw error;\r\n }\r\n }\r\n}\r\n\r\nfunction splitCredential(value: string): string[] {\r\n const chunks: string[] = [];\r\n for (let offset = 0; offset < value.length; offset += KEYRING_CHUNK_SIZE) {\r\n chunks.push(value.slice(offset, offset + KEYRING_CHUNK_SIZE));\r\n }\r\n return chunks.length ? chunks : [\"\"];\r\n}\r\n\r\nfunction chunkAccount(account: string, generation: string, index: number): string {\r\n return `${account}:v2:${generation}:${index}`;\r\n}\r\n\r\nfunction parseChunkManifest(value: string): ChunkManifest | undefined {\r\n try {\r\n const parsed = JSON.parse(value) as Partial;\r\n if (parsed.version !== 2 || typeof parsed.generation !== \"string\" || !/^[0-9a-f]{32}$/.test(parsed.generation)\r\n || !Number.isInteger(parsed.chunks) || (parsed.chunks ?? 0) < 1 || (parsed.chunks ?? 0) > 100) return undefined;\r\n return parsed as ChunkManifest;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nfunction isMissingCredential(error: unknown): boolean {\r\n const message = String((error as Error)?.message ?? error).toLowerCase();\r\n return message.includes(\"no entry\") || message.includes(\"not found\");\r\n}\r\n\r\nfunction invalidCredentialError(): Error {\r\n return new Error(\"The saved CraftTable MCP credential is invalid; run `crafttable-mcp logout --local-only` and log in again\");\r\n}\r\n\r\nasync function keyringEntry(account: string): Promise {\r\n try {\r\n const { AsyncEntry } = await import(\"@napi-rs/keyring\");\r\n return new AsyncEntry(KEYRING_SERVICE, account);\r\n } catch (error) {\r\n throw keyringError(error);\r\n }\r\n}\r\n\r\nfunction keyringError(error: unknown): Error {\r\n if (error instanceof Error && error.message.startsWith(\"The operating-system credential store is unavailable\")) return error;\r\n const detail = error instanceof Error ? error.message : String(error);\r\n return new Error(`The operating-system credential store is unavailable (${detail}). Enable Windows Credential Manager, macOS Keychain, or a Secret Service provider, then retry; plaintext token storage is not supported.`);\r\n}\r\n"], + "mappings": "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", "names": ["mkdir", "readFile", "rename", "writeFile", "os", "path", "path", "os", "readFile", "mkdir", "rename", "writeFile", "randomUUID"] } diff --git a/skills/crafttable/SKILL.md b/skills/crafttable/SKILL.md index a83f6b9..fe9df5e 100644 --- a/skills/crafttable/SKILL.md +++ b/skills/crafttable/SKILL.md @@ -5,15 +5,17 @@ description: Use the configured CraftTable MCP when a task involves project spac # CraftTable -Use the MCP server named `crafttable`; do not substitute direct HTTP calls or expose OAuth credentials. +Use the MCP server named `crafttable` when it is mounted in the current session. If it is not mounted, use the installed `crafttable-mcp-client` CLI (or the compatible `crafttable-mcp` command alias) instead. Do not substitute direct HTTP calls, override the configured server URL, or expose OAuth credentials. + +For CLI access, use `status` to check connectivity, `tools` or `resources` to discover the remote surface, `call ''` to invoke a tool, and `read ` for an MCP resource. Run `login` only when `status` reports that the user is not logged in. ## Workflow -1. Call `list_spaces` when the target space ID is unknown. Match by name and ask only when multiple spaces remain plausible. +1. Confirm the connection through the mounted MCP server or `crafttable-mcp-client status`, then call `list_spaces` when the target space ID is unknown. Match by name and ask only when multiple spaces remain plausible. 2. Read the smallest useful surface before answering or changing data. Prefer overview and list tools, then fetch a specific work item or document. 3. Treat CraftTable as the source of truth for workspace state. Do not infer current members, tasks, document text, or project knowledge from stale conversation context when MCP reads are available. 4. Perform writes only when the user's request authorizes that state change. Keep changes within the named space and object. 5. Before updating Markdown, call `get_document` and pass its `currentRevisionId` as `baseRevisionId` to `update_markdown_document`. 6. Pass `confirm: true` to deletion tools only after the user has explicitly confirmed the specific deletion. -Respect membership roles, enabled-plugin checks, and MCP errors. Never try to bypass a denied operation. If the MCP server is unavailable because the user is not logged in, ask them to run `crafttable-mcp login`; never open a login page from an Agent stdio session. +Respect membership roles, enabled-plugin checks, and MCP errors. Never try to bypass a denied operation. Do not run `configure` or `unconfigure` unless the user explicitly asks to change an agent's MCP registration. If the user is not logged in, ask them to run `crafttable-mcp-client login` (or `crafttable-mcp login`); never open a login page from an Agent stdio session. diff --git a/src/agents.ts b/src/agents.ts index 5700bbc..8313bc1 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -1,3 +1,9 @@ +/** + * 管理 CraftTable MCP 在 Codex、Claude Code 与 OpenCode 中的注册和 Skill 安装。 + * + * @packageDocumentation + */ + import { randomUUID } from "node:crypto"; import { constants } from "node:fs"; import { copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises"; @@ -9,16 +15,24 @@ import spawn from "cross-spawn"; import type { ClientOptions } from "./config.js"; import { errorMessage } from "./config.js"; +/** CraftTable MCP 服务及随附 Skill 使用的注册名称。 */ export const SERVER_NAME = "crafttable"; +/** 支持自动配置的 Agent CLI。 */ export type AgentName = "codex" | "claude" | "opencode"; +/** 单个受支持 Agent 或全部受支持 Agent。 */ export type AgentTarget = AgentName | "all"; +/** 安装或移除托管 CraftTable Skill 的结果。 */ export type SkillAction = "installed" | "replaced" | "removed" | "unchanged" | "absent" | "preserved" | "would-install" | "would-replace" | "would-remove" | "would-preserve"; +/** 外部 Agent CLI 调用的捕获结果。 */ export type CommandResult = { code: number; stdout: string; stderr: string }; +/** Agent 配置使用的可注入命令执行边界。 */ export interface CommandRunner { + /** 不经过 shell 执行命令并捕获其输出。 */ run(command: string, args: string[]): Promise; } +/** 控制 MCP 注册和随附 Skill 安装的输入。 */ export type ConfigureInput = { target: AgentTarget; options: ClientOptions; @@ -34,12 +48,22 @@ export type ConfigureInput = { skillPaths?: Partial>; }; +/** 配置或取消配置操作中每个 Agent 的结果。 */ export type ConfigureResult = { agent: AgentName; action: "added" | "replaced" | "removed" | "unchanged" | "absent" | "would-add" | "would-replace" | "would-remove"; skillAction: SkillAction; }; +/** + * 为选定 Agent 注册 stdio 桥接并安装随附 Skill。 + * + * 除非现有配置与托管内容一致、用户确认替换或启用 `force`,否则保留现有配置。 + * + * @param input - 目标 Agent、启动设置与变更策略。 + * @returns 每个选定 Agent 的 MCP 与 Skill 操作结果。 + * @throws 无法检查配置、无法取得确认或写入/CLI 命令失败时抛出。 + */ export async function configureAgents(input: ConfigureInput): Promise { const agents = expandTarget(input.target); const command = launchCommand(input.options, input.cliEntry, input.nodePath ?? process.execPath); @@ -55,6 +79,15 @@ export async function configureAgents(input: ConfigureInput): Promise { const agents = expandTarget(input.target); const runner = input.runner ?? new SpawnCommandRunner(); @@ -88,6 +121,14 @@ async function unconfigureCliAgent( return { agent, action: "removed" }; } +/** + * 构造注册到 Agent CLI 的完整可执行程序与参数。 + * + * @param options - 远端 MCP 与 OAuth 回调设置。 + * @param cliEntry - CraftTable MCP CLI 入口模块路径。 + * @param nodePath - 用于启动入口模块的 Node.js 可执行文件。 + * @returns 可用于 Agent 配置的 argv 风格命令数组。 + */ export function launchCommand(options: ClientOptions, cliEntry: string, nodePath: string): string[] { return [ path.resolve(nodePath), @@ -147,7 +188,7 @@ function outputMatchesCommand(output: string, command: string[]): boolean { const document = JSON.parse(output) as unknown; if (findCommand(document, command)) return true; } catch { - // Claude currently returns a human-readable record. + // Claude 当前返回便于阅读的文本记录。 } return command.every((part) => output.includes(part)); } @@ -201,6 +242,10 @@ async function unconfigureOpenCode(input: ConfigureInput): Promise { return new Promise((resolve, reject) => { const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] }); diff --git a/src/bridge.ts b/src/bridge.ts index 5a864e9..be98cb6 100644 --- a/src/bridge.ts +++ b/src/bridge.ts @@ -1,3 +1,9 @@ +/** + * 将远端 CraftTable MCP 服务桥接为本地 stdio MCP 服务。 + * + * @packageDocumentation + */ + import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { @@ -12,6 +18,19 @@ import { DiscoveryStore } from "./config.js"; import type { TokenStore } from "./credentials.js"; import { connectRemote } from "./remote.js"; +/** + * 连接远端 CraftTable MCP 端点,并通过 stdio 提供服务。 + * + * 桥接会一直运行到 stdio 传输关闭或进程收到 SIGINT/SIGTERM; + * 关闭时会同时释放本地和远端连接。 + * + * @param options - 远端 MCP 与 OAuth 客户端设置。 + * @param tokenStore - OAuth 令牌的安全存储。 + * @param discoveryStore - 持久化的 OAuth 发现缓存。 + * @param serviceToken - OAuth 令牌不存在时可使用的旧版 Bearer 令牌。 + * @returns 桥接停止后完成的 Promise。 + * @throws 无法完成鉴权或建立任一 MCP 传输时抛出。 + */ export async function serveBridge( options: ClientOptions, tokenStore: TokenStore, @@ -38,6 +57,12 @@ export async function serveBridge( } } +/** + * 创建一个将支持的请求转发到远端的本地 MCP 服务。 + * + * @param upstream - 代理所需的最小远端 MCP 客户端接口。 + * @returns 尚未连接、可绑定服务传输的 MCP 服务实例。 + */ export function createProxyServer(upstream: { listTools: (params?: { cursor?: string }) => Promise; callTool: (params: { name: string; arguments?: Record }) => Promise; diff --git a/src/callbackServer.ts b/src/callbackServer.ts index 20dab5b..b5eb3d7 100644 --- a/src/callbackServer.ts +++ b/src/callbackServer.ts @@ -1,8 +1,21 @@ +/** + * 在 loopback 地址接收 OAuth 授权码回调。 + * + * @packageDocumentation + */ + import { createServer, type Server } from "node:http"; import type { AddressInfo } from "node:net"; +/** loopback 回调收到的成功授权响应。 */ export type OAuthCallback = { code: string }; +/** + * 在 loopback 接口接收一次 OAuth 授权码回调。 + * + * 服务会拒绝包含 OAuth 错误、状态无效或缺少授权码的回调。 + * 登录尝试完成后应调用 {@link close}。 + */ export class OAuthCallbackServer { private server?: Server; private resolveResult?: (value: OAuthCallback) => void; @@ -16,11 +29,17 @@ export class OAuthCallbackServer { private readonly port: number, private readonly validateState: (state: string | null) => boolean, ) { - // A very fast browser callback can arrive before loginRemote starts awaiting it. - // Keep rejection handled while preserving the original promise for wait(). + // 浏览器回调可能早于 loginRemote 开始等待;预先处理拒绝, + // 同时为 wait() 保留原始 Promise。 void this.result.catch(() => undefined); } + /** + * 在配置的固定回调端口监听 `127.0.0.1`。 + * + * @returns 端口绑定完成后解析的 Promise。 + * @throws 服务已运行或端口无法绑定时抛出。 + */ async listen(): Promise { if (this.server) throw new Error("OAuth callback server is already running"); this.server = createServer((request, response) => { @@ -61,6 +80,13 @@ export class OAuthCallbackServer { if (!address || address.port !== this.port) throw new Error(`OAuth callback server did not bind port ${this.port}`); } + /** + * 等待有效回调或达到配置的超时时间。 + * + * @param timeoutMs - 最大等待时间(毫秒)。 + * @returns 经过验证的回调中的授权码。 + * @throws 授权失败、状态验证失败、缺少授权码或等待超时时抛出。 + */ async wait(timeoutMs = 10 * 60 * 1000): Promise { let timeout: NodeJS.Timeout | undefined; try { @@ -75,6 +101,11 @@ export class OAuthCallbackServer { } } + /** + * 停止接收回调;对已停止的服务调用此方法不会产生任何操作。 + * + * @returns HTTP 服务关闭后解析的 Promise。 + */ async close(): Promise { if (!this.server) return; const server = this.server; diff --git a/src/cli.ts b/src/cli.ts index da611ee..0da2c1e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,5 +1,11 @@ #!/usr/bin/env node +/** + * 定义 CraftTable MCP 客户端的命令行入口与子命令。 + * + * @packageDocumentation + */ + import { fileURLToPath } from "node:url"; import { Command } from "commander"; import { configureAgents, type AgentTarget, unconfigureAgents } from "./agents.js"; @@ -10,6 +16,7 @@ import { connectRemote, loginRemote, logoutRemote } from "./remote.js"; type ConnectionFlags = { url?: string; clientId?: string; callbackPort?: string }; +/** 交互式 OAuth、检查与 stdio 桥接流程共用的根命令。 */ const program = new Command() .name("crafttable-mcp") .description("OAuth client and stdio bridge for CraftTable MCP") @@ -80,7 +87,7 @@ for (const operation of ["configure", "unconfigure"] as const) { .option("--force", "replace a conflicting entry without prompting") .action(async (agent: AgentTarget, flags: ConnectionFlags & { dryRun?: boolean; force?: boolean }) => { const cliEntry = fileURLToPath(import.meta.url); - if (!cliEntry.endsWith(".js")) throw new Error("Agent configuration requires the built CLI; run `npm --prefix apps/mcp-client run build` first"); + if (!cliEntry.endsWith(".js")) throw new Error("Agent configuration requires the built CLI; run `npm --prefix mcp-client run build` first"); const input = { target: agent, options: connectionOptions(flags), @@ -93,6 +100,12 @@ for (const operation of ["configure", "unconfigure"] as const) { }); } +/** + * 为命令添加通用的远端端点与 OAuth 客户端选项。 + * + * @param command - 要扩展的 Commander 命令。 + * @returns 已注册连接选项的同一命令实例。 + */ function withConnection(command: Command): Command { return command .option("--url ", "MCP Streamable HTTP URL") @@ -100,10 +113,17 @@ function withConnection(command: Command): Command { .option("--callback-port ", "fixed localhost OAuth callback port"); } +/** 解析并验证一次命令调用的连接选项。 */ function connectionOptions(flags: ConnectionFlags): ClientOptions { return resolveClientOptions(flags); } +/** + * 使用已连接的远端客户端执行操作,并确保连接始终关闭。 + * + * @param options - 已验证的远端连接设置。 + * @param action - 使用已连接 MCP 客户端执行的操作。 + */ async function withClient(options: ClientOptions, action: (client: Awaited>["client"]) => Promise): Promise { const connection = await connectRemote(options, new KeyringTokenStore(), new DiscoveryStore()); try { @@ -113,6 +133,13 @@ async function withClient(options: ClientOptions, action: (client: Awaited { let value: unknown; try { @@ -124,12 +151,21 @@ function parseObject(raw: string): Record { return value as Record; } +/** + * 解析正整数命令选项。 + * + * @param value - 原始 CLI 选项值。 + * @param name - 错误消息中使用的可读选项名称。 + * @returns 经过验证的整数。 + * @throws 值不是正整数时抛出。 + */ function positiveInteger(value: string, name: string): number { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${name} must be a positive integer`); return parsed; } +/** 将稳定且便于阅读的 JSON 结果写入标准输出。 */ function printJson(value: unknown): void { process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); } diff --git a/src/config.ts b/src/config.ts index fbbee2a..80ba502 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,20 +1,38 @@ +/** + * 解析 MCP 连接配置并管理 OAuth 发现缓存。 + * + * @packageDocumentation + */ + import { createHash } from "node:crypto"; import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import type { OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js"; +/** CLI 使用的默认公开 Streamable HTTP 端点。 */ export const DEFAULT_MCP_URL = "https://crafttable.crash.work/mcp"; +/** 为 CLI 注册的 OAuth 公共客户端标识。 */ export const DEFAULT_CLIENT_ID = "crafttable-mcp-cli"; +/** 注册为 OAuth 重定向 URI 的固定 loopback 端口。 */ export const DEFAULT_CALLBACK_PORT = 48321; +/** 交互式登录时请求的 OAuth 作用域。 */ export const OAUTH_SCOPES = "openid profile email offline_access"; +/** MCP 操作使用的已解析连接与 OAuth 回调设置。 */ export type ClientOptions = { url: URL; clientId: string; callbackPort: number; }; +/** + * 将 CLI 覆盖项、环境变量与默认值解析为经过验证的设置。 + * + * @param input - 可选命令标志与环境变量来源。 + * @returns 规范化的客户端设置。 + * @throws URL、客户端 ID 或回调端口无效时抛出。 + */ export function resolveClientOptions(input: { url?: string; clientId?: string; @@ -32,6 +50,16 @@ export function resolveClientOptions(input: { return { url, clientId, callbackPort }; } +/** + * 验证并规范化远端 MCP URL。 + * + * 除 loopback 开发端点外必须使用 HTTPS;为避免泄露认证数据, + * URL 中不允许包含凭据、查询字符串或片段。 + * + * @param value - 用户或环境变量提供的绝对 URL。 + * @returns 去除路径末尾斜杠后的规范化 URL。 + * @throws URL 格式错误或违反传输策略时抛出。 + */ export function validateMcpUrl(value: string): URL { let url: URL; try { @@ -50,10 +78,23 @@ export function validateMcpUrl(value: string): URL { return url; } +/** + * 派生稳定且不包含秘密的凭据存储账户键。 + * + * @param options - 定义账户的服务 URL 与 OAuth 客户端标识。 + * @returns SHA-256 账户标识。 + */ export function credentialAccount(options: Pick): string { return createHash("sha256").update(`${options.url.toString()}\0${options.clientId}`).digest("hex"); } +/** + * 解析保存非敏感客户端状态的平台目录。 + * + * @param env - 用于覆盖平台目录的环境变量。 + * @param platform - Node 平台标识,可注入以便测试。 + * @returns 绝对配置目录路径。 + */ export function platformConfigDir(env: NodeJS.ProcessEnv = process.env, platform = process.platform): string { if (platform === "win32") return path.join(env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "GameCraftTable", "mcp-client"); if (platform === "darwin") return path.join(os.homedir(), "Library", "Application Support", "GameCraftTable", "mcp-client"); @@ -65,6 +106,7 @@ type DiscoveryFile = { entries: Record; }; +/** 在用户非敏感配置目录中存储 OAuth 发现状态。 */ export class DiscoveryStore { readonly filePath: string; @@ -72,16 +114,27 @@ export class DiscoveryStore { this.filePath = path.join(configDir, "discovery.json"); } + /** + * @param account - 稳定账户标识。 + * @returns 账户的缓存发现状态;不存在时返回 `undefined`。 + */ async get(account: string): Promise { return (await this.read()).entries[account]; } + /** + * 使用原子文件替换持久化账户的发现状态。 + * + * @param account - 稳定账户标识。 + * @param state - MCP OAuth 客户端提供的发现状态。 + */ async set(account: string, state: OAuthDiscoveryState): Promise { const document = await this.read(); document.entries[account] = state; await atomicWriteJson(this.filePath, document); } + /** @param account - 要删除其状态的稳定账户标识。 */ async delete(account: string): Promise { const document = await this.read(); if (!(account in document.entries)) return; @@ -100,6 +153,13 @@ export class DiscoveryStore { } } +/** + * 先写入私有临时文件,再通过原子重命名替换 JSON 文档。 + * + * @param filePath - 目标文件路径。 + * @param value - 可序列化为 JSON 的值。 + * @returns 替换完成后解析的 Promise。 + */ export async function atomicWriteJson(filePath: string, value: unknown): Promise { await mkdir(path.dirname(filePath), { recursive: true }); const temporary = `${filePath}.${process.pid}.tmp`; @@ -107,6 +167,7 @@ export async function atomicWriteJson(filePath: string, value: unknown): Promise await rename(temporary, filePath); } +/** 将未知的抛出值转换为可显示的错误消息。 */ export function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } diff --git a/src/credentials.ts b/src/credentials.ts index 26b7e3b..9619d8b 100644 --- a/src/credentials.ts +++ b/src/credentials.ts @@ -1,11 +1,18 @@ +/** + * 使用操作系统凭据管理器安全持久化 OAuth 令牌。 + * + * @packageDocumentation + */ + import { randomUUID } from "node:crypto"; import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; +/** 操作系统凭据存储中使用的服务名称。 */ export const KEYRING_SERVICE = "CraftTable MCP"; const KEYRING_CHUNK_SIZE = 1000; type CredentialEntry = { - getPassword(): Promise; + getPassword(): Promise; setPassword(password: string): Promise; deleteCredential(): Promise; }; @@ -18,15 +25,30 @@ type ChunkManifest = { chunks: number; }; +/** 以账户哈希为键安全持久化 OAuth 令牌集的契约。 */ export interface TokenStore { + /** 返回账户的令牌集;不存在时返回 `undefined`。 */ get(account: string): Promise; + /** 替换账户的令牌集。 */ set(account: string, tokens: OAuthTokens): Promise; - delete(account: string): Promise; + /** 删除账户的全部已存储令牌数据,返回是否存在凭据。 */ + delete(account: string): Promise; } +/** + * 在操作系统凭据管理器中存储 OAuth 令牌。 + * + * 部分凭据管理器的单条记录上限较小,因此令牌负载会被分块。 + * 此实现有意不支持明文后备存储。 + */ export class KeyringTokenStore implements TokenStore { constructor(private readonly entryFactory: CredentialEntryFactory = keyringEntry) {} + /** + * @param account - 稳定凭据账户标识。 + * @returns 解码后的令牌集;不存在凭据时返回 `undefined`。 + * @throws 凭据存储不可用或已存数据无效时抛出。 + */ async get(account: string): Promise { let serialized: string | undefined; try { @@ -58,6 +80,13 @@ export class KeyringTokenStore implements TokenStore { } } + /** + * 原子发布新一代分块,并删除上一代分块。 + * + * @param account - 稳定凭据账户标识。 + * @param tokens - 要安全存储的 OAuth 令牌。 + * @throws 任一凭据存储操作失败时抛出。 + */ async set(account: string, tokens: OAuthTokens): Promise { const encoded = Buffer.from(JSON.stringify(tokens), "utf8").toString("base64"); const chunks = splitCredential(encoded); @@ -86,7 +115,13 @@ export class KeyringTokenStore implements TokenStore { } } - async delete(account: string): Promise { + /** + * 删除账户清单及其引用的全部分块。 + * + * @param account - 稳定凭据账户标识。 + * @throws 无法访问凭据存储时抛出。 + */ + async delete(account: string): Promise { try { const serialized = await this.readPassword(account); const manifest = serialized ? parseChunkManifest(serialized) : undefined; @@ -95,6 +130,7 @@ export class KeyringTokenStore implements TokenStore { await Promise.all(Array.from({ length: manifest.chunks }, (_, index) => this.deletePassword(chunkAccount(account, manifest.generation, index)))); } + return serialized !== undefined; } catch (error) { throw keyringError(error); } @@ -102,7 +138,7 @@ export class KeyringTokenStore implements TokenStore { private async readPassword(account: string): Promise { try { - return await (await this.entryFactory(account)).getPassword(); + return (await (await this.entryFactory(account)).getPassword()) ?? undefined; } catch (error) { if (isMissingCredential(error)) return undefined; throw error; diff --git a/src/oauthProvider.ts b/src/oauthProvider.ts index 33252f3..3f039fe 100644 --- a/src/oauthProvider.ts +++ b/src/oauthProvider.ts @@ -1,3 +1,9 @@ +/** + * 实现 MCP SDK 所需的 CraftTable OAuth 提供者。 + * + * @packageDocumentation + */ + import { randomBytes, timingSafeEqual } from "node:crypto"; import type { OAuthClientProvider, OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js"; import type { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; @@ -5,6 +11,12 @@ import type { ClientOptions } from "./config.js"; import { credentialAccount, DiscoveryStore, OAUTH_SCOPES } from "./config.js"; import type { TokenStore } from "./credentials.js"; +/** + * 由安全令牌存储和发现缓存支持的 MCP SDK OAuth 提供者。 + * + * 每个实例拥有独立的随机 OAuth state 和内存 PKCE verifier, + * 防止这些值在不同登录尝试间共享。 + */ export class CraftTableOAuthProvider implements OAuthClientProvider { readonly redirectUrl: URL; readonly clientMetadata: OAuthClientMetadata; @@ -12,6 +24,13 @@ export class CraftTableOAuthProvider implements OAuthClientProvider { private readonly expectedState: string; private codeVerifierValue?: string; + /** + * @param options - 远端端点与已注册公共客户端设置。 + * @param tokenStore - OAuth 令牌安全持久化接口。 + * @param discoveryStore - 授权服务器发现状态缓存。 + * @param onRedirect - 打开或显示授权 URL 的处理器。 + * @param state - 预期回调状态,可注入以实现确定性测试。 + */ constructor( private readonly options: ClientOptions, private readonly tokenStore: TokenStore, @@ -32,10 +51,17 @@ export class CraftTableOAuthProvider implements OAuthClientProvider { this.expectedState = state; } + /** @returns 授权回调必须携带的 state 值。 */ state(): string { return this.expectedState; } + /** + * 在长度一致时以常量时间比较回调 state。 + * + * @param value - 从 loopback 回调收到的 state。 + * @returns 回调是否属于本次授权尝试。 + */ validateState(value: string | null): boolean { if (!value) return false; const expected = Buffer.from(this.expectedState); @@ -43,39 +69,55 @@ export class CraftTableOAuthProvider implements OAuthClientProvider { return expected.length === actual.length && timingSafeEqual(expected, actual); } + /** @returns MCP SDK 使用的静态公共客户端注册信息。 */ clientInformation(): OAuthClientInformationMixed { return { client_id: this.options.clientId }; } + /** @returns 当前服务/客户端组合已保存的 OAuth 令牌。 */ tokens(): Promise { return this.tokenStore.get(this.account); } + /** 持久化刷新或新签发的 OAuth 令牌集。 */ saveTokens(tokens: OAuthTokens): Promise { return this.tokenStore.set(this.account, tokens); } + /** 将授权 URL 交给 CLI 的浏览器或手动登录处理器。 */ redirectToAuthorization(url: URL): void | Promise { return this.onRedirect(url); } + /** 在本次登录尝试期间将 PKCE verifier 保存在内存中。 */ saveCodeVerifier(codeVerifier: string): void { this.codeVerifierValue = codeVerifier; } + /** + * @returns 为当前授权尝试保存的 PKCE verifier。 + * @throws 尚未生成 verifier 或其已失效时抛出。 + */ codeVerifier(): string { if (!this.codeVerifierValue) throw new Error("OAuth PKCE verifier is missing or expired"); return this.codeVerifierValue; } + /** @returns 当前服务/客户端组合的 OAuth 发现缓存。 */ discoveryState(): Promise { return this.discoveryStore.get(this.account); } + /** 在凭据存储之外持久化 OAuth 发现状态。 */ saveDiscoveryState(state: OAuthDiscoveryState): Promise { return this.discoveryStore.set(this.account, state); } + /** + * 使 MCP OAuth 客户端指定的凭据材料失效。 + * + * @param scope - 要清理的凭据类别。 + */ async invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): Promise { if (scope === "all" || scope === "tokens") await this.tokenStore.delete(this.account); if (scope === "all" || scope === "discovery") await this.discoveryStore.delete(this.account); diff --git a/src/remote.ts b/src/remote.ts index 3a97764..eb68288 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -1,3 +1,9 @@ +/** + * 建立远端 MCP 连接,并编排 OAuth 登录、注销与元数据发现。 + * + * @packageDocumentation + */ + import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; @@ -9,6 +15,7 @@ import { credentialAccount, DiscoveryStore, errorMessage } from "./config.js"; import type { TokenStore } from "./credentials.js"; import { CraftTableOAuthProvider } from "./oauthProvider.js"; +/** 已连接的远端 MCP 客户端及其所选鉴权模式。 */ export type RemoteConnection = { client: Client; transport: StreamableHTTPClientTransport; @@ -16,6 +23,18 @@ export type RemoteConnection = { close: () => Promise; }; +/** + * 使用已保存 OAuth 令牌或旧版令牌连接远端 MCP 服务。 + * + * 已保存的 OAuth 凭据优先于服务令牌。 + * + * @param options - 远端 MCP 与 OAuth 客户端设置。 + * @param tokenStore - OAuth 令牌安全持久化接口。 + * @param discoveryStore - OAuth 发现缓存。 + * @param serviceToken - 可选的旧版 Bearer 令牌后备项。 + * @returns 已连接客户端与可重复调用的关闭边界。 + * @throws 没有可用鉴权方式或远端连接失败时抛出。 + */ export async function connectRemote( options: ClientOptions, tokenStore: TokenStore, @@ -37,6 +56,16 @@ export async function connectRemote( throw new Error("Not logged in; run `crafttable-mcp login` or set CRAFTTABLE_MCP_TOKEN for the legacy service-account path"); } +/** + * 完成 OAuth 授权码流程并验证 MCP 连通性。 + * + * @param options - 远端 MCP 与 loopback 回调设置。 + * @param tokenStore - OAuth 令牌的安全目标存储。 + * @param discoveryStore - 持久化 OAuth 发现缓存。 + * @param input - 浏览器行为、回调超时与状态输出钩子。 + * @returns 凭据此前是否有效,以及当前可见工具数量。 + * @throws OAuth 元数据、浏览器回调、令牌交换或 MCP 连接失败时抛出。 + */ export async function loginRemote( options: ClientOptions, tokenStore: TokenStore, @@ -87,6 +116,19 @@ export async function loginRemote( } } +/** + * 按需撤销已保存的 OAuth 凭据,然后清理本地 OAuth 状态。 + * + * 远端撤销失败时会保留本地凭据,以便重试。 + * + * @param options - 远端 MCP 与 OAuth 客户端设置。 + * @param tokenStore - OAuth 令牌安全持久化接口。 + * @param discoveryStore - OAuth 发现缓存。 + * @param localOnly - 为 `true` 时跳过远端撤销。 + * @param fetchFn - HTTP 实现,可注入以便测试。 + * @returns 凭据是否存在,以及是否成功执行撤销。 + * @throws 发现流程或远端令牌撤销失败时抛出。 + */ export async function logoutRemote( options: ClientOptions, tokenStore: TokenStore, @@ -95,14 +137,19 @@ export async function logoutRemote( fetchFn: typeof fetch = fetch, ): Promise<{ hadCredential: boolean; revoked: boolean }> { const account = credentialAccount(options); + if (localOnly) { + const hadCredential = await tokenStore.delete(account); + await discoveryStore.delete(account); + return { hadCredential, revoked: false }; + } const tokens = await tokenStore.get(account); - if (tokens && !localOnly) { + if (tokens) { const endpoint = await discoverRevocationEndpoint(options.url, fetchFn); await revokeTokens(endpoint, options.clientId, tokens, fetchFn); } await tokenStore.delete(account); await discoveryStore.delete(account); - return { hadCredential: Boolean(tokens), revoked: Boolean(tokens && !localOnly) }; + return { hadCredential: Boolean(tokens), revoked: Boolean(tokens) }; } async function connectWithTransport( @@ -144,12 +191,20 @@ async function discoverRevocationEndpoint(resource: URL, fetchFn: typeof fetch): const metadata = await response.json() as { revocation_endpoint?: unknown }; if (typeof metadata.revocation_endpoint === "string") return new URL(metadata.revocation_endpoint); } catch { - // Try the next standards-compatible discovery location. + // 尝试下一个符合标准的发现位置。 } } throw new Error("OAuth authorization server does not advertise a revocation endpoint"); } +/** + * 加载并验证 MCP 端点的 RFC 9728 受保护资源元数据。 + * + * @param resource - 要发现其元数据的 MCP 资源 URL。 + * @param fetchFn - HTTP 实现,可注入以便测试。 + * @returns 包含已公布授权服务器的解析后元数据。 + * @throws 元数据不可达、响应失败、不是 JSON 或格式错误时抛出。 + */ export async function requireOAuthProtectedResourceMetadata( resource: URL, fetchFn: typeof fetch = fetch, diff --git a/test/agents.test.ts b/test/agents.test.ts index ff2dddf..92b5ca7 100644 --- a/test/agents.test.ts +++ b/test/agents.test.ts @@ -1,3 +1,9 @@ +/** + * 覆盖 Agent 配置适配器、Skill 目录解析、幂等配置、冲突替换与备份行为。 + * + * @packageDocumentation + */ + import assert from "node:assert/strict"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; @@ -35,7 +41,7 @@ class FakeRunner implements CommandRunner { } const options = resolveClientOptions({ url: "https://example.test/mcp", clientId: "client", callbackPort: 48321, env: {} }); -const cliEntry = path.resolve("dist", "cli.js"); +const cliEntry = path.resolve("..", "build", "mcp-client", "cli.js"); const nodePath = path.resolve("bin", "node.exe"); test("Agent Skill paths use each client's user-level discovery directory", () => { diff --git a/test/bridge.test.ts b/test/bridge.test.ts index 62cfa84..63891f8 100644 --- a/test/bridge.test.ts +++ b/test/bridge.test.ts @@ -1,3 +1,9 @@ +/** + * 覆盖 stdio 代理对 MCP 工具、资源、模板、游标与上游错误的转发行为。 + * + * @packageDocumentation + */ + import assert from "node:assert/strict"; import test from "node:test"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; diff --git a/test/configAndOAuth.test.ts b/test/configAndOAuth.test.ts index ff62e33..1ce941e 100644 --- a/test/configAndOAuth.test.ts +++ b/test/configAndOAuth.test.ts @@ -1,3 +1,9 @@ +/** + * 覆盖连接配置校验、OAuth state/回调、元数据检查与令牌撤销边界。 + * + * @packageDocumentation + */ + import assert from "node:assert/strict"; import { createServer } from "node:net"; import test from "node:test"; @@ -12,7 +18,7 @@ class MemoryTokens implements TokenStore { readonly values = new Map(); async get(account: string): Promise { return this.values.get(account); } async set(account: string, tokens: OAuthTokens): Promise { this.values.set(account, tokens); } - async delete(account: string): Promise { this.values.delete(account); } + async delete(account: string): Promise { return this.values.delete(account); } } class MemoryDiscovery { diff --git a/test/credentials.test.ts b/test/credentials.test.ts index 73d2ae5..9c1748c 100644 --- a/test/credentials.test.ts +++ b/test/credentials.test.ts @@ -1,7 +1,15 @@ +/** + * 覆盖系统凭据存储适配器的令牌分块、轮换清理与旧格式兼容行为。 + * + * @packageDocumentation + */ + import assert from "node:assert/strict"; import test from "node:test"; import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; import { KeyringTokenStore } from "../src/credentials.js"; +import { credentialAccount, resolveClientOptions } from "../src/config.js"; +import { logoutRemote } from "../src/remote.js"; class MemoryEntry { constructor(private readonly values: Map, private readonly account: string) {} @@ -64,3 +72,49 @@ test("keyring token store still reads and removes legacy single-entry credential await store.delete("account"); assert.equal(values.size, 0); }); + +for (const damage of ["invalid-json", "missing-token-fields", "missing-chunk"] as const) { + test(`local-only logout removes ${damage} credentials without remote requests`, async () => { + const options = resolveClientOptions({ url: "https://example.test/mcp", env: {} }); + const account = credentialAccount(options); + const values = new Map(); + const store = memoryStore(values); + if (damage === "missing-chunk") { + await store.set(account, { access_token: "a".repeat(3000), token_type: "Bearer" }); + values.delete([...values.keys()].find((key) => key !== account)!); + } else { + values.set(account, damage === "invalid-json" ? "{broken" : "{}"); + } + values.set("other-account", "untouched"); + await assert.rejects(store.get(account), /credential is invalid/); + const discovery = new Map([[account, "cached"], ["other-account", "untouched"]]); + const result = await logoutRemote(options, store, { + async delete(key: string) { discovery.delete(key); }, + } as never, true, async () => { throw new Error("unexpected network request"); }); + + assert.deepEqual(result, { hadCredential: true, revoked: false }); + assert.deepEqual([...values], [["other-account", "untouched"]]); + assert.deepEqual([...discovery], [["other-account", "untouched"]]); + assert.equal(await store.get(account), undefined); + assert.deepEqual(await logoutRemote(options, store, { + async delete(key: string) { discovery.delete(key); }, + } as never, true), { hadCredential: false, revoked: false }); + }); +} + +test("local-only logout reports credential store failures without claiming success", async () => { + const store = new KeyringTokenStore(async () => { throw new Error("access denied"); }); + await assert.rejects(logoutRemote(resolveClientOptions({ env: {} }), store, { + async delete() { assert.fail("discovery must be retained when credential deletion fails"); }, + } as never, true), /credential store is unavailable.*access denied/); +}); + +test("keyring null for an absent Windows credential is treated as missing", async () => { + const store = new KeyringTokenStore(async () => ({ + async getPassword() { return null; }, + async setPassword() { assert.fail("unexpected credential write"); }, + async deleteCredential() { return false; }, + })); + assert.equal(await store.get("account"), undefined); + assert.equal(await store.delete("account"), false); +}); diff --git a/test/oauthIntegration.test.ts b/test/oauthIntegration.test.ts index bef9c95..95542a6 100644 --- a/test/oauthIntegration.test.ts +++ b/test/oauthIntegration.test.ts @@ -1,3 +1,9 @@ +/** + * 通过本地 OAuth 与 MCP 模拟服务验证完整 PKCE 登录、令牌刷新和秘密隔离流程。 + * + * @packageDocumentation + */ + import assert from "node:assert/strict"; import { createHash, randomUUID } from "node:crypto"; import express from "express"; @@ -15,7 +21,7 @@ class MemoryTokens implements TokenStore { readonly values = new Map(); async get(account: string): Promise { return this.values.get(account); } async set(account: string, tokens: OAuthTokens): Promise { this.values.set(account, tokens); } - async delete(account: string): Promise { this.values.delete(account); } + async delete(account: string): Promise { return this.values.delete(account); } } class MemoryDiscovery { diff --git a/test/stdioFixture.ts b/test/stdioFixture.ts index 3817c66..a8987e1 100644 --- a/test/stdioFixture.ts +++ b/test/stdioFixture.ts @@ -1,3 +1,9 @@ +/** + * 为 stdio 协议集成测试提供最小 MCP 服务进程。 + * + * @packageDocumentation + */ + import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createProxyServer } from "../src/bridge.js"; diff --git a/test/stdioProtocol.test.ts b/test/stdioProtocol.test.ts index 601efb3..2557d17 100644 --- a/test/stdioProtocol.test.ts +++ b/test/stdioProtocol.test.ts @@ -1,3 +1,9 @@ +/** + * 验证 stdio 模式的标准输出只包含合法 JSON-RPC 消息,不混入诊断文本。 + * + * @packageDocumentation + */ + import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import path from "node:path";