ralph-hero-mcp-server 2.5.71 → 2.5.72

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