diff --git a/README.md b/README.md index ebeaa73..035c405 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ This gives you 14 MCP tools, 5 workflow skills that teach Claude *when* and *how **Other agents** (Cursor, Codex CLI, Gemini CLI, GitHub Copilot) — restart your agent session. The MCP server and skills are already configured. +For **Codex CLI**, setup now registers the server with `codex mcp add` using absolute paths so the tools work even when Codex launches from outside your project root. + First query takes ~2s (tsserver warmup). Subsequent queries: 1-60ms. ## Requirements @@ -147,6 +149,26 @@ npx typegraph-mcp check ## Manual MCP configuration +### Codex CLI + +Register the server with absolute paths: + +```bash +codex mcp add typegraph \ + --env TYPEGRAPH_PROJECT_ROOT=/absolute/path/to/your-project \ + --env TYPEGRAPH_TSCONFIG=/absolute/path/to/your-project/tsconfig.json \ + -- npx tsx /absolute/path/to/your-project/plugins/typegraph-mcp/server.ts +``` + +Verify with: + +```bash +codex mcp get typegraph +codex mcp list +``` + +### JSON-based MCP clients + Add to `.claude/mcp.json` (or `~/.claude/mcp.json` for global): ```json diff --git a/check.ts b/check.ts index 67fb725..6a71dc2 100644 --- a/check.ts +++ b/check.ts @@ -12,7 +12,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { createRequire } from "node:module"; -import { spawn } from "node:child_process"; +import { spawn, spawnSync } from "node:child_process"; import { resolveConfig, type TypegraphConfig } from "./config.js"; // ─── Result Type ───────────────────────────────────────────────────────────── @@ -186,10 +186,17 @@ export async function main(configOverride?: TypegraphConfig): Promise; +} { + return { + command: "npx", + args: ["tsx", path.resolve(projectRoot, PLUGIN_DIR_NAME, "server.ts")], + env: { + TYPEGRAPH_PROJECT_ROOT: projectRoot, + TYPEGRAPH_TSCONFIG: path.resolve(projectRoot, "tsconfig.json"), + }, + }; +} + /** Register the typegraph MCP server in agent-specific config files */ function registerMcpServers(projectRoot: string, selectedAgents: AgentId[]): void { if (selectedAgents.includes("cursor")) { @@ -259,6 +274,57 @@ function deregisterJsonMcp(projectRoot: string, configPath: string, rootKey: str /** Register MCP server in Codex CLI's TOML config */ function registerCodexMcp(projectRoot: string): void { + const absoluteEntry = getAbsoluteMcpServerEntry(projectRoot); + + const codexGet = spawnSync("codex", ["mcp", "get", "typegraph"], { + stdio: "pipe", + encoding: "utf-8", + }); + + if (codexGet.status === 0) { + const output = `${codexGet.stdout ?? ""}${codexGet.stderr ?? ""}`; + const hasServerPath = output.includes(absoluteEntry.args[1]!); + const hasProjectRoot = output.includes("TYPEGRAPH_PROJECT_ROOT=*****") || output.includes(projectRoot); + const hasTsconfig = output.includes("TYPEGRAPH_TSCONFIG=*****") || output.includes(path.resolve(projectRoot, "tsconfig.json")); + if (hasServerPath && hasProjectRoot && hasTsconfig) { + p.log.info("Codex CLI: typegraph MCP server already registered"); + return; + } + spawnSync("codex", ["mcp", "remove", "typegraph"], { + stdio: "pipe", + encoding: "utf-8", + }); + } + + const codexAdd = spawnSync( + "codex", + [ + "mcp", + "add", + "typegraph", + "--env", + `TYPEGRAPH_PROJECT_ROOT=${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}`, + "--env", + `TYPEGRAPH_TSCONFIG=${absoluteEntry.env.TYPEGRAPH_TSCONFIG}`, + "--", + absoluteEntry.command, + ...absoluteEntry.args, + ], + { + stdio: "pipe", + encoding: "utf-8", + } + ); + + if (codexAdd.status === 0) { + p.log.success("Codex CLI: registered typegraph MCP server"); + return; + } + + p.log.warn( + `Codex CLI registration failed — falling back to ${".codex/config.toml"}` + ); + const configPath = ".codex/config.toml"; const fullPath = path.resolve(projectRoot, configPath); let content = ""; @@ -275,9 +341,9 @@ function registerCodexMcp(projectRoot: string): void { const block = [ "", "[mcp_servers.typegraph]", - 'command = "npx"', - 'args = ["tsx", "./plugins/typegraph-mcp/server.ts"]', - 'env = { TYPEGRAPH_PROJECT_ROOT = ".", TYPEGRAPH_TSCONFIG = "./tsconfig.json" }', + `command = "${absoluteEntry.command}"`, + `args = ["${absoluteEntry.args[0]}", "${absoluteEntry.args[1]}"]`, + `env = { TYPEGRAPH_PROJECT_ROOT = "${absoluteEntry.env.TYPEGRAPH_PROJECT_ROOT}", TYPEGRAPH_TSCONFIG = "${absoluteEntry.env.TYPEGRAPH_TSCONFIG}" }`, "", ].join("\n"); @@ -292,6 +358,14 @@ function registerCodexMcp(projectRoot: string): void { /** Deregister MCP server from Codex CLI's TOML config */ function deregisterCodexMcp(projectRoot: string): void { + const codexRemove = spawnSync("codex", ["mcp", "remove", "typegraph"], { + stdio: "pipe", + encoding: "utf-8", + }); + if (codexRemove.status === 0) { + p.log.info("Codex CLI: removed typegraph MCP server"); + } + const configPath = ".codex/config.toml"; const fullPath = path.resolve(projectRoot, configPath); if (!fs.existsSync(fullPath)) return; diff --git a/package-lock.json b/package-lock.json index a862cf3..461a948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "devDependencies": { "@types/node": "^25.3.0", "tsup": "^8.5.0", + "tsx": "^4.21.0", "typescript": "^5.8.0" } }, @@ -2239,6 +2240,19 @@ "node": ">= 0.4" } }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2845,6 +2859,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/rollup": { "version": "4.58.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.58.0.tgz", @@ -3237,6 +3261,26 @@ } } }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", diff --git a/package.json b/package.json index 8582b39..e884ea5 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "devDependencies": { "@types/node": "^25.3.0", "tsup": "^8.5.0", + "tsx": "^4.21.0", "typescript": "^5.8.0" } }