@mandujs/mcp 0.27.1 → 0.28.0
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 +242 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
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.
|
|
37
|
+
"@mandujs/core": "^0.41.0",
|
|
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
|
@@ -157,6 +157,51 @@ export const brainToolDefinitions: Tool[] = [
|
|
|
157
157
|
required: [],
|
|
158
158
|
},
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
name: "mandu.brain.status",
|
|
162
|
+
description:
|
|
163
|
+
"Check which LLM adapter is active for brain (openai / anthropic / ollama / template) and whether auth tokens are present. Read-only — does not call an LLM or spawn subprocesses.",
|
|
164
|
+
annotations: { readOnlyHint: true },
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: {},
|
|
168
|
+
required: [],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "mandu.brain.login",
|
|
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.",
|
|
175
|
+
annotations: { readOnlyHint: false },
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
provider: {
|
|
180
|
+
type: "string",
|
|
181
|
+
enum: ["openai", "anthropic"],
|
|
182
|
+
description: "Which provider to sign into. Default: openai.",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
required: [],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "mandu.brain.logout",
|
|
190
|
+
description:
|
|
191
|
+
"Delete stored brain credentials for a provider. For openai, deletes the keychain-stored enterprise token only — the ~/.codex/auth.json owned by the Codex CLI is intentionally left in place (run `npx @openai/codex logout` to revoke that).",
|
|
192
|
+
annotations: { readOnlyHint: false },
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
provider: {
|
|
197
|
+
type: "string",
|
|
198
|
+
enum: ["openai", "anthropic", "all"],
|
|
199
|
+
description: "Which provider to log out of. Default: all.",
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
required: [],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
160
205
|
];
|
|
161
206
|
|
|
162
207
|
/** Module-level unsubscribe handle for MCP warning notifications */
|
|
@@ -547,6 +592,203 @@ export function brainTools(projectRoot: string, server?: Server, monitor?: Activ
|
|
|
547
592
|
},
|
|
548
593
|
};
|
|
549
594
|
|
|
595
|
+
// #235 followup — brain auth tools (status / login / logout).
|
|
596
|
+
handlers["mandu.brain.status"] = async () => {
|
|
597
|
+
const core = await import("@mandujs/core");
|
|
598
|
+
const store = core.getCredentialStore();
|
|
599
|
+
const resolution = await core.resolveBrainAdapter({
|
|
600
|
+
adapter: "auto",
|
|
601
|
+
credentialStore: store,
|
|
602
|
+
projectRoot,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// Check ChatGPT session token (managed by @openai/codex, not the keychain).
|
|
606
|
+
const chatgpt = new core.ChatGPTAuth();
|
|
607
|
+
const chatgptFile = chatgpt.locateAuthFile();
|
|
608
|
+
|
|
609
|
+
const providers: Record<string, unknown> = {};
|
|
610
|
+
for (const provider of ["openai", "anthropic"] as const) {
|
|
611
|
+
const token = await store.load(provider);
|
|
612
|
+
providers[provider] = token
|
|
613
|
+
? {
|
|
614
|
+
logged_in: true,
|
|
615
|
+
source: "keychain",
|
|
616
|
+
model: token.default_model ?? null,
|
|
617
|
+
expires_at: token.expires_at
|
|
618
|
+
? new Date(token.expires_at * 1000).toISOString()
|
|
619
|
+
: null,
|
|
620
|
+
last_used_at: token.last_used_at ?? null,
|
|
621
|
+
}
|
|
622
|
+
: provider === "openai" && chatgptFile
|
|
623
|
+
? {
|
|
624
|
+
logged_in: true,
|
|
625
|
+
source: "chatgpt_session",
|
|
626
|
+
auth_file: chatgptFile,
|
|
627
|
+
note: "Managed by `@openai/codex` CLI. Mandu reads + auto-refreshes.",
|
|
628
|
+
}
|
|
629
|
+
: { logged_in: false };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return {
|
|
633
|
+
content: [
|
|
634
|
+
{
|
|
635
|
+
type: "text",
|
|
636
|
+
text: JSON.stringify(
|
|
637
|
+
{
|
|
638
|
+
active_tier: resolution.resolved,
|
|
639
|
+
reason: resolution.reason,
|
|
640
|
+
backend: store.backendName,
|
|
641
|
+
providers,
|
|
642
|
+
},
|
|
643
|
+
null,
|
|
644
|
+
2,
|
|
645
|
+
),
|
|
646
|
+
},
|
|
647
|
+
],
|
|
648
|
+
};
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
handlers["mandu.brain.login"] = async (args) => {
|
|
652
|
+
const { provider = "openai" } = args as { provider?: "openai" | "anthropic" };
|
|
653
|
+
const { spawnSync } = await import("node:child_process");
|
|
654
|
+
|
|
655
|
+
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) {
|
|
662
|
+
return {
|
|
663
|
+
content: [
|
|
664
|
+
{
|
|
665
|
+
type: "text",
|
|
666
|
+
text: JSON.stringify(
|
|
667
|
+
{
|
|
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",
|
|
672
|
+
},
|
|
673
|
+
null,
|
|
674
|
+
2,
|
|
675
|
+
),
|
|
676
|
+
},
|
|
677
|
+
],
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
const result = spawnSync("npx", ["-y", "@openai/codex", "login"], {
|
|
681
|
+
stdio: "inherit",
|
|
682
|
+
shell: process.platform === "win32",
|
|
683
|
+
});
|
|
684
|
+
const core = await import("@mandujs/core");
|
|
685
|
+
const auth = new core.ChatGPTAuth();
|
|
686
|
+
const file = auth.locateAuthFile();
|
|
687
|
+
return {
|
|
688
|
+
content: [
|
|
689
|
+
{
|
|
690
|
+
type: "text",
|
|
691
|
+
text: JSON.stringify(
|
|
692
|
+
{
|
|
693
|
+
ok: result.status === 0 && Boolean(file),
|
|
694
|
+
exit_code: result.status,
|
|
695
|
+
auth_file: file,
|
|
696
|
+
provider: "openai",
|
|
697
|
+
note: file
|
|
698
|
+
? "auth.json present; brain will use it automatically."
|
|
699
|
+
: "Login flow exited without writing auth.json.",
|
|
700
|
+
},
|
|
701
|
+
null,
|
|
702
|
+
2,
|
|
703
|
+
),
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Anthropic — Mandu-managed OAuth loopback flow.
|
|
710
|
+
const core = await import("@mandujs/core");
|
|
711
|
+
try {
|
|
712
|
+
const adapter = new core.AnthropicOAuthAdapter({
|
|
713
|
+
credentialStore: core.getCredentialStore(),
|
|
714
|
+
projectRoot,
|
|
715
|
+
strict: true,
|
|
716
|
+
skipConsent: true,
|
|
717
|
+
});
|
|
718
|
+
const token = await adapter.login({});
|
|
719
|
+
return {
|
|
720
|
+
content: [
|
|
721
|
+
{
|
|
722
|
+
type: "text",
|
|
723
|
+
text: JSON.stringify(
|
|
724
|
+
{
|
|
725
|
+
ok: true,
|
|
726
|
+
provider: "anthropic",
|
|
727
|
+
model: token.default_model ?? null,
|
|
728
|
+
expires_at: token.expires_at
|
|
729
|
+
? new Date(token.expires_at * 1000).toISOString()
|
|
730
|
+
: null,
|
|
731
|
+
},
|
|
732
|
+
null,
|
|
733
|
+
2,
|
|
734
|
+
),
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
};
|
|
738
|
+
} catch (err) {
|
|
739
|
+
return {
|
|
740
|
+
content: [
|
|
741
|
+
{
|
|
742
|
+
type: "text",
|
|
743
|
+
text: JSON.stringify(
|
|
744
|
+
{
|
|
745
|
+
ok: false,
|
|
746
|
+
provider: "anthropic",
|
|
747
|
+
error: err instanceof Error ? err.message : String(err),
|
|
748
|
+
},
|
|
749
|
+
null,
|
|
750
|
+
2,
|
|
751
|
+
),
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
handlers["mandu.brain.logout"] = async (args) => {
|
|
759
|
+
const { provider = "all" } = args as {
|
|
760
|
+
provider?: "openai" | "anthropic" | "all";
|
|
761
|
+
};
|
|
762
|
+
const core = await import("@mandujs/core");
|
|
763
|
+
const store = core.getCredentialStore();
|
|
764
|
+
const targets =
|
|
765
|
+
provider === "all"
|
|
766
|
+
? (["openai", "anthropic"] as const)
|
|
767
|
+
: ([provider] as const);
|
|
768
|
+
for (const p of targets) {
|
|
769
|
+
await store.delete(p);
|
|
770
|
+
await core.revokeConsent(p, projectRoot);
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
content: [
|
|
774
|
+
{
|
|
775
|
+
type: "text",
|
|
776
|
+
text: JSON.stringify(
|
|
777
|
+
{
|
|
778
|
+
ok: true,
|
|
779
|
+
logged_out: targets,
|
|
780
|
+
note: targets.includes("openai")
|
|
781
|
+
? "Keychain-stored openai token cleared. To revoke the Codex CLI session (~/.codex/auth.json), run `npx @openai/codex logout`."
|
|
782
|
+
: undefined,
|
|
783
|
+
},
|
|
784
|
+
null,
|
|
785
|
+
2,
|
|
786
|
+
),
|
|
787
|
+
},
|
|
788
|
+
],
|
|
789
|
+
};
|
|
790
|
+
};
|
|
791
|
+
|
|
550
792
|
// Backward-compatible aliases (deprecated)
|
|
551
793
|
handlers["mandu_doctor"] = handlers["mandu.brain.doctor"];
|
|
552
794
|
handlers["mandu_watch_start"] = handlers["mandu.watch.start"];
|