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 MCPLogin 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 MCPLogin 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": {