@mandujs/mcp 0.28.0 → 0.28.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/tools/brain.ts +66 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/mcp",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
4
4
  "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@mandujs/core": "^0.41.0",
37
+ "@mandujs/core": "^0.41.1",
38
38
  "@mandujs/ate": "^0.24.0",
39
39
  "@mandujs/skills": "^0.18.0",
40
40
  "@modelcontextprotocol/sdk": "^1.25.3"
@@ -171,7 +171,7 @@ export const brainToolDefinitions: Tool[] = [
171
171
  {
172
172
  name: "mandu.brain.login",
173
173
  description:
174
- "Authenticate the brain to an LLM provider. For openai, delegates to the OpenAI-official `@openai/codex` CLI (writes ~/.codex/auth.json; Mandu reads + auto-refreshes). MUST be invoked from a context where a terminal / browser is available — the CLI opens a browser tab for OAuth. Anthropic path uses the Mandu OAuth flow with a local loopback listener.",
174
+ "Authenticate the brain to an LLM provider. For openai, spawns `npx @openai/codex login` which opens the user's default browser to the OpenAI OAuth page; on approval, the token lands in ~/.codex/auth.json and this tool returns. Anthropic path uses the Mandu OAuth flow with a local loopback listener.",
175
175
  annotations: { readOnlyHint: false },
176
176
  inputSchema: {
177
177
  type: "object",
@@ -181,6 +181,11 @@ export const brainToolDefinitions: Tool[] = [
181
181
  enum: ["openai", "anthropic"],
182
182
  description: "Which provider to sign into. Default: openai.",
183
183
  },
184
+ waitMs: {
185
+ type: "number",
186
+ description:
187
+ "How long to wait for auth.json to appear after spawning the OAuth flow. Default 180000 (3 min). Increase if the user takes longer to approve in the browser.",
188
+ },
184
189
  },
185
190
  required: [],
186
191
  },
@@ -649,26 +654,28 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
649
654
  };
650
655
 
651
656
  handlers["mandu.brain.login"] = async (args) => {
652
- const { provider = "openai" } = args as { provider?: "openai" | "anthropic" };
653
- const { spawnSync } = await import("node:child_process");
657
+ const { provider = "openai", waitMs = 180000 } = args as {
658
+ provider?: "openai" | "anthropic";
659
+ waitMs?: number;
660
+ };
654
661
 
655
662
  if (provider === "openai") {
656
- // Delegate to the OpenAI-official Codex CLI. This MUST run
657
- // interactively (browser-based OAuth). If stdin/stdout aren't a
658
- // TTY the agent should surface the command for the user to run
659
- // manually instead of attempting to spawn.
660
- const isTty = Boolean(process.stdout.isTTY && process.stdin.isTTY);
661
- if (!isTty) {
663
+ const core = await import("@mandujs/core");
664
+ const auth = new core.ChatGPTAuth();
665
+ const existing = auth.locateAuthFile();
666
+ if (existing) {
662
667
  return {
663
668
  content: [
664
669
  {
665
670
  type: "text",
666
671
  text: JSON.stringify(
667
672
  {
668
- ok: false,
669
- reason: "not_a_tty",
670
- instruction:
671
- "Run this in your terminal, then call mandu.brain.status:\n\n npx @openai/codex login\n",
673
+ ok: true,
674
+ provider: "openai",
675
+ already_authenticated: true,
676
+ auth_file: existing,
677
+ note:
678
+ "ChatGPT session already present. Call mandu.brain.logout + mandu.brain.login again to re-authenticate.",
672
679
  },
673
680
  null,
674
681
  2,
@@ -677,26 +684,62 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
677
684
  ],
678
685
  };
679
686
  }
680
- const result = spawnSync("npx", ["-y", "@openai/codex", "login"], {
681
- stdio: "inherit",
687
+
688
+ // Spawn `npx @openai/codex login` detached from the MCP process.
689
+ // Codex itself opens the user's default browser (`start` on
690
+ // Windows, `open` on macOS, `xdg-open` on Linux) — no TTY needed
691
+ // on our side. We poll for ~/.codex/auth.json to appear and
692
+ // return once it does.
693
+ const { spawn } = await import("node:child_process");
694
+ const child = spawn("npx", ["-y", "@openai/codex", "login"], {
695
+ cwd: projectRoot,
696
+ detached: false,
697
+ stdio: ["ignore", "pipe", "pipe"],
682
698
  shell: process.platform === "win32",
683
699
  });
684
- const core = await import("@mandujs/core");
685
- const auth = new core.ChatGPTAuth();
686
- const file = auth.locateAuthFile();
700
+
701
+ let stdoutBuffer = "";
702
+ let stderrBuffer = "";
703
+ child.stdout?.on("data", (d) => {
704
+ stdoutBuffer += d.toString();
705
+ });
706
+ child.stderr?.on("data", (d) => {
707
+ stderrBuffer += d.toString();
708
+ });
709
+
710
+ const deadline = Date.now() + Math.max(15_000, Math.min(waitMs, 600_000));
711
+ let file: string | null = null;
712
+ while (Date.now() < deadline) {
713
+ file = auth.locateAuthFile();
714
+ if (file) break;
715
+ await new Promise((r) => setTimeout(r, 1000));
716
+ }
717
+
718
+ // Kill the codex process if it's still running (normally it exits
719
+ // on its own once auth.json is written).
720
+ if (!child.killed) {
721
+ try { child.kill(); } catch { /* ignore */ }
722
+ }
723
+
724
+ const urlMatch = stdoutBuffer.match(
725
+ /https:\/\/auth\.openai\.com\/oauth\/authorize\?[^\s]+/,
726
+ );
727
+
687
728
  return {
688
729
  content: [
689
730
  {
690
731
  type: "text",
691
732
  text: JSON.stringify(
692
733
  {
693
- ok: result.status === 0 && Boolean(file),
694
- exit_code: result.status,
695
- auth_file: file,
734
+ ok: Boolean(file),
696
735
  provider: "openai",
736
+ auth_file: file,
737
+ oauth_url: urlMatch ? urlMatch[0] : undefined,
738
+ stdout_tail: stdoutBuffer.slice(-500),
739
+ stderr_tail: stderrBuffer.slice(-500),
697
740
  note: file
698
- ? "auth.json present; brain will use it automatically."
699
- : "Login flow exited without writing auth.json.",
741
+ ? "auth.json written; Mandu brain will now use the OpenAI tier."
742
+ : "No auth.json detected before waitMs expired. If the OAuth URL is present above, open it in a browser; otherwise rerun with a larger waitMs or run `npx @openai/codex login` in your own terminal.",
700
743
  },
701
744
  null,
702
745
  2,