ralph-hero-mcp-server 2.5.71 → 2.5.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +112 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@
6
6
  * operations. Connects via stdio transport for use as a Claude Code
7
7
  * plugin's bundled MCP server.
8
8
  */
9
+ import { execSync } from "node:child_process";
10
+ import { realpathSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
9
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
14
  import { createGitHubClient } from "./github-client.js";
@@ -34,27 +37,98 @@ function resolveEnv(name) {
34
37
  return undefined;
35
38
  return val;
36
39
  }
40
+ /**
41
+ * Module-level cache for the resolved `gh auth token` value.
42
+ *
43
+ * - `null` — not yet resolved (initial state).
44
+ * - `undefined` — resolved-to-failure (gh missing / unauthenticated / timed out).
45
+ * - `string` — resolved-to-success (the trimmed token).
46
+ *
47
+ * The `null` sentinel is intentional: `undefined` is itself a valid resolved
48
+ * value, so we need a separate "not-yet-resolved" marker.
49
+ *
50
+ * Exported for tests via `resetGhAuthTokenCache()` below.
51
+ */
52
+ let cachedGhAuthToken = null;
53
+ /**
54
+ * Test-only helper to reset the module-level cache between cases.
55
+ * Production code never calls this — the cache is intentionally process-scoped.
56
+ */
57
+ export function resetGhAuthTokenCache() {
58
+ cachedGhAuthToken = null;
59
+ }
60
+ /**
61
+ * Resolve a GitHub token by invoking `gh auth token` as a subprocess.
62
+ *
63
+ * **Contract**: The result of this function flows ONLY into the internal
64
+ * `repoToken` variable. It is NEVER re-exported via `process.env.GH_TOKEN`
65
+ * or `process.env.GITHUB_TOKEN`. This preserves the anti-collision invariant
66
+ * documented in the contract test at
67
+ * `src/__tests__/init-config.test.ts` (the `forbiddenVars` list).
68
+ *
69
+ * The subprocess runs at most once per process (cached). On any failure
70
+ * (gh not installed, not authenticated, network timeout, etc.) the result
71
+ * is cached as `undefined` so subsequent callers don't re-incur the cost.
72
+ *
73
+ * - `timeout: 3000` — fail fast if gh is hung.
74
+ * - `stdio: ["ignore", "pipe", "ignore"]` — suppress stderr noise on the
75
+ * common "not authenticated" error path.
76
+ *
77
+ * @returns The trimmed token string on success, otherwise `undefined`.
78
+ */
79
+ export function resolveGhAuthToken() {
80
+ if (cachedGhAuthToken !== null) {
81
+ return cachedGhAuthToken;
82
+ }
83
+ try {
84
+ const output = execSync("gh auth token", {
85
+ encoding: "utf8",
86
+ stdio: ["ignore", "pipe", "ignore"],
87
+ timeout: 3000,
88
+ });
89
+ // execSync with encoding: "utf8" returns a string; coerce defensively in
90
+ // case a mock returns a Buffer.
91
+ const trimmed = output.toString().trim();
92
+ cachedGhAuthToken = trimmed.length > 0 ? trimmed : undefined;
93
+ }
94
+ catch {
95
+ cachedGhAuthToken = undefined;
96
+ }
97
+ return cachedGhAuthToken ?? undefined;
98
+ }
37
99
  function initGitHubClient(debugLogger) {
38
100
  // Repo token: for repository operations (issues, PRs, comments)
39
- const repoToken = resolveEnv("RALPH_GH_REPO_TOKEN") || resolveEnv("RALPH_HERO_GITHUB_TOKEN");
101
+ // Resolution chain (highest priority first):
102
+ // 1. RALPH_GH_REPO_TOKEN — explicit split-token repo override
103
+ // 2. RALPH_HERO_GITHUB_TOKEN — legacy single-token form
104
+ // 3. gh auth token — gh CLI keychain (subprocess, cached)
105
+ const repoToken = resolveEnv("RALPH_GH_REPO_TOKEN") ||
106
+ resolveEnv("RALPH_HERO_GITHUB_TOKEN") ||
107
+ resolveGhAuthToken();
40
108
  // Project token: for Projects V2 operations (fields, workflow state)
41
- // Falls back to repo token if not set
109
+ // Falls back to repo token if not set (transitively gains the gh source).
42
110
  const projectToken = resolveEnv("RALPH_GH_PROJECT_TOKEN") || repoToken;
43
111
  if (!repoToken) {
44
112
  console.error("[ralph-hero] Error: No GitHub token found.\n" +
45
113
  "\n" +
46
- "Quick fix — add to .claude/settings.local.json:\n" +
114
+ "Quickest fix — authenticate gh (recommended):\n" +
115
+ "\n" +
116
+ " gh auth login -s repo,project,read:org\n" +
117
+ "\n" +
118
+ "Then restart Claude Code. The MCP server will pick up the token\n" +
119
+ "from the gh CLI keychain automatically.\n" +
120
+ "\n" +
121
+ "Alternative — paste a PAT into Claude Code settings:\n" +
47
122
  "\n" +
123
+ " Add to .claude/settings.local.json:\n" +
48
124
  ' {\n' +
49
125
  ' "env": {\n' +
50
126
  ' "RALPH_HERO_GITHUB_TOKEN": "ghp_your_token_here"\n' +
51
127
  " }\n" +
52
128
  " }\n" +
53
129
  "\n" +
54
- "Then restart Claude Code.\n" +
55
- "\n" +
56
- "Generate a token at: https://github.com/settings/tokens\n" +
57
- "Required scopes: repo, project\n" +
130
+ " Generate a token at: https://github.com/settings/tokens\n" +
131
+ " Required scopes: repo, project\n" +
58
132
  "\n" +
59
133
  "For advanced setups (dual tokens, org projects), run /ralph-hero:setup.");
60
134
  process.exit(1);
@@ -86,7 +160,9 @@ function initGitHubClient(debugLogger) {
86
160
  }
87
161
  const repoTokenSource = resolveEnv("RALPH_GH_REPO_TOKEN")
88
162
  ? "RALPH_GH_REPO_TOKEN"
89
- : "RALPH_HERO_GITHUB_TOKEN";
163
+ : resolveEnv("RALPH_HERO_GITHUB_TOKEN")
164
+ ? "RALPH_HERO_GITHUB_TOKEN"
165
+ : "gh auth (keychain)";
90
166
  console.error(`[ralph-hero] Repo token: ${repoTokenSource}`);
91
167
  if (projectToken !== repoToken) {
92
168
  console.error(`[ralph-hero] Project token: RALPH_GH_PROJECT_TOKEN (separate)`);
@@ -221,7 +297,9 @@ function registerCoreTools(server, client) {
221
297
  // Token source detection — re-derive which env vars resolved
222
298
  const repoTokenSource = resolveEnv("RALPH_GH_REPO_TOKEN")
223
299
  ? "RALPH_GH_REPO_TOKEN"
224
- : "RALPH_HERO_GITHUB_TOKEN";
300
+ : resolveEnv("RALPH_HERO_GITHUB_TOKEN")
301
+ ? "RALPH_HERO_GITHUB_TOKEN"
302
+ : "gh auth (keychain)";
225
303
  const projectTokenSource = resolveEnv("RALPH_GH_PROJECT_TOKEN")
226
304
  ? "RALPH_GH_PROJECT_TOKEN"
227
305
  : repoTokenSource;
@@ -329,9 +407,29 @@ async function main() {
329
407
  await server.connect(transport);
330
408
  console.error("[ralph-hero] MCP server connected and ready.");
331
409
  }
332
- // Run
333
- main().catch((error) => {
334
- console.error("[ralph-hero] Fatal error:", error);
335
- process.exit(1);
336
- });
410
+ // Run only when invoked as the entry point (not when imported by tests).
411
+ // `process.argv[1]` is the script path Node was started with. When npm/npx
412
+ // invokes a `bin` script it sets argv[1] to the bin-shim symlink (e.g.
413
+ // `node_modules/.bin/ralph-hero-mcp-server`), not the resolved target — so
414
+ // a string-suffix match on `/dist/index.js` would miss the bin-shim case
415
+ // and silently skip main(). Compare realpath(argv[1]) to this module's
416
+ // own URL instead; that's the canonical ESM "am I the entry point?" check.
417
+ const isEntryPoint = (() => {
418
+ try {
419
+ if (!process.argv[1])
420
+ return process.env.RALPH_HERO_RUN_MAIN === "true";
421
+ const argvReal = realpathSync(process.argv[1]);
422
+ const moduleReal = fileURLToPath(import.meta.url);
423
+ return argvReal === moduleReal || process.env.RALPH_HERO_RUN_MAIN === "true";
424
+ }
425
+ catch {
426
+ return process.env.RALPH_HERO_RUN_MAIN === "true";
427
+ }
428
+ })();
429
+ if (isEntryPoint) {
430
+ main().catch((error) => {
431
+ console.error("[ralph-hero] Fatal error:", error);
432
+ process.exit(1);
433
+ });
434
+ }
337
435
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.5.71",
3
+ "version": "2.5.73",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",