gaslamp 1.0.0 → 1.0.1

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.
package/bin/gaslamp.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // gaslamp — pass the lamp between Claude Code and Codex over MCP.
2
+ // gaslamp: Minimal MCP server that lets Claude and Codex talk to each other.
3
3
  //
4
4
  // gaslamp run the MCP server on stdio (what Codex spawns)
5
5
  // gaslamp serve same as above, explicit
@@ -18,7 +18,7 @@ import { fileURLToPath } from "node:url";
18
18
  const HERE = dirname(fileURLToPath(import.meta.url));
19
19
  const pkg = JSON.parse(readFileSync(join(HERE, "..", "package.json"), "utf8"));
20
20
 
21
- const HELP = `gaslamp ${pkg.version} — pass the lamp between Claude Code and Codex over MCP
21
+ const HELP = `gaslamp ${pkg.version}: Minimal MCP server that lets Claude and Codex talk to each other.
22
22
 
23
23
  Usage:
24
24
  gaslamp [serve] Run the MCP server on stdio (what Codex spawns).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gaslamp",
3
- "version": "1.0.0",
4
- "description": "Pass the lamp between Claude Code and Codex over MCP — either agent can hand work to the other for a review, a second opinion, or a fix. Zero dependencies.",
3
+ "version": "1.0.1",
4
+ "description": "Minimal MCP server that lets Claude and Codex talk to each other.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "gaslamp": "bin/gaslamp.mjs"
package/src/setup.mjs CHANGED
@@ -16,6 +16,8 @@
16
16
  // the package is published, or for from-source dev.
17
17
 
18
18
  import { spawnSync } from "node:child_process";
19
+ import { readFileSync, writeFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
19
21
  import { dirname, join, resolve } from "node:path";
20
22
  import { fileURLToPath } from "node:url";
21
23
  import { which } from "./which.mjs";
@@ -26,6 +28,45 @@ function run(label, cmd, args) {
26
28
  return r.status === 0;
27
29
  }
28
30
 
31
+ // `codex mcp add` cannot persist per-server timeouts (its `-c` flag is a runtime
32
+ // override that never lands in the block), so we patch ~/.codex/config.toml
33
+ // directly. Without this, Codex's MCP client kills any tools/call that runs past
34
+ // its default tool_timeout_sec — a substantial consult (30-45 min) dies with
35
+ // `timed out awaiting tools/call after 120s` while the gaslamp child keeps
36
+ // working, orphaned. The server itself has no timeout; this is the client side.
37
+ // Idempotent: sets the keys in-place if present, inserts them if not.
38
+ function patchCodexTimeouts() {
39
+ const tool = process.env.GASLAMP_TOOL_TIMEOUT_SEC || "100000"; // ~27.8h — matches Claude Code's own default MCP tool timeout (1e8 ms), so both directions are symmetric
40
+ const startup = process.env.GASLAMP_STARTUP_TIMEOUT_SEC || "30"; // headroom for npx cold-start
41
+ const cfgPath = join(process.env.CODEX_HOME || join(homedir(), ".codex"), "config.toml");
42
+
43
+ let text;
44
+ try { text = readFileSync(cfgPath, "utf8"); }
45
+ catch { console.warn(` (could not read ${cfgPath}; skipped timeout patch)`); return; }
46
+
47
+ const lines = text.split("\n");
48
+ const header = lines.findIndex((l) => l.trim() === "[mcp_servers.gaslamp]");
49
+ if (header < 0) { console.warn(" (no [mcp_servers.gaslamp] block found; skipped timeout patch)"); return; }
50
+
51
+ // Block runs from the header to the next table header (line starting with `[`).
52
+ let end = lines.length;
53
+ for (let i = header + 1; i < lines.length; i++) { if (/^\s*\[/.test(lines[i])) { end = i; break; } }
54
+ const block = lines.slice(header, end);
55
+
56
+ const setKey = (key, val) => {
57
+ const i = block.findIndex((l) => new RegExp(`^\\s*${key}\\s*=`).test(l));
58
+ if (i >= 0) { block[i] = `${key} = ${val}`; return; }
59
+ let at = block.length; // insert before trailing blank lines
60
+ while (at > 1 && block[at - 1].trim() === "") at--;
61
+ block.splice(at, 0, `${key} = ${val}`);
62
+ };
63
+ setKey("tool_timeout_sec", tool);
64
+ setKey("startup_timeout_sec", startup);
65
+
66
+ writeFileSync(cfgPath, [...lines.slice(0, header), ...block, ...lines.slice(end)].join("\n"));
67
+ process.stdout.write(` timeouts: tool_timeout_sec=${tool}s startup_timeout_sec=${startup}s in ${cfgPath}\n`);
68
+ }
69
+
29
70
  // Quietly remove an existing registration (ignore "not found" noise).
30
71
  function remove(cli, name, scopeArgs = []) {
31
72
  spawnSync(cli, ["mcp", "remove", name, ...scopeArgs], { stdio: "ignore" });
@@ -62,6 +103,7 @@ export function runSetup(argv = []) {
62
103
  serveCmd = ["mcp", "add", "gaslamp", "--", "npx", "-y", "gaslamp", "serve"];
63
104
  }
64
105
  const okCodex = run("codex -> claude", "codex", serveCmd);
106
+ if (okCodex) patchCodexTimeouts(); // codex mcp add can't persist these; do it ourselves
65
107
 
66
108
  // --- Claude -> Codex: claude gets gaslamp's codex tools (user scope) --------
67
109
  remove("claude", "gaslamp", ["-s", "user"]);