From 4e5ee6c746e8d506b3533ea8dab9e88e95bd0999 Mon Sep 17 00:00:00 2001 From: cneicy Date: Sat, 15 Aug 2026 20:43:32 +0800 Subject: [PATCH] fix: ship prebuilt CraftTable CLI --- README.md | 4 +- dist/cli.js | 777 ++++++++++++++++++++++++++++++++++++++++++++++++ dist/cli.js.map | 7 + package.json | 1 - 4 files changed, 787 insertions(+), 2 deletions(-) create mode 100644 dist/cli.js create mode 100644 dist/cli.js.map diff --git a/README.md b/README.md index 303f45e..7ec63b4 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,13 @@ The repository is private, so first make sure Git Credential Manager can access `git.crash.work`, then run this in PowerShell: ```powershell -npm install --global git+https://git.crash.work/cneicy/crafttable-mcp-client.git; crafttable-mcp login; crafttable-mcp configure all +npm install --global --allow-git=all git+https://git.crash.work/cneicy/crafttable-mcp-client.git; crafttable-mcp login; crafttable-mcp configure all ``` This installs the CLI, opens the browser for OAuth login, and configures the same local stdio bridge for Codex, Claude Code, and OpenCode at user scope. +The explicit `--allow-git=all` is required by npm 12, whose default policy rejects Git-based package dependencies. The repository includes the built CLI bundle, so installation does not run a build script. + ## Commands ```powershell diff --git a/dist/cli.js b/dist/cli.js new file mode 100644 index 0000000..e7702c4 --- /dev/null +++ b/dist/cli.js @@ -0,0 +1,777 @@ +#!/usr/bin/env node + +// src/cli.ts +import { fileURLToPath } from "node:url"; +import { Command } from "commander"; + +// src/agents.ts +import { spawn } from "node:child_process"; +import { constants } from "node:fs"; +import { copyFile, mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises"; +import os2 from "node:os"; +import path2 from "node:path"; +import { createInterface } from "node:readline/promises"; +import { applyEdits, modify, parse } from "jsonc-parser"; + +// src/config.ts +import { createHash } from "node:crypto"; +import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +var DEFAULT_MCP_URL = "https://crafttable.crash.work/mcp"; +var DEFAULT_CLIENT_ID = "crafttable-mcp-cli"; +var DEFAULT_CALLBACK_PORT = 48321; +var OAUTH_SCOPES = "openid profile email offline_access"; +function resolveClientOptions(input = {}) { + const env = input.env ?? process.env; + const url = validateMcpUrl(input.url ?? env.CRAFTTABLE_MCP_URL ?? DEFAULT_MCP_URL); + const clientId = (input.clientId ?? env.CRAFTTABLE_MCP_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID).trim(); + if (!clientId) throw new Error("OAuth client ID must not be empty"); + const callbackPort = Number(input.callbackPort ?? env.CRAFTTABLE_MCP_OAUTH_CALLBACK_PORT ?? DEFAULT_CALLBACK_PORT); + if (!Number.isInteger(callbackPort) || callbackPort < 1 || callbackPort > 65535) { + throw new Error("OAuth callback port must be an integer between 1 and 65535"); + } + return { url, clientId, callbackPort }; +} +function validateMcpUrl(value) { + let url; + try { + url = new URL(value); + } catch { + throw new Error("MCP URL must be an absolute URL"); + } + if (url.username || url.password || url.search || url.hash) { + throw new Error("MCP URL must not include credentials, query parameters, or a fragment"); + } + const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]"; + if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) { + throw new Error("MCP URL must use HTTPS unless it targets localhost"); + } + url.pathname = url.pathname.replace(/\/+$/, "") || "/"; + return url; +} +function credentialAccount(options) { + return createHash("sha256").update(`${options.url.toString()}\0${options.clientId}`).digest("hex"); +} +function platformConfigDir(env = process.env, platform = process.platform) { + 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"); + return path.join(env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "gamecrafttable", "mcp-client"); +} +var DiscoveryStore = class { + filePath; + constructor(configDir = platformConfigDir()) { + this.filePath = path.join(configDir, "discovery.json"); + } + async get(account) { + return (await this.read()).entries[account]; + } + async set(account, state) { + const document = await this.read(); + document.entries[account] = state; + await atomicWriteJson(this.filePath, document); + } + async delete(account) { + const document = await this.read(); + if (!(account in document.entries)) return; + delete document.entries[account]; + await atomicWriteJson(this.filePath, document); + } + async read() { + try { + const value = JSON.parse(await readFile(this.filePath, "utf8")); + return { version: 1, entries: value.entries && typeof value.entries === "object" ? value.entries : {} }; + } catch (error) { + if (error.code === "ENOENT") return { version: 1, entries: {} }; + throw new Error(`Could not read OAuth discovery cache: ${errorMessage(error)}`); + } + } +}; +async function atomicWriteJson(filePath, value) { + await mkdir(path.dirname(filePath), { recursive: true }); + const temporary = `${filePath}.${process.pid}.tmp`; + await writeFile(temporary, `${JSON.stringify(value, null, 2)} +`, { encoding: "utf8", mode: 384 }); + await rename(temporary, filePath); +} +function errorMessage(error) { + return error instanceof Error ? error.message : String(error); +} + +// src/agents.ts +var SERVER_NAME = "crafttable"; +async function configureAgents(input) { + const agents = expandTarget(input.target); + const command = launchCommand(input.options, input.cliEntry, input.nodePath ?? process.execPath); + const runner = input.runner ?? new SpawnCommandRunner(); + const results = []; + for (const agent of agents) { + if (agent === "opencode") { + results.push(await configureOpenCode(input, command)); + } else { + results.push(await configureCliAgent(agent, command, input, runner)); + } + } + return results; +} +async function unconfigureAgents(input) { + const agents = expandTarget(input.target); + const runner = input.runner ?? new SpawnCommandRunner(); + const results = []; + for (const agent of agents) { + if (agent === "opencode") { + results.push(await unconfigureOpenCode(input)); + continue; + } + const executable = agent === "codex" ? "codex" : "claude"; + const existing = await probeCliAgent(agent, runner); + if (!existing.exists) { + results.push({ agent, action: "absent" }); + continue; + } + if (input.dryRun) { + results.push({ agent, action: "would-remove" }); + continue; + } + const args = agent === "codex" ? ["mcp", "remove", SERVER_NAME] : ["mcp", "remove", "--scope", "user", SERVER_NAME]; + await requireSuccess(runner.run(executable, args), `${agent} MCP removal`); + results.push({ agent, action: "removed" }); + } + return results; +} +function launchCommand(options, cliEntry, nodePath) { + return [ + path2.resolve(nodePath), + path2.resolve(cliEntry), + "serve", + "--url", + options.url.toString(), + "--client-id", + options.clientId, + "--callback-port", + String(options.callbackPort) + ]; +} +async function configureCliAgent(agent, command, input, runner) { + const existing = await probeCliAgent(agent, runner); + if (existing.exists && outputMatchesCommand(existing.output, command)) return { agent, action: "unchanged" }; + if (existing.exists && !input.force && !input.dryRun) { + const confirm = input.confirm ?? terminalConfirm; + if (!process.stdin.isTTY && !input.confirm) throw new Error(`${agent} already has a different ${SERVER_NAME} MCP entry; use --force to replace it`); + if (!await confirm(`${agent} already has a different ${SERVER_NAME} MCP entry. Replace it?`)) { + throw new Error(`${agent} MCP configuration was not changed`); + } + } + const action = existing.exists ? "replaced" : "added"; + if (input.dryRun) return { agent, action: existing.exists ? "would-replace" : "would-add" }; + const executable = agent === "codex" ? "codex" : "claude"; + if (existing.exists) { + const removeArgs = agent === "codex" ? ["mcp", "remove", SERVER_NAME] : ["mcp", "remove", "--scope", "user", SERVER_NAME]; + await requireSuccess(runner.run(executable, removeArgs), `${agent} MCP replacement cleanup`); + } + const addArgs = agent === "codex" ? ["mcp", "add", SERVER_NAME, "--", ...command] : ["mcp", "add", "--scope", "user", SERVER_NAME, "--", ...command]; + await requireSuccess(runner.run(executable, addArgs), `${agent} MCP registration`); + return { agent, action }; +} +async function probeCliAgent(agent, runner) { + const executable = agent === "codex" ? "codex" : "claude"; + const args = agent === "codex" ? ["mcp", "get", SERVER_NAME, "--json"] : ["mcp", "get", SERVER_NAME]; + const result = await runner.run(executable, args); + if (result.code === 0) return { exists: true, output: result.stdout }; + const combined = `${result.stdout} +${result.stderr}`; + if (/not found|does not exist|no mcp server|not configured|unknown server/i.test(combined)) return { exists: false, output: combined }; + throw new Error(`Could not inspect ${agent} MCP configuration: ${safeCommandError(result)}`); +} +function outputMatchesCommand(output, command) { + try { + const document = JSON.parse(output); + if (findCommand(document, command)) return true; + } catch { + } + return command.every((part) => output.includes(part)); +} +function findCommand(value, command) { + if (!value || typeof value !== "object") return false; + if (Array.isArray(value)) return value.some((item) => findCommand(item, command)); + const record = value; + if (typeof record.command === "string" && Array.isArray(record.args)) { + const candidate = [record.command, ...record.args.filter((item) => typeof item === "string")]; + if (candidate.length === command.length && candidate.every((item, index) => item === command[index])) return true; + } + return Object.values(record).some((item) => findCommand(item, command)); +} +async function configureOpenCode(input, command) { + const filePath = input.opencodePath ?? defaultOpenCodePath(input.env); + const original = await readOptionalFile(filePath) ?? "{}\n"; + const document = parse(original); + const existing = document?.mcp?.[SERVER_NAME]; + const desired = { type: "local", command, enabled: true }; + if (existing && existing.type === desired.type && existing.enabled === true && arraysEqual(existing.command, command)) { + return { agent: "opencode", action: "unchanged" }; + } + if (existing && !input.force && !input.dryRun) { + const confirm = input.confirm ?? terminalConfirm; + if (!process.stdin.isTTY && !input.confirm) throw new Error(`opencode already has a different ${SERVER_NAME} MCP entry; use --force to replace it`); + if (!await confirm(`opencode already has a different ${SERVER_NAME} MCP entry. Replace it?`)) { + throw new Error("opencode MCP configuration was not changed"); + } + } + if (input.dryRun) return { agent: "opencode", action: existing ? "would-replace" : "would-add" }; + const updated = applyEdits(original, modify(original, ["mcp", SERVER_NAME], desired, { + formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" } + })); + await backupAndAtomicWrite(filePath, updated, await readOptionalFile(filePath) !== void 0); + return { agent: "opencode", action: existing ? "replaced" : "added" }; +} +async function unconfigureOpenCode(input) { + const filePath = input.opencodePath ?? defaultOpenCodePath(input.env); + const original = await readOptionalFile(filePath); + if (original === void 0) return { agent: "opencode", action: "absent" }; + const document = parse(original); + if (!document?.mcp || !(SERVER_NAME in document.mcp)) return { agent: "opencode", action: "absent" }; + if (input.dryRun) return { agent: "opencode", action: "would-remove" }; + const updated = applyEdits(original, modify(original, ["mcp", SERVER_NAME], void 0, { + formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" } + })); + await backupAndAtomicWrite(filePath, updated, true); + return { agent: "opencode", action: "removed" }; +} +function defaultOpenCodePath(env = process.env) { + const base = env.XDG_CONFIG_HOME || (process.platform === "win32" ? path2.join(env.USERPROFILE || os2.homedir(), ".config") : path2.join(os2.homedir(), ".config")); + return path2.join(base, "opencode", "opencode.json"); +} +async function backupAndAtomicWrite(filePath, updated, existed) { + await mkdir2(path2.dirname(filePath), { recursive: true }); + if (existed) { + try { + await copyFile(filePath, `${filePath}.crafttable-mcp.backup`, constants.COPYFILE_EXCL); + } catch (error) { + if (error.code !== "EEXIST") throw error; + } + } + const temporary = `${filePath}.${process.pid}.tmp`; + await writeFile2(temporary, updated, "utf8"); + await rename2(temporary, filePath); +} +async function readOptionalFile(filePath) { + try { + return await readFile2(filePath, "utf8"); + } catch (error) { + if (error.code === "ENOENT") return void 0; + throw error; + } +} +function arraysEqual(value, expected) { + return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]); +} +function expandTarget(target) { + if (target === "all") return ["codex", "claude", "opencode"]; + if (["codex", "claude", "opencode"].includes(target)) return [target]; + throw new Error("Agent must be one of: codex, claude, opencode, all"); +} +async function terminalConfirm(message) { + const readline = createInterface({ input: process.stdin, output: process.stderr }); + try { + return /^y(es)?$/i.test((await readline.question(`${message} [y/N] `)).trim()); + } finally { + readline.close(); + } +} +async function requireSuccess(resultPromise, operation) { + const result = await resultPromise; + if (result.code !== 0) throw new Error(`${operation} failed: ${safeCommandError(result)}`); +} +function safeCommandError(result) { + return (result.stderr || result.stdout || `exit code ${result.code}`).trim(); +} +var SpawnCommandRunner = class { + run(command, args) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] }); + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8").on("data", (chunk) => { + stdout += String(chunk); + }); + child.stderr.setEncoding("utf8").on("data", (chunk) => { + stderr += String(chunk); + }); + child.once("error", (error) => reject(new Error(`Could not run ${command}: ${errorMessage(error)}`))); + child.once("close", (code) => resolve({ code: code ?? 1, stdout, stderr })); + }); + } +}; + +// src/bridge.ts +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListResourceTemplatesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema +} from "@modelcontextprotocol/sdk/types.js"; + +// src/remote.ts +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import open from "open"; + +// src/callbackServer.ts +import { createServer } from "node:http"; +var OAuthCallbackServer = class { + constructor(port, validateState) { + this.port = port; + this.validateState = validateState; + void this.result.catch(() => void 0); + } + port; + validateState; + server; + resolveResult; + rejectResult; + result = new Promise((resolve, reject) => { + this.resolveResult = resolve; + this.rejectResult = reject; + }); + async listen() { + if (this.server) throw new Error("OAuth callback server is already running"); + this.server = createServer((request, response) => { + const url = new URL(request.url ?? "/", `http://127.0.0.1:${this.port}`); + if (request.method !== "GET" || url.pathname !== "/oauth/callback") { + response.writeHead(404, { "content-type": "text/plain; charset=utf-8" }).end("Not found"); + return; + } + const oauthError = url.searchParams.get("error"); + if (oauthError) { + response.writeHead(400, { "content-type": "text/plain; charset=utf-8" }).end("OAuth login failed. Return to the terminal."); + this.rejectResult?.(new Error(`OAuth authorization failed: ${oauthError}`)); + return; + } + if (!this.validateState(url.searchParams.get("state"))) { + response.writeHead(400, { "content-type": "text/plain; charset=utf-8" }).end("OAuth state did not match. Return to the terminal."); + this.rejectResult?.(new Error("OAuth callback state did not match")); + return; + } + const code = url.searchParams.get("code"); + if (!code) { + response.writeHead(400, { "content-type": "text/plain; charset=utf-8" }).end("OAuth authorization code is missing."); + this.rejectResult?.(new Error("OAuth callback did not include an authorization code")); + return; + } + response.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end("CraftTable MCP

Login complete. You can close this window.

"); + this.resolveResult?.({ code }); + }); + await new Promise((resolve, reject) => { + const onError = (error) => reject(new Error(`Could not listen on OAuth callback port ${this.port}: ${error.message}`)); + this.server.once("error", onError); + this.server.listen(this.port, "127.0.0.1", () => { + this.server.off("error", onError); + resolve(); + }); + }); + const address = this.server.address(); + if (!address || address.port !== this.port) throw new Error(`OAuth callback server did not bind port ${this.port}`); + } + async wait(timeoutMs = 10 * 60 * 1e3) { + let timeout; + try { + return await Promise.race([ + this.result, + new Promise((_resolve, reject) => { + timeout = setTimeout(() => reject(new Error("OAuth callback timed out")), timeoutMs); + }) + ]); + } finally { + if (timeout) clearTimeout(timeout); + } + } + async close() { + if (!this.server) return; + const server = this.server; + this.server = void 0; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + } +}; + +// src/oauthProvider.ts +import { randomBytes, timingSafeEqual } from "node:crypto"; +var GameCraftOAuthProvider = class { + constructor(options, tokenStore, discoveryStore, onRedirect, state = randomBytes(32).toString("base64url")) { + this.options = options; + this.tokenStore = tokenStore; + this.discoveryStore = discoveryStore; + this.onRedirect = onRedirect; + this.redirectUrl = new URL(`http://127.0.0.1:${options.callbackPort}/oauth/callback`); + this.clientMetadata = { + client_name: "CraftTable MCP CLI", + redirect_uris: [this.redirectUrl.toString()], + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + token_endpoint_auth_method: "none", + scope: OAUTH_SCOPES + }; + this.account = credentialAccount(options); + this.expectedState = state; + } + options; + tokenStore; + discoveryStore; + onRedirect; + redirectUrl; + clientMetadata; + account; + expectedState; + codeVerifierValue; + state() { + return this.expectedState; + } + 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); + } + clientInformation() { + return { client_id: this.options.clientId }; + } + tokens() { + return this.tokenStore.get(this.account); + } + saveTokens(tokens) { + return this.tokenStore.set(this.account, tokens); + } + redirectToAuthorization(url) { + return this.onRedirect(url); + } + saveCodeVerifier(codeVerifier) { + this.codeVerifierValue = codeVerifier; + } + codeVerifier() { + if (!this.codeVerifierValue) throw new Error("OAuth PKCE verifier is missing or expired"); + return this.codeVerifierValue; + } + discoveryState() { + return this.discoveryStore.get(this.account); + } + saveDiscoveryState(state) { + return this.discoveryStore.set(this.account, state); + } + 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); + if (scope === "all" || scope === "verifier") this.codeVerifierValue = void 0; + } +}; + +// src/remote.ts +async function connectRemote(options, tokenStore, discoveryStore, serviceToken = process.env.CRAFTTABLE_MCP_TOKEN?.trim()) { + const tokens = await tokenStore.get(credentialAccount(options)); + if (tokens) { + const provider = new GameCraftOAuthProvider(options, tokenStore, discoveryStore, () => { + throw new Error("OAuth login is required; run `crafttable-mcp login`"); + }); + return connectWithTransport(options, new StreamableHTTPClientTransport(options.url, { authProvider: provider }), "oauth"); + } + if (serviceToken) { + return connectWithTransport(options, new StreamableHTTPClientTransport(options.url, { + requestInit: { headers: { authorization: `Bearer ${serviceToken}` } } + }), "service-token"); + } + throw new Error("Not logged in; run `crafttable-mcp login` or set CRAFTTABLE_MCP_TOKEN for the legacy service-account path"); +} +async function loginRemote(options, tokenStore, discoveryStore, input = { browser: true }) { + const writeLine = input.writeLine ?? ((value) => process.stderr.write(`${value} +`)); + let authorizationUrl; + const provider = new GameCraftOAuthProvider(options, tokenStore, discoveryStore, async (url) => { + authorizationUrl = url; + if (!input.browser) { + writeLine(`Open this URL to log in: +${url.toString()}`); + return; + } + try { + await open(url.toString(), { wait: false }); + writeLine("Opened the system browser for Game-CraftTable login."); + } catch (error) { + writeLine(`Could not open the browser (${errorMessage(error)}). Open this URL manually: +${url.toString()}`); + } + }); + const callback = new OAuthCallbackServer(options.callbackPort, (state) => provider.validateState(state)); + await callback.listen(); + const firstClient = new Client({ name: "crafttable-mcp-cli", version: "0.1.0" }); + const firstTransport = new StreamableHTTPClientTransport(options.url, { authProvider: provider }); + try { + try { + await firstClient.connect(firstTransport); + const tools = await firstClient.listTools(); + return { alreadyAuthenticated: true, toolCount: tools.tools.length }; + } catch (error) { + if (!(error instanceof UnauthorizedError) && !authorizationUrl) throw error; + const { code } = await callback.wait(input.timeoutMs); + await firstTransport.finishAuth(code); + } + } finally { + await firstClient.close().catch(() => void 0); + await callback.close().catch(() => void 0); + } + const connection = await connectRemote(options, tokenStore, discoveryStore, ""); + try { + const tools = await connection.client.listTools(); + return { alreadyAuthenticated: false, toolCount: tools.tools.length }; + } finally { + await connection.close(); + } +} +async function logoutRemote(options, tokenStore, discoveryStore, localOnly, fetchFn = fetch) { + const account = credentialAccount(options); + const tokens = await tokenStore.get(account); + if (tokens && !localOnly) { + 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) }; +} +async function connectWithTransport(options, transport, authentication) { + const client = new Client({ name: "crafttable-mcp-cli", version: "0.1.0" }); + try { + await client.connect(transport); + } catch (error) { + await client.close().catch(() => void 0); + if (error instanceof UnauthorizedError) throw new Error("OAuth login is required; run `crafttable-mcp login`"); + throw error; + } + return { + client, + transport, + authentication, + close: () => client.close() + }; +} +async function discoverRevocationEndpoint(resource, fetchFn) { + const metadataUrl = new URL(`/.well-known/oauth-protected-resource${resource.pathname === "/" ? "" : resource.pathname}`, resource.origin); + const protectedResponse = await fetchFn(metadataUrl, { headers: { accept: "application/json" } }); + if (!protectedResponse.ok) throw new Error(`OAuth protected-resource discovery returned HTTP ${protectedResponse.status}`); + const protectedMetadata = await protectedResponse.json(); + const issuer = Array.isArray(protectedMetadata.authorization_servers) ? protectedMetadata.authorization_servers.find((value) => typeof value === "string") : void 0; + if (!issuer) throw new Error("OAuth protected-resource metadata has no authorization server"); + const issuerUrl = new URL(issuer); + const candidates = [ + new URL(`${issuerUrl.toString().replace(/\/$/, "")}/.well-known/openid-configuration`), + new URL(`/.well-known/openid-configuration${issuerUrl.pathname === "/" ? "" : issuerUrl.pathname}`, issuerUrl.origin) + ]; + for (const candidate of candidates) { + const response = await fetchFn(candidate, { headers: { accept: "application/json" } }); + if (!response.ok) continue; + try { + const metadata = await response.json(); + if (typeof metadata.revocation_endpoint === "string") return new URL(metadata.revocation_endpoint); + } catch { + } + } + throw new Error("OAuth authorization server does not advertise a revocation endpoint"); +} +async function revokeTokens(endpoint, clientId, tokens, fetchFn) { + const candidates = [tokens.refresh_token ? { token: tokens.refresh_token, hint: "refresh_token" } : { token: tokens.access_token, hint: "access_token" }]; + for (const value of candidates) { + const response = await fetchFn(endpoint, { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" }, + body: new URLSearchParams({ token: value.token, token_type_hint: value.hint, client_id: clientId }) + }); + if (!response.ok) throw new Error(`OAuth token revocation returned HTTP ${response.status}; local credentials were retained`); + } +} + +// src/bridge.ts +async function serveBridge(options, tokenStore, discoveryStore, serviceToken = process.env.CRAFTTABLE_MCP_TOKEN?.trim()) { + const upstream = await connectRemote(options, tokenStore, discoveryStore, serviceToken); + if (upstream.authentication === "service-token") { + process.stderr.write("CraftTable MCP: using the legacy service-account token path.\n"); + } + const server = createProxyServer(upstream.client); + const transport = new StdioServerTransport(); + const close = async () => { + await server.close().catch(() => void 0); + await upstream.close().catch(() => void 0); + }; + process.once("SIGINT", () => void close()); + process.once("SIGTERM", () => void close()); + try { + await server.connect(transport); + } catch (error) { + await close(); + throw error; + } +} +function createProxyServer(upstream) { + const server = new Server({ name: "crafttable-mcp-stdio-bridge", version: "0.1.0" }, { + capabilities: { + tools: {}, + resources: {} + } + }); + server.setRequestHandler(ListToolsRequestSchema, (request) => upstream.listTools(request.params)); + server.setRequestHandler(CallToolRequestSchema, (request) => upstream.callTool(request.params)); + server.setRequestHandler(ListResourcesRequestSchema, (request) => upstream.listResources(request.params)); + server.setRequestHandler(ListResourceTemplatesRequestSchema, (request) => upstream.listResourceTemplates(request.params)); + server.setRequestHandler(ReadResourceRequestSchema, (request) => upstream.readResource(request.params)); + return server; +} + +// src/credentials.ts +var KEYRING_SERVICE = "CraftTable MCP"; +var KeyringTokenStore = class { + async get(account) { + let serialized; + try { + const entry = await keyringEntry(account); + serialized = await entry.getPassword(); + } catch (error) { + throw keyringError(error); + } + if (!serialized) return void 0; + try { + const value = JSON.parse(serialized); + if (!value.access_token || !value.token_type) throw new Error("missing token fields"); + return value; + } catch { + throw new Error("The saved CraftTable MCP credential is invalid; run `crafttable-mcp logout --local-only` and log in again"); + } + } + async set(account, tokens) { + try { + const entry = await keyringEntry(account); + await entry.setPassword(JSON.stringify(tokens)); + } catch (error) { + throw keyringError(error); + } + } + async delete(account) { + try { + const entry = await keyringEntry(account); + await entry.deleteCredential(); + } catch (error) { + const message = String(error?.message ?? error).toLowerCase(); + if (message.includes("no entry") || message.includes("not found")) return; + throw keyringError(error); + } + } +}; +async function keyringEntry(account) { + try { + const { AsyncEntry } = await import("@napi-rs/keyring"); + return new AsyncEntry(KEYRING_SERVICE, account); + } catch (error) { + throw keyringError(error); + } +} +function keyringError(error) { + if (error instanceof Error && error.message.startsWith("The operating-system credential store is unavailable")) return error; + const detail = error instanceof Error ? error.message : String(error); + 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.`); +} + +// src/cli.ts +var program = new Command().name("crafttable-mcp").description("OAuth client and stdio bridge for CraftTable MCP").version("0.1.0"); +withConnection(program.command("login").description("Log in through the system browser")).option("--no-browser", "print the authorization URL instead of opening it").option("--timeout ", "OAuth callback timeout", "600000").action(async (flags) => { + const options = connectionOptions(flags); + const timeoutMs = positiveInteger(flags.timeout, "OAuth callback timeout"); + const result = await loginRemote(options, new KeyringTokenStore(), new DiscoveryStore(), { browser: flags.browser, timeoutMs }); + printJson({ loggedIn: true, alreadyAuthenticated: result.alreadyAuthenticated, server: options.url.toString(), tools: result.toolCount }); +}); +withConnection(program.command("status").description("Check saved login state and remote MCP connectivity")).action(async (flags) => { + const options = connectionOptions(flags); + const tokens = await new KeyringTokenStore().get(credentialAccount(options)); + const connection = await connectRemote(options, new KeyringTokenStore(), new DiscoveryStore()); + try { + const tools = await connection.client.listTools(); + printJson({ + loggedIn: Boolean(tokens), + authentication: connection.authentication, + connected: true, + server: options.url.toString(), + tools: tools.tools.length + }); + } finally { + await connection.close(); + } +}); +withConnection(program.command("logout").description("Revoke OAuth tokens and remove the local credential")).option("--local-only", "remove local credentials without contacting the authorization server").action(async (flags) => { + const options = connectionOptions(flags); + const result = await logoutRemote(options, new KeyringTokenStore(), new DiscoveryStore(), Boolean(flags.localOnly)); + printJson({ loggedIn: false, credentialRemoved: result.hadCredential, revoked: result.revoked, localOnly: Boolean(flags.localOnly) }); +}); +withConnection(program.command("tools").description("List remote MCP tools")).action(async (flags) => withClient(connectionOptions(flags), async (client) => printJson(await client.listTools()))); +withConnection(program.command("resources").description("List remote MCP resources and resource templates")).action(async (flags) => withClient(connectionOptions(flags), async (client) => printJson({ + resources: (await client.listResources()).resources, + resourceTemplates: (await client.listResourceTemplates()).resourceTemplates +}))); +withConnection(program.command("read ").description("Read an MCP resource")).action(async (uri, flags) => withClient(connectionOptions(flags), async (client) => printJson(await client.readResource({ uri })))); +withConnection(program.command("call [json]").description("Call an MCP tool")).action(async (tool, json, flags) => withClient(connectionOptions(flags), async (client) => { + printJson(await client.callTool({ name: tool, arguments: parseObject(json ?? "{}") })); +})); +withConnection(program.command("serve").description("Run the local stdio bridge")).action(async (flags) => { + await serveBridge(connectionOptions(flags), new KeyringTokenStore(), new DiscoveryStore()); +}); +for (const operation of ["configure", "unconfigure"]) { + withConnection(program.command(`${operation} `).description(`${operation === "configure" ? "Add" : "Remove"} the stdio bridge 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"); + const input = { + target: agent, + options: connectionOptions(flags), + cliEntry, + dryRun: Boolean(flags.dryRun), + force: Boolean(flags.force) + }; + const result = operation === "configure" ? await configureAgents(input) : await unconfigureAgents(input); + printJson({ result }); + }); +} +function withConnection(command) { + return command.option("--url ", "MCP Streamable HTTP URL").option("--client-id ", "pre-registered OAuth public client ID").option("--callback-port ", "fixed localhost OAuth callback port"); +} +function connectionOptions(flags) { + return resolveClientOptions(flags); +} +async function withClient(options, action) { + const connection = await connectRemote(options, new KeyringTokenStore(), new DiscoveryStore()); + try { + await action(connection.client); + } finally { + await connection.close(); + } +} +function parseObject(raw) { + let value; + try { + value = JSON.parse(raw); + } catch { + throw new Error("Tool arguments must be a JSON object"); + } + if (!value || Array.isArray(value) || typeof value !== "object") throw new Error("Tool arguments must be a JSON object"); + return value; +} +function positiveInteger(value, name) { + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${name} must be a positive integer`); + return parsed; +} +function printJson(value) { + process.stdout.write(`${JSON.stringify(value, null, 2)} +`); +} +program.parseAsync().catch((error) => { + process.stderr.write(`CraftTable MCP failed: ${error instanceof Error ? error.message : String(error)} +`); + process.exitCode = 1; +}); +//# sourceMappingURL=cli.js.map diff --git a/dist/cli.js.map b/dist/cli.js.map new file mode 100644 index 0000000..23ff3c8 --- /dev/null +++ b/dist/cli.js.map @@ -0,0 +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.0\");\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 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 { spawn } from \"node:child_process\";\nimport { constants } from \"node:fs\";\nimport { copyFile, mkdir, readFile, rename, 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 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\";\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};\n\nexport type ConfigureResult = {\n agent: AgentName;\n action: \"added\" | \"replaced\" | \"removed\" | \"unchanged\" | \"absent\" | \"would-add\" | \"would-replace\" | \"would-remove\";\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 if (agent === \"opencode\") {\n results.push(await configureOpenCode(input, command));\n } else {\n results.push(await configureCliAgent(agent, command, input, runner));\n }\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 if (agent === \"opencode\") {\n results.push(await unconfigureOpenCode(input));\n continue;\n }\n const executable = agent === \"codex\" ? \"codex\" : \"claude\";\n const existing = await probeCliAgent(agent, runner);\n if (!existing.exists) {\n results.push({ agent, action: \"absent\" });\n continue;\n }\n if (input.dryRun) {\n results.push({ agent, action: \"would-remove\" });\n continue;\n }\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 results.push({ agent, action: \"removed\" });\n }\n return results;\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\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.0\" }, {\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 { GameCraftOAuthProvider } 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 GameCraftOAuthProvider(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 let authorizationUrl: URL | undefined;\n const provider = new GameCraftOAuthProvider(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 Game-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.0\" });\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.0\" });\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 metadataUrl = new URL(`/.well-known/oauth-protected-resource${resource.pathname === \"/\" ? \"\" : resource.pathname}`, resource.origin);\n const protectedResponse = await fetchFn(metadataUrl, { headers: { accept: \"application/json\" } });\n if (!protectedResponse.ok) throw new Error(`OAuth protected-resource discovery returned HTTP ${protectedResponse.status}`);\n const protectedMetadata = await protectedResponse.json() as { authorization_servers?: unknown };\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\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 GameCraftOAuthProvider 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 type { OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\";\n\nexport const KEYRING_SERVICE = \"CraftTable MCP\";\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 async get(account: string): Promise {\n let serialized: string | undefined;\n try {\n const entry = await keyringEntry(account);\n serialized = await entry.getPassword();\n } catch (error) {\n throw keyringError(error);\n }\n if (!serialized) return undefined;\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 new Error(\"The saved CraftTable MCP credential is invalid; run `crafttable-mcp logout --local-only` and log in again\");\n }\n }\n\n async set(account: string, tokens: OAuthTokens): Promise {\n try {\n const entry = await keyringEntry(account);\n await entry.setPassword(JSON.stringify(tokens));\n } catch (error) {\n throw keyringError(error);\n }\n }\n\n async delete(account: string): Promise {\n try {\n const entry = await keyringEntry(account);\n await entry.deleteCredential();\n } catch (error) {\n const message = String((error as Error)?.message ?? error).toLowerCase();\n if (message.includes(\"no entry\") || message.includes(\"not found\")) return;\n throw keyringError(error);\n }\n }\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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", + "names": ["mkdir", "readFile", "rename", "writeFile", "os", "path", "path", "os", "mkdir", "writeFile", "rename", "readFile"] +} diff --git a/package.json b/package.json index 5ac47ff..897e979 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "dev": "tsx src/cli.ts", "typecheck": "tsc -p tsconfig.json --noEmit", "build": "npm run typecheck && esbuild src/cli.ts --bundle --platform=node --format=esm --packages=external --sourcemap --outfile=dist/cli.js", - "prepare": "npm run build", "test": "tsx --test test/*.test.ts" }, "dependencies": {