@mandujs/mcp 0.28.0 → 0.28.2
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/package.json +2 -2
- package/src/tools/brain.ts +104 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/mcp",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.2",
|
|
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.
|
|
37
|
+
"@mandujs/core": "^0.41.2",
|
|
38
38
|
"@mandujs/ate": "^0.24.0",
|
|
39
39
|
"@mandujs/skills": "^0.18.0",
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.25.3"
|
package/src/tools/brain.ts
CHANGED
|
@@ -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,
|
|
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
|
},
|
|
@@ -207,6 +212,40 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
207
212
|
/** Module-level unsubscribe handle for MCP warning notifications */
|
|
208
213
|
let mcpWarningUnsubscribe: (() => void) | null = null;
|
|
209
214
|
|
|
215
|
+
/**
|
|
216
|
+
* #236 — surface a clear error when a stale `@mandujs/core` resolves
|
|
217
|
+
* under `node_modules/@mandujs/mcp/node_modules/` (Bun's installer
|
|
218
|
+
* sometimes lands an older nested copy even with `linker=hoisted`).
|
|
219
|
+
* Without this check the user saw `getCredentialStore is not a
|
|
220
|
+
* function` / `undefined is not a constructor` with no hint.
|
|
221
|
+
*/
|
|
222
|
+
function assertBrainAuthSurface(core: Record<string, unknown>): void {
|
|
223
|
+
const missing: string[] = [];
|
|
224
|
+
if (typeof core.getCredentialStore !== "function")
|
|
225
|
+
missing.push("getCredentialStore");
|
|
226
|
+
if (typeof core.resolveBrainAdapter !== "function")
|
|
227
|
+
missing.push("resolveBrainAdapter");
|
|
228
|
+
if (typeof core.ChatGPTAuth !== "function") missing.push("ChatGPTAuth");
|
|
229
|
+
if (typeof core.AnthropicOAuthAdapter !== "function")
|
|
230
|
+
missing.push("AnthropicOAuthAdapter");
|
|
231
|
+
if (typeof core.revokeConsent !== "function") missing.push("revokeConsent");
|
|
232
|
+
if (missing.length === 0) return;
|
|
233
|
+
|
|
234
|
+
const pkgVersion =
|
|
235
|
+
typeof core.__MANDU_CORE_VERSION__ === "string"
|
|
236
|
+
? core.__MANDU_CORE_VERSION__
|
|
237
|
+
: "unknown";
|
|
238
|
+
throw new Error(
|
|
239
|
+
`[mandu-mcp] The resolved @mandujs/core (v${pkgVersion}) is missing brain-auth exports: ${missing.join(
|
|
240
|
+
", ",
|
|
241
|
+
)}. ` +
|
|
242
|
+
`This usually means Bun's installer placed a stale nested copy at ` +
|
|
243
|
+
`node_modules/@mandujs/mcp/node_modules/@mandujs/core instead of hoisting to the top level. ` +
|
|
244
|
+
`Fix: \`rm -rf node_modules bun.lock && bun install\` (or confirm linker=hoisted in bunfig.toml). ` +
|
|
245
|
+
`See https://github.com/konamgil/mandu/issues/236 for details.`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
210
249
|
export function brainTools(projectRoot: string, server?: Server, monitor?: ActivityMonitor) {
|
|
211
250
|
const paths = getProjectPaths(projectRoot);
|
|
212
251
|
|
|
@@ -595,6 +634,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
595
634
|
// #235 followup — brain auth tools (status / login / logout).
|
|
596
635
|
handlers["mandu.brain.status"] = async () => {
|
|
597
636
|
const core = await import("@mandujs/core");
|
|
637
|
+
assertBrainAuthSurface(core);
|
|
598
638
|
const store = core.getCredentialStore();
|
|
599
639
|
const resolution = await core.resolveBrainAdapter({
|
|
600
640
|
adapter: "auto",
|
|
@@ -649,26 +689,29 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
649
689
|
};
|
|
650
690
|
|
|
651
691
|
handlers["mandu.brain.login"] = async (args) => {
|
|
652
|
-
const { provider = "openai" } = args as {
|
|
653
|
-
|
|
692
|
+
const { provider = "openai", waitMs = 180000 } = args as {
|
|
693
|
+
provider?: "openai" | "anthropic";
|
|
694
|
+
waitMs?: number;
|
|
695
|
+
};
|
|
654
696
|
|
|
655
697
|
if (provider === "openai") {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (!isTty) {
|
|
698
|
+
const core = await import("@mandujs/core");
|
|
699
|
+
assertBrainAuthSurface(core);
|
|
700
|
+
const auth = new core.ChatGPTAuth();
|
|
701
|
+
const existing = auth.locateAuthFile();
|
|
702
|
+
if (existing) {
|
|
662
703
|
return {
|
|
663
704
|
content: [
|
|
664
705
|
{
|
|
665
706
|
type: "text",
|
|
666
707
|
text: JSON.stringify(
|
|
667
708
|
{
|
|
668
|
-
ok:
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
709
|
+
ok: true,
|
|
710
|
+
provider: "openai",
|
|
711
|
+
already_authenticated: true,
|
|
712
|
+
auth_file: existing,
|
|
713
|
+
note:
|
|
714
|
+
"ChatGPT session already present. Call mandu.brain.logout + mandu.brain.login again to re-authenticate.",
|
|
672
715
|
},
|
|
673
716
|
null,
|
|
674
717
|
2,
|
|
@@ -677,26 +720,62 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
677
720
|
],
|
|
678
721
|
};
|
|
679
722
|
}
|
|
680
|
-
|
|
681
|
-
|
|
723
|
+
|
|
724
|
+
// Spawn `npx @openai/codex login` detached from the MCP process.
|
|
725
|
+
// Codex itself opens the user's default browser (`start` on
|
|
726
|
+
// Windows, `open` on macOS, `xdg-open` on Linux) — no TTY needed
|
|
727
|
+
// on our side. We poll for ~/.codex/auth.json to appear and
|
|
728
|
+
// return once it does.
|
|
729
|
+
const { spawn } = await import("node:child_process");
|
|
730
|
+
const child = spawn("npx", ["-y", "@openai/codex", "login"], {
|
|
731
|
+
cwd: projectRoot,
|
|
732
|
+
detached: false,
|
|
733
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
682
734
|
shell: process.platform === "win32",
|
|
683
735
|
});
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
736
|
+
|
|
737
|
+
let stdoutBuffer = "";
|
|
738
|
+
let stderrBuffer = "";
|
|
739
|
+
child.stdout?.on("data", (d) => {
|
|
740
|
+
stdoutBuffer += d.toString();
|
|
741
|
+
});
|
|
742
|
+
child.stderr?.on("data", (d) => {
|
|
743
|
+
stderrBuffer += d.toString();
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const deadline = Date.now() + Math.max(15_000, Math.min(waitMs, 600_000));
|
|
747
|
+
let file: string | null = null;
|
|
748
|
+
while (Date.now() < deadline) {
|
|
749
|
+
file = auth.locateAuthFile();
|
|
750
|
+
if (file) break;
|
|
751
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Kill the codex process if it's still running (normally it exits
|
|
755
|
+
// on its own once auth.json is written).
|
|
756
|
+
if (!child.killed) {
|
|
757
|
+
try { child.kill(); } catch { /* ignore */ }
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const urlMatch = stdoutBuffer.match(
|
|
761
|
+
/https:\/\/auth\.openai\.com\/oauth\/authorize\?[^\s]+/,
|
|
762
|
+
);
|
|
763
|
+
|
|
687
764
|
return {
|
|
688
765
|
content: [
|
|
689
766
|
{
|
|
690
767
|
type: "text",
|
|
691
768
|
text: JSON.stringify(
|
|
692
769
|
{
|
|
693
|
-
ok:
|
|
694
|
-
exit_code: result.status,
|
|
695
|
-
auth_file: file,
|
|
770
|
+
ok: Boolean(file),
|
|
696
771
|
provider: "openai",
|
|
772
|
+
auth_file: file,
|
|
773
|
+
oauth_url: urlMatch ? urlMatch[0] : undefined,
|
|
774
|
+
stdout_tail: stdoutBuffer.slice(-500),
|
|
775
|
+
stderr_tail: stderrBuffer.slice(-500),
|
|
697
776
|
note: file
|
|
698
|
-
? "auth.json
|
|
699
|
-
: "
|
|
777
|
+
? "auth.json written; Mandu brain will now use the OpenAI tier."
|
|
778
|
+
: "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
779
|
},
|
|
701
780
|
null,
|
|
702
781
|
2,
|
|
@@ -708,6 +787,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
708
787
|
|
|
709
788
|
// Anthropic — Mandu-managed OAuth loopback flow.
|
|
710
789
|
const core = await import("@mandujs/core");
|
|
790
|
+
assertBrainAuthSurface(core);
|
|
711
791
|
try {
|
|
712
792
|
const adapter = new core.AnthropicOAuthAdapter({
|
|
713
793
|
credentialStore: core.getCredentialStore(),
|
|
@@ -760,6 +840,7 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
760
840
|
provider?: "openai" | "anthropic" | "all";
|
|
761
841
|
};
|
|
762
842
|
const core = await import("@mandujs/core");
|
|
843
|
+
assertBrainAuthSurface(core);
|
|
763
844
|
const store = core.getCredentialStore();
|
|
764
845
|
const targets =
|
|
765
846
|
provider === "all"
|