@agentwonderland/mcp 0.1.39 → 0.1.41
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/dist/core/__tests__/api-client.test.js +1 -1
- package/dist/core/__tests__/setup.test.d.ts +1 -0
- package/dist/core/__tests__/setup.test.js +72 -0
- package/dist/core/api-client.js +1 -1
- package/dist/index.js +48 -4
- package/dist/setup.d.ts +36 -0
- package/dist/setup.js +346 -0
- package/package.json +2 -2
- package/src/core/__tests__/api-client.test.ts +1 -1
- package/src/core/__tests__/setup.test.ts +88 -0
- package/src/core/api-client.ts +1 -1
- package/src/index.ts +48 -4
- package/src/setup.ts +411 -0
|
@@ -46,7 +46,7 @@ describe("api-client headers", () => {
|
|
|
46
46
|
"X-AW-Consumer-Principal": "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:42W2HfLfveSm1T5et9WTLp2CZ2QXdF2EYCUvyJ2gPpxF",
|
|
47
47
|
"X-AW-Rebate-Principal": "did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
48
48
|
"X-AW-Surface": "mcp",
|
|
49
|
-
"X-AW-MCP-Version": "0.1.
|
|
49
|
+
"X-AW-MCP-Version": "0.1.41",
|
|
50
50
|
"X-AW-MCP-Tool": "run_agent",
|
|
51
51
|
"X-AW-MCP-Action": "execute",
|
|
52
52
|
}),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { installClient, mergeCodexTomlConfig, mergeJsonMcpConfig, parseSetupArgs, } from "../../setup.js";
|
|
6
|
+
let tempDirs = [];
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await Promise.all(tempDirs.map((dir) => rm(dir, { recursive: true, force: true })));
|
|
9
|
+
tempDirs = [];
|
|
10
|
+
});
|
|
11
|
+
describe("MCP setup", () => {
|
|
12
|
+
it("merges the server into existing JSON MCP config", () => {
|
|
13
|
+
const next = JSON.parse(mergeJsonMcpConfig(JSON.stringify({
|
|
14
|
+
mcpServers: {
|
|
15
|
+
existing: { command: "node", args: ["server.js"] },
|
|
16
|
+
},
|
|
17
|
+
otherSetting: true,
|
|
18
|
+
})));
|
|
19
|
+
expect(next.otherSetting).toBe(true);
|
|
20
|
+
expect(next.mcpServers.existing.command).toBe("node");
|
|
21
|
+
expect(next.mcpServers.agentwonderland).toEqual({
|
|
22
|
+
command: "npx",
|
|
23
|
+
args: ["-y", "@agentwonderland/mcp"],
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it("replaces an existing Codex TOML block without disturbing other servers", () => {
|
|
27
|
+
const next = mergeCodexTomlConfig([
|
|
28
|
+
"[mcp_servers.other]",
|
|
29
|
+
'command = "node"',
|
|
30
|
+
'args = ["other.js"]',
|
|
31
|
+
"",
|
|
32
|
+
"[mcp_servers.agentwonderland]",
|
|
33
|
+
'command = "old"',
|
|
34
|
+
'args = ["old"]',
|
|
35
|
+
"",
|
|
36
|
+
"[model_provider.openai]",
|
|
37
|
+
'name = "OpenAI"',
|
|
38
|
+
"",
|
|
39
|
+
].join("\n"));
|
|
40
|
+
expect(next).toContain("[mcp_servers.other]");
|
|
41
|
+
expect(next).toContain("[mcp_servers.agentwonderland]");
|
|
42
|
+
expect(next).toContain('command = "npx"');
|
|
43
|
+
expect(next).toContain('args = ["-y", "@agentwonderland/mcp"]');
|
|
44
|
+
expect(next).not.toContain('command = "old"');
|
|
45
|
+
expect(next).toContain("[model_provider.openai]");
|
|
46
|
+
});
|
|
47
|
+
it("writes config and creates a backup when updating", async () => {
|
|
48
|
+
const dir = await mkdtemp(path.join(tmpdir(), "aw-mcp-setup-"));
|
|
49
|
+
tempDirs.push(dir);
|
|
50
|
+
const configPath = path.join(dir, "mcp.json");
|
|
51
|
+
await writeFile(configPath, JSON.stringify({ mcpServers: { old: { command: "node" } } }), "utf8");
|
|
52
|
+
const client = {
|
|
53
|
+
id: "cursor",
|
|
54
|
+
name: "Cursor",
|
|
55
|
+
path: configPath,
|
|
56
|
+
format: "json",
|
|
57
|
+
detected: true,
|
|
58
|
+
note: "test",
|
|
59
|
+
};
|
|
60
|
+
const result = await installClient(client);
|
|
61
|
+
const written = JSON.parse(await readFile(configPath, "utf8"));
|
|
62
|
+
expect(result.status).toBe("updated");
|
|
63
|
+
expect(result.backupPath).toBeTruthy();
|
|
64
|
+
expect(written.mcpServers.agentwonderland.args).toEqual(["-y", "@agentwonderland/mcp"]);
|
|
65
|
+
});
|
|
66
|
+
it("parses client selection flags", () => {
|
|
67
|
+
expect(parseSetupArgs(["--clients", "codex,cursor", "--yes"])).toEqual({
|
|
68
|
+
clients: ["codex", "cursor"],
|
|
69
|
+
yes: true,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
package/dist/core/api-client.js
CHANGED
|
@@ -12,7 +12,7 @@ export class ApiError extends Error {
|
|
|
12
12
|
this.name = "ApiError";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
const MCP_VERSION = "0.1.
|
|
15
|
+
const MCP_VERSION = "0.1.41";
|
|
16
16
|
function inferToolHeaders(path, method) {
|
|
17
17
|
if (path === "/solve") {
|
|
18
18
|
return { "X-AW-MCP-Tool": "solve", "X-AW-MCP-Action": method === "POST" ? "execute" : "view" };
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@ import { registerWalletResources } from "./resources/wallet.js";
|
|
|
21
21
|
import { registerJobResources } from "./resources/jobs.js";
|
|
22
22
|
// ── Prompts ──────────────────────────────────────────────────────
|
|
23
23
|
import { registerPrompts } from "./prompts/index.js";
|
|
24
|
+
import { runSetupCli } from "./setup.js";
|
|
25
|
+
const PACKAGE_VERSION = "0.1.41";
|
|
24
26
|
export async function startMcpServer() {
|
|
25
27
|
const server = new McpServer({
|
|
26
28
|
name: "agentwonderland",
|
|
@@ -93,8 +95,50 @@ export async function startMcpServer() {
|
|
|
93
95
|
const isCli = process.argv[1]?.endsWith("agentwonderland-mcp") ||
|
|
94
96
|
process.argv[1]?.includes("packages/mcp");
|
|
95
97
|
if (isCli) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const args = process.argv.slice(2);
|
|
99
|
+
if (args[0] === "setup" || args[0] === "install" || args[0] === "configure") {
|
|
100
|
+
runSetupCli(args.slice(1)).catch((err) => {
|
|
101
|
+
console.error("Setup failed:", err instanceof Error ? err.message : err);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else if (args.includes("--help") || args.includes("-h")) {
|
|
106
|
+
console.log([
|
|
107
|
+
"Agent Wonderland MCP server",
|
|
108
|
+
"",
|
|
109
|
+
"Usage:",
|
|
110
|
+
" npx @agentwonderland/mcp setup Configure MCP clients",
|
|
111
|
+
" npx @agentwonderland/mcp Start the stdio MCP server",
|
|
112
|
+
"",
|
|
113
|
+
"The bare command starts a stdio MCP server. It is meant to be launched by",
|
|
114
|
+
"Codex, Cursor, Claude Desktop, or another MCP client. When run directly",
|
|
115
|
+
"in a terminal it waits silently for MCP JSON-RPC messages on stdin.",
|
|
116
|
+
"",
|
|
117
|
+
"MCP client config:",
|
|
118
|
+
' { "mcpServers": { "agentwonderland": { "command": "npx", "args": ["-y", "@agentwonderland/mcp"] } } }',
|
|
119
|
+
"",
|
|
120
|
+
"Options:",
|
|
121
|
+
" -h, --help Show this help",
|
|
122
|
+
" -v, --version Show package version",
|
|
123
|
+
].join("\n"));
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
else if (args.includes("--version") || args.includes("-v")) {
|
|
127
|
+
console.log(PACKAGE_VERSION);
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
132
|
+
console.error([
|
|
133
|
+
"Agent Wonderland MCP server is running.",
|
|
134
|
+
"This process speaks MCP over stdio, so it waits for a client instead of printing a prompt.",
|
|
135
|
+
"Add it to an MCP client config, or press Ctrl+C to stop.",
|
|
136
|
+
"Run `npx @agentwonderland/mcp setup` to configure your MCP clients.",
|
|
137
|
+
].join("\n"));
|
|
138
|
+
}
|
|
139
|
+
startMcpServer().catch((err) => {
|
|
140
|
+
console.error("MCP server error:", err);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
100
144
|
}
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare const SERVER_NAME = "agentwonderland";
|
|
2
|
+
export declare const SERVER_CONFIG: {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
};
|
|
6
|
+
export type SetupClientId = "codex" | "claude-code" | "claude-desktop" | "cursor" | "antigravity";
|
|
7
|
+
type ConfigFormat = "json" | "toml" | "claude-code-cli";
|
|
8
|
+
export type SetupClient = {
|
|
9
|
+
id: SetupClientId;
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
format: ConfigFormat;
|
|
13
|
+
detected: boolean;
|
|
14
|
+
note: string;
|
|
15
|
+
};
|
|
16
|
+
export type SetupOptions = {
|
|
17
|
+
all?: boolean;
|
|
18
|
+
yes?: boolean;
|
|
19
|
+
clients?: SetupClientId[];
|
|
20
|
+
dryRun?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type InstallResult = {
|
|
23
|
+
client: SetupClient;
|
|
24
|
+
status: "created" | "updated" | "unchanged" | "skipped" | "failed";
|
|
25
|
+
backupPath?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function runSetupCli(args?: string[]): Promise<void>;
|
|
29
|
+
export declare function parseSetupArgs(args: string[]): SetupOptions & {
|
|
30
|
+
help?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function getSetupClients(home?: string, env?: NodeJS.ProcessEnv): SetupClient[];
|
|
33
|
+
export declare function installClient(client: SetupClient, options?: SetupOptions): Promise<InstallResult>;
|
|
34
|
+
export declare function mergeJsonMcpConfig(existing: string): string;
|
|
35
|
+
export declare function mergeCodexTomlConfig(existing: string): string;
|
|
36
|
+
export {};
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
export const SERVER_NAME = "agentwonderland";
|
|
9
|
+
export const SERVER_CONFIG = {
|
|
10
|
+
command: "npx",
|
|
11
|
+
args: ["-y", "@agentwonderland/mcp"],
|
|
12
|
+
};
|
|
13
|
+
const CLIENT_ORDER = [
|
|
14
|
+
"codex",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"claude-desktop",
|
|
17
|
+
"cursor",
|
|
18
|
+
"antigravity",
|
|
19
|
+
];
|
|
20
|
+
const CLIENT_LABELS = {
|
|
21
|
+
codex: "Codex",
|
|
22
|
+
"claude-code": "Claude Code",
|
|
23
|
+
"claude-desktop": "Claude Desktop",
|
|
24
|
+
cursor: "Cursor",
|
|
25
|
+
antigravity: "Google Antigravity",
|
|
26
|
+
};
|
|
27
|
+
export async function runSetupCli(args = process.argv.slice(2)) {
|
|
28
|
+
const options = parseSetupArgs(args);
|
|
29
|
+
if (options.help) {
|
|
30
|
+
printSetupHelp();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const clients = getSetupClients();
|
|
34
|
+
const selected = await chooseClients(clients, options);
|
|
35
|
+
if (selected.length === 0) {
|
|
36
|
+
console.log("No MCP clients selected. Nothing changed.");
|
|
37
|
+
printManualConfig();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log("Agent Wonderland will add this MCP server entry:");
|
|
42
|
+
console.log(JSON.stringify({ mcpServers: { [SERVER_NAME]: SERVER_CONFIG } }, null, 2));
|
|
43
|
+
console.log("");
|
|
44
|
+
const results = [];
|
|
45
|
+
for (const client of selected) {
|
|
46
|
+
results.push(await installClient(client, options));
|
|
47
|
+
}
|
|
48
|
+
printResults(results, options);
|
|
49
|
+
}
|
|
50
|
+
export function parseSetupArgs(args) {
|
|
51
|
+
const options = {};
|
|
52
|
+
for (let index = 0; index < args.length; index++) {
|
|
53
|
+
const arg = args[index];
|
|
54
|
+
if (arg === "--help" || arg === "-h") {
|
|
55
|
+
options.help = true;
|
|
56
|
+
}
|
|
57
|
+
else if (arg === "--all") {
|
|
58
|
+
options.all = true;
|
|
59
|
+
}
|
|
60
|
+
else if (arg === "--yes" || arg === "-y") {
|
|
61
|
+
options.yes = true;
|
|
62
|
+
}
|
|
63
|
+
else if (arg === "--dry-run") {
|
|
64
|
+
options.dryRun = true;
|
|
65
|
+
}
|
|
66
|
+
else if (arg === "--clients") {
|
|
67
|
+
const value = args[index + 1];
|
|
68
|
+
if (!value)
|
|
69
|
+
throw new Error("--clients requires a comma-separated value");
|
|
70
|
+
options.clients = parseClientList(value);
|
|
71
|
+
index++;
|
|
72
|
+
}
|
|
73
|
+
else if (arg.startsWith("--clients=")) {
|
|
74
|
+
options.clients = parseClientList(arg.slice("--clients=".length));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
throw new Error(`Unknown setup option: ${arg}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return options;
|
|
81
|
+
}
|
|
82
|
+
function parseClientList(value) {
|
|
83
|
+
const selected = value
|
|
84
|
+
.split(",")
|
|
85
|
+
.map((item) => item.trim().toLowerCase())
|
|
86
|
+
.filter(Boolean);
|
|
87
|
+
const invalid = selected.filter((item) => !CLIENT_ORDER.includes(item));
|
|
88
|
+
if (invalid.length > 0) {
|
|
89
|
+
throw new Error(`Unknown client(s): ${invalid.join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
return selected;
|
|
92
|
+
}
|
|
93
|
+
export function getSetupClients(home = homedir(), env = process.env) {
|
|
94
|
+
const appData = env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
95
|
+
const isWindows = platform() === "win32";
|
|
96
|
+
const isMac = platform() === "darwin";
|
|
97
|
+
const claudeDesktopPath = isMac
|
|
98
|
+
? path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
99
|
+
: isWindows
|
|
100
|
+
? path.join(appData, "Claude", "claude_desktop_config.json")
|
|
101
|
+
: path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
102
|
+
const clients = [
|
|
103
|
+
{
|
|
104
|
+
id: "codex",
|
|
105
|
+
name: CLIENT_LABELS.codex,
|
|
106
|
+
path: path.join(home, ".codex", "config.toml"),
|
|
107
|
+
format: "toml",
|
|
108
|
+
detected: existsSync(path.join(home, ".codex")) || commandExists("codex"),
|
|
109
|
+
note: "Global Codex CLI/Desktop config",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "claude-code",
|
|
113
|
+
name: CLIENT_LABELS["claude-code"],
|
|
114
|
+
path: "claude mcp add-json --scope user",
|
|
115
|
+
format: "claude-code-cli",
|
|
116
|
+
detected: commandExists("claude"),
|
|
117
|
+
note: "Claude Code user-scope MCP config via the Claude CLI",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "claude-desktop",
|
|
121
|
+
name: CLIENT_LABELS["claude-desktop"],
|
|
122
|
+
path: claudeDesktopPath,
|
|
123
|
+
format: "json",
|
|
124
|
+
detected: existsSync(claudeDesktopPath) || existsSync(path.dirname(claudeDesktopPath)),
|
|
125
|
+
note: "Claude Desktop developer config",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "cursor",
|
|
129
|
+
name: CLIENT_LABELS.cursor,
|
|
130
|
+
path: path.join(home, ".cursor", "mcp.json"),
|
|
131
|
+
format: "json",
|
|
132
|
+
detected: existsSync(path.join(home, ".cursor")),
|
|
133
|
+
note: "Global Cursor MCP config",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "antigravity",
|
|
137
|
+
name: CLIENT_LABELS.antigravity,
|
|
138
|
+
path: path.join(home, ".gemini", "antigravity", "mcp_config.json"),
|
|
139
|
+
format: "json",
|
|
140
|
+
detected: existsSync(path.join(home, ".gemini", "antigravity")),
|
|
141
|
+
note: "Antigravity raw MCP config",
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
return clients;
|
|
145
|
+
}
|
|
146
|
+
async function chooseClients(clients, options) {
|
|
147
|
+
if (options.clients?.length) {
|
|
148
|
+
return clients.filter((client) => options.clients?.includes(client.id));
|
|
149
|
+
}
|
|
150
|
+
if (options.all) {
|
|
151
|
+
return clients;
|
|
152
|
+
}
|
|
153
|
+
const detected = clients.filter((client) => client.detected);
|
|
154
|
+
if (options.yes) {
|
|
155
|
+
return detected.length > 0 ? detected : clients.filter((client) => client.id === "codex");
|
|
156
|
+
}
|
|
157
|
+
console.log("Agent Wonderland MCP setup");
|
|
158
|
+
console.log("");
|
|
159
|
+
console.log("Detected clients:");
|
|
160
|
+
for (const client of clients) {
|
|
161
|
+
const marker = client.detected ? "found" : "not found";
|
|
162
|
+
console.log(` ${client.id.padEnd(15)} ${marker.padEnd(9)} ${client.path}`);
|
|
163
|
+
}
|
|
164
|
+
console.log("");
|
|
165
|
+
const rl = createInterface({ input, output });
|
|
166
|
+
try {
|
|
167
|
+
const selected = [];
|
|
168
|
+
for (const client of clients) {
|
|
169
|
+
const defaultAnswer = client.detected ? "Y/n" : "y/N";
|
|
170
|
+
const answer = (await rl.question(`Install for ${client.name}? (${defaultAnswer}) `)).trim().toLowerCase();
|
|
171
|
+
const yes = answer === ""
|
|
172
|
+
? client.detected
|
|
173
|
+
: answer === "y" || answer === "yes";
|
|
174
|
+
if (yes)
|
|
175
|
+
selected.push(client);
|
|
176
|
+
}
|
|
177
|
+
return selected;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
rl.close();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export async function installClient(client, options = {}) {
|
|
184
|
+
try {
|
|
185
|
+
if (client.format === "claude-code-cli") {
|
|
186
|
+
return installClaudeCode(client, options);
|
|
187
|
+
}
|
|
188
|
+
const existing = await readExisting(client.path);
|
|
189
|
+
const next = client.format === "json"
|
|
190
|
+
? mergeJsonMcpConfig(existing)
|
|
191
|
+
: mergeCodexTomlConfig(existing);
|
|
192
|
+
if (next === existing) {
|
|
193
|
+
return { client, status: "unchanged", message: "already configured" };
|
|
194
|
+
}
|
|
195
|
+
const backupPath = existing ? `${client.path}.bak-${timestamp()}` : undefined;
|
|
196
|
+
if (!options.dryRun) {
|
|
197
|
+
await mkdir(path.dirname(client.path), { recursive: true });
|
|
198
|
+
if (backupPath) {
|
|
199
|
+
await writeFile(backupPath, existing, "utf8");
|
|
200
|
+
}
|
|
201
|
+
await writeFile(client.path, next, "utf8");
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
client,
|
|
205
|
+
status: existing ? "updated" : "created",
|
|
206
|
+
backupPath,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
return {
|
|
211
|
+
client,
|
|
212
|
+
status: "failed",
|
|
213
|
+
message: error instanceof Error ? error.message : String(error),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function installClaudeCode(client, options) {
|
|
218
|
+
if (!commandExists("claude")) {
|
|
219
|
+
return {
|
|
220
|
+
client,
|
|
221
|
+
status: "failed",
|
|
222
|
+
message: [
|
|
223
|
+
"Claude Code CLI was not found on PATH.",
|
|
224
|
+
"Run manually after installing Claude Code:",
|
|
225
|
+
`claude mcp add-json ${SERVER_NAME} '${JSON.stringify({ type: "stdio", ...SERVER_CONFIG })}' --scope user`,
|
|
226
|
+
].join(" "),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (!options.dryRun) {
|
|
230
|
+
execFileSync("claude", [
|
|
231
|
+
"mcp",
|
|
232
|
+
"add-json",
|
|
233
|
+
SERVER_NAME,
|
|
234
|
+
JSON.stringify({ type: "stdio", ...SERVER_CONFIG }),
|
|
235
|
+
"--scope",
|
|
236
|
+
"user",
|
|
237
|
+
], { stdio: "pipe" });
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
client,
|
|
241
|
+
status: options.dryRun ? "skipped" : "updated",
|
|
242
|
+
message: options.dryRun ? "would run Claude Code CLI installer" : "installed with Claude Code CLI",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function readExisting(filePath) {
|
|
246
|
+
try {
|
|
247
|
+
return await readFile(filePath, "utf8");
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (error.code === "ENOENT")
|
|
251
|
+
return "";
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export function mergeJsonMcpConfig(existing) {
|
|
256
|
+
const parsed = existing.trim()
|
|
257
|
+
? JSON.parse(existing)
|
|
258
|
+
: {};
|
|
259
|
+
const mcpServers = typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers)
|
|
260
|
+
? parsed.mcpServers
|
|
261
|
+
: {};
|
|
262
|
+
const next = {
|
|
263
|
+
...parsed,
|
|
264
|
+
mcpServers: {
|
|
265
|
+
...mcpServers,
|
|
266
|
+
[SERVER_NAME]: SERVER_CONFIG,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
return `${JSON.stringify(next, null, 2)}\n`;
|
|
270
|
+
}
|
|
271
|
+
export function mergeCodexTomlConfig(existing) {
|
|
272
|
+
const block = [
|
|
273
|
+
`[mcp_servers.${SERVER_NAME}]`,
|
|
274
|
+
`command = "npx"`,
|
|
275
|
+
`args = ["-y", "@agentwonderland/mcp"]`,
|
|
276
|
+
"",
|
|
277
|
+
].join("\n");
|
|
278
|
+
const pattern = new RegExp(`\\n?\\[mcp_servers\\.${SERVER_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\n\\[[^\\]]+\\]|$)`);
|
|
279
|
+
const trimmed = existing.trimEnd();
|
|
280
|
+
if (!trimmed)
|
|
281
|
+
return block;
|
|
282
|
+
if (pattern.test(trimmed)) {
|
|
283
|
+
const next = trimmed.replace(pattern, `\n${block.trimEnd()}`);
|
|
284
|
+
return `${next.trimEnd()}\n`;
|
|
285
|
+
}
|
|
286
|
+
return `${trimmed}\n\n${block}`;
|
|
287
|
+
}
|
|
288
|
+
function printResults(results, options) {
|
|
289
|
+
console.log(options.dryRun ? "Dry run complete:" : "Setup complete:");
|
|
290
|
+
for (const result of results) {
|
|
291
|
+
const suffix = result.backupPath ? ` (backup: ${result.backupPath})` : "";
|
|
292
|
+
const detail = result.message ? ` - ${result.message}` : "";
|
|
293
|
+
console.log(` ${result.status.padEnd(9)} ${result.client.name}: ${result.client.path}${suffix}${detail}`);
|
|
294
|
+
}
|
|
295
|
+
console.log("");
|
|
296
|
+
console.log("Restart any clients you updated, then ask your coding agent:");
|
|
297
|
+
console.log(' "Check my Agent Wonderland wallet status."');
|
|
298
|
+
console.log("");
|
|
299
|
+
console.log("If no wallet is configured yet, ask:");
|
|
300
|
+
console.log(' "Set up payment for Agent Wonderland."');
|
|
301
|
+
}
|
|
302
|
+
function printSetupHelp() {
|
|
303
|
+
console.log([
|
|
304
|
+
"Agent Wonderland MCP setup",
|
|
305
|
+
"",
|
|
306
|
+
"Usage:",
|
|
307
|
+
" npx @agentwonderland/mcp setup",
|
|
308
|
+
" npx @agentwonderland/mcp setup --clients codex,claude-code",
|
|
309
|
+
" npx @agentwonderland/mcp setup --all --yes",
|
|
310
|
+
"",
|
|
311
|
+
"Supported clients:",
|
|
312
|
+
` ${CLIENT_ORDER.join(", ")}`,
|
|
313
|
+
"",
|
|
314
|
+
"Options:",
|
|
315
|
+
" --clients <list> Comma-separated client ids",
|
|
316
|
+
" --all Offer/install every supported config target",
|
|
317
|
+
" -y, --yes Accept defaults without prompts",
|
|
318
|
+
" --dry-run Show what would be changed without writing files",
|
|
319
|
+
" -h, --help Show this help",
|
|
320
|
+
].join("\n"));
|
|
321
|
+
}
|
|
322
|
+
function printManualConfig() {
|
|
323
|
+
console.log("");
|
|
324
|
+
console.log("Manual MCP config:");
|
|
325
|
+
console.log(JSON.stringify({ mcpServers: { [SERVER_NAME]: SERVER_CONFIG } }, null, 2));
|
|
326
|
+
}
|
|
327
|
+
function timestamp() {
|
|
328
|
+
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
329
|
+
}
|
|
330
|
+
function commandExists(command) {
|
|
331
|
+
try {
|
|
332
|
+
if (platform() === "win32") {
|
|
333
|
+
execFileSync("where", [command], { stdio: "ignore" });
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
execFileSync("sh", ["-lc", `command -v ${shellQuote(command)}`], { stdio: "ignore" });
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function shellQuote(value) {
|
|
345
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
346
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentwonderland/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for the Agent Wonderland AI agent marketplace",
|
|
6
6
|
"bin": {
|
|
7
|
-
"agentwonderland-mcp": "
|
|
7
|
+
"agentwonderland-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
@@ -72,7 +72,7 @@ describe("api-client headers", () => {
|
|
|
72
72
|
"X-AW-Rebate-Principal":
|
|
73
73
|
"did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
74
74
|
"X-AW-Surface": "mcp",
|
|
75
|
-
"X-AW-MCP-Version": "0.1.
|
|
75
|
+
"X-AW-MCP-Version": "0.1.41",
|
|
76
76
|
"X-AW-MCP-Tool": "run_agent",
|
|
77
77
|
"X-AW-MCP-Action": "execute",
|
|
78
78
|
}),
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
installClient,
|
|
7
|
+
mergeCodexTomlConfig,
|
|
8
|
+
mergeJsonMcpConfig,
|
|
9
|
+
parseSetupArgs,
|
|
10
|
+
type SetupClient,
|
|
11
|
+
} from "../../setup.js";
|
|
12
|
+
|
|
13
|
+
let tempDirs: string[] = [];
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await Promise.all(tempDirs.map((dir) => rm(dir, { recursive: true, force: true })));
|
|
17
|
+
tempDirs = [];
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("MCP setup", () => {
|
|
21
|
+
it("merges the server into existing JSON MCP config", () => {
|
|
22
|
+
const next = JSON.parse(mergeJsonMcpConfig(JSON.stringify({
|
|
23
|
+
mcpServers: {
|
|
24
|
+
existing: { command: "node", args: ["server.js"] },
|
|
25
|
+
},
|
|
26
|
+
otherSetting: true,
|
|
27
|
+
})));
|
|
28
|
+
|
|
29
|
+
expect(next.otherSetting).toBe(true);
|
|
30
|
+
expect(next.mcpServers.existing.command).toBe("node");
|
|
31
|
+
expect(next.mcpServers.agentwonderland).toEqual({
|
|
32
|
+
command: "npx",
|
|
33
|
+
args: ["-y", "@agentwonderland/mcp"],
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("replaces an existing Codex TOML block without disturbing other servers", () => {
|
|
38
|
+
const next = mergeCodexTomlConfig([
|
|
39
|
+
"[mcp_servers.other]",
|
|
40
|
+
'command = "node"',
|
|
41
|
+
'args = ["other.js"]',
|
|
42
|
+
"",
|
|
43
|
+
"[mcp_servers.agentwonderland]",
|
|
44
|
+
'command = "old"',
|
|
45
|
+
'args = ["old"]',
|
|
46
|
+
"",
|
|
47
|
+
"[model_provider.openai]",
|
|
48
|
+
'name = "OpenAI"',
|
|
49
|
+
"",
|
|
50
|
+
].join("\n"));
|
|
51
|
+
|
|
52
|
+
expect(next).toContain("[mcp_servers.other]");
|
|
53
|
+
expect(next).toContain("[mcp_servers.agentwonderland]");
|
|
54
|
+
expect(next).toContain('command = "npx"');
|
|
55
|
+
expect(next).toContain('args = ["-y", "@agentwonderland/mcp"]');
|
|
56
|
+
expect(next).not.toContain('command = "old"');
|
|
57
|
+
expect(next).toContain("[model_provider.openai]");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("writes config and creates a backup when updating", async () => {
|
|
61
|
+
const dir = await mkdtemp(path.join(tmpdir(), "aw-mcp-setup-"));
|
|
62
|
+
tempDirs.push(dir);
|
|
63
|
+
const configPath = path.join(dir, "mcp.json");
|
|
64
|
+
await writeFile(configPath, JSON.stringify({ mcpServers: { old: { command: "node" } } }), "utf8");
|
|
65
|
+
const client: SetupClient = {
|
|
66
|
+
id: "cursor",
|
|
67
|
+
name: "Cursor",
|
|
68
|
+
path: configPath,
|
|
69
|
+
format: "json",
|
|
70
|
+
detected: true,
|
|
71
|
+
note: "test",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const result = await installClient(client);
|
|
75
|
+
const written = JSON.parse(await readFile(configPath, "utf8"));
|
|
76
|
+
|
|
77
|
+
expect(result.status).toBe("updated");
|
|
78
|
+
expect(result.backupPath).toBeTruthy();
|
|
79
|
+
expect(written.mcpServers.agentwonderland.args).toEqual(["-y", "@agentwonderland/mcp"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("parses client selection flags", () => {
|
|
83
|
+
expect(parseSetupArgs(["--clients", "codex,cursor", "--yes"])).toEqual({
|
|
84
|
+
clients: ["codex", "cursor"],
|
|
85
|
+
yes: true,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
package/src/core/api-client.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -25,6 +25,9 @@ import { registerJobResources } from "./resources/jobs.js";
|
|
|
25
25
|
|
|
26
26
|
// ── Prompts ──────────────────────────────────────────────────────
|
|
27
27
|
import { registerPrompts } from "./prompts/index.js";
|
|
28
|
+
import { runSetupCli } from "./setup.js";
|
|
29
|
+
|
|
30
|
+
const PACKAGE_VERSION = "0.1.41";
|
|
28
31
|
|
|
29
32
|
export async function startMcpServer(): Promise<void> {
|
|
30
33
|
const server = new McpServer(
|
|
@@ -108,8 +111,49 @@ const isCli =
|
|
|
108
111
|
process.argv[1]?.includes("packages/mcp");
|
|
109
112
|
|
|
110
113
|
if (isCli) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
const args = process.argv.slice(2);
|
|
115
|
+
|
|
116
|
+
if (args[0] === "setup" || args[0] === "install" || args[0] === "configure") {
|
|
117
|
+
runSetupCli(args.slice(1)).catch((err) => {
|
|
118
|
+
console.error("Setup failed:", err instanceof Error ? err.message : err);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
|
121
|
+
} else if (args.includes("--help") || args.includes("-h")) {
|
|
122
|
+
console.log([
|
|
123
|
+
"Agent Wonderland MCP server",
|
|
124
|
+
"",
|
|
125
|
+
"Usage:",
|
|
126
|
+
" npx @agentwonderland/mcp setup Configure MCP clients",
|
|
127
|
+
" npx @agentwonderland/mcp Start the stdio MCP server",
|
|
128
|
+
"",
|
|
129
|
+
"The bare command starts a stdio MCP server. It is meant to be launched by",
|
|
130
|
+
"Codex, Cursor, Claude Desktop, or another MCP client. When run directly",
|
|
131
|
+
"in a terminal it waits silently for MCP JSON-RPC messages on stdin.",
|
|
132
|
+
"",
|
|
133
|
+
"MCP client config:",
|
|
134
|
+
' { "mcpServers": { "agentwonderland": { "command": "npx", "args": ["-y", "@agentwonderland/mcp"] } } }',
|
|
135
|
+
"",
|
|
136
|
+
"Options:",
|
|
137
|
+
" -h, --help Show this help",
|
|
138
|
+
" -v, --version Show package version",
|
|
139
|
+
].join("\n"));
|
|
140
|
+
process.exit(0);
|
|
141
|
+
} else if (args.includes("--version") || args.includes("-v")) {
|
|
142
|
+
console.log(PACKAGE_VERSION);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
} else {
|
|
145
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
146
|
+
console.error([
|
|
147
|
+
"Agent Wonderland MCP server is running.",
|
|
148
|
+
"This process speaks MCP over stdio, so it waits for a client instead of printing a prompt.",
|
|
149
|
+
"Add it to an MCP client config, or press Ctrl+C to stop.",
|
|
150
|
+
"Run `npx @agentwonderland/mcp setup` to configure your MCP clients.",
|
|
151
|
+
].join("\n"));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
startMcpServer().catch((err) => {
|
|
155
|
+
console.error("MCP server error:", err);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
115
159
|
}
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
export const SERVER_NAME = "agentwonderland";
|
|
10
|
+
export const SERVER_CONFIG = {
|
|
11
|
+
command: "npx",
|
|
12
|
+
args: ["-y", "@agentwonderland/mcp"],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type SetupClientId =
|
|
16
|
+
| "codex"
|
|
17
|
+
| "claude-code"
|
|
18
|
+
| "claude-desktop"
|
|
19
|
+
| "cursor"
|
|
20
|
+
| "antigravity";
|
|
21
|
+
|
|
22
|
+
type ConfigFormat = "json" | "toml" | "claude-code-cli";
|
|
23
|
+
|
|
24
|
+
export type SetupClient = {
|
|
25
|
+
id: SetupClientId;
|
|
26
|
+
name: string;
|
|
27
|
+
path: string;
|
|
28
|
+
format: ConfigFormat;
|
|
29
|
+
detected: boolean;
|
|
30
|
+
note: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type SetupOptions = {
|
|
34
|
+
all?: boolean;
|
|
35
|
+
yes?: boolean;
|
|
36
|
+
clients?: SetupClientId[];
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type InstallResult = {
|
|
41
|
+
client: SetupClient;
|
|
42
|
+
status: "created" | "updated" | "unchanged" | "skipped" | "failed";
|
|
43
|
+
backupPath?: string;
|
|
44
|
+
message?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const CLIENT_ORDER: SetupClientId[] = [
|
|
48
|
+
"codex",
|
|
49
|
+
"claude-code",
|
|
50
|
+
"claude-desktop",
|
|
51
|
+
"cursor",
|
|
52
|
+
"antigravity",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const CLIENT_LABELS: Record<SetupClientId, string> = {
|
|
56
|
+
codex: "Codex",
|
|
57
|
+
"claude-code": "Claude Code",
|
|
58
|
+
"claude-desktop": "Claude Desktop",
|
|
59
|
+
cursor: "Cursor",
|
|
60
|
+
antigravity: "Google Antigravity",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export async function runSetupCli(args: string[] = process.argv.slice(2)): Promise<void> {
|
|
64
|
+
const options = parseSetupArgs(args);
|
|
65
|
+
if (options.help) {
|
|
66
|
+
printSetupHelp();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const clients = getSetupClients();
|
|
71
|
+
const selected = await chooseClients(clients, options);
|
|
72
|
+
|
|
73
|
+
if (selected.length === 0) {
|
|
74
|
+
console.log("No MCP clients selected. Nothing changed.");
|
|
75
|
+
printManualConfig();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log("");
|
|
80
|
+
console.log("Agent Wonderland will add this MCP server entry:");
|
|
81
|
+
console.log(JSON.stringify({ mcpServers: { [SERVER_NAME]: SERVER_CONFIG } }, null, 2));
|
|
82
|
+
console.log("");
|
|
83
|
+
|
|
84
|
+
const results: InstallResult[] = [];
|
|
85
|
+
for (const client of selected) {
|
|
86
|
+
results.push(await installClient(client, options));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
printResults(results, options);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function parseSetupArgs(args: string[]): SetupOptions & { help?: boolean } {
|
|
93
|
+
const options: SetupOptions & { help?: boolean } = {};
|
|
94
|
+
|
|
95
|
+
for (let index = 0; index < args.length; index++) {
|
|
96
|
+
const arg = args[index];
|
|
97
|
+
if (arg === "--help" || arg === "-h") {
|
|
98
|
+
options.help = true;
|
|
99
|
+
} else if (arg === "--all") {
|
|
100
|
+
options.all = true;
|
|
101
|
+
} else if (arg === "--yes" || arg === "-y") {
|
|
102
|
+
options.yes = true;
|
|
103
|
+
} else if (arg === "--dry-run") {
|
|
104
|
+
options.dryRun = true;
|
|
105
|
+
} else if (arg === "--clients") {
|
|
106
|
+
const value = args[index + 1];
|
|
107
|
+
if (!value) throw new Error("--clients requires a comma-separated value");
|
|
108
|
+
options.clients = parseClientList(value);
|
|
109
|
+
index++;
|
|
110
|
+
} else if (arg.startsWith("--clients=")) {
|
|
111
|
+
options.clients = parseClientList(arg.slice("--clients=".length));
|
|
112
|
+
} else {
|
|
113
|
+
throw new Error(`Unknown setup option: ${arg}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return options;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseClientList(value: string): SetupClientId[] {
|
|
121
|
+
const selected = value
|
|
122
|
+
.split(",")
|
|
123
|
+
.map((item) => item.trim().toLowerCase())
|
|
124
|
+
.filter(Boolean) as SetupClientId[];
|
|
125
|
+
const invalid = selected.filter((item) => !CLIENT_ORDER.includes(item));
|
|
126
|
+
if (invalid.length > 0) {
|
|
127
|
+
throw new Error(`Unknown client(s): ${invalid.join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
return selected;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function getSetupClients(home = homedir(), env = process.env): SetupClient[] {
|
|
133
|
+
const appData = env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
134
|
+
const isWindows = platform() === "win32";
|
|
135
|
+
const isMac = platform() === "darwin";
|
|
136
|
+
const claudeDesktopPath = isMac
|
|
137
|
+
? path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
138
|
+
: isWindows
|
|
139
|
+
? path.join(appData, "Claude", "claude_desktop_config.json")
|
|
140
|
+
: path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
141
|
+
|
|
142
|
+
const clients: SetupClient[] = [
|
|
143
|
+
{
|
|
144
|
+
id: "codex",
|
|
145
|
+
name: CLIENT_LABELS.codex,
|
|
146
|
+
path: path.join(home, ".codex", "config.toml"),
|
|
147
|
+
format: "toml",
|
|
148
|
+
detected: existsSync(path.join(home, ".codex")) || commandExists("codex"),
|
|
149
|
+
note: "Global Codex CLI/Desktop config",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: "claude-code",
|
|
153
|
+
name: CLIENT_LABELS["claude-code"],
|
|
154
|
+
path: "claude mcp add-json --scope user",
|
|
155
|
+
format: "claude-code-cli",
|
|
156
|
+
detected: commandExists("claude"),
|
|
157
|
+
note: "Claude Code user-scope MCP config via the Claude CLI",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "claude-desktop",
|
|
161
|
+
name: CLIENT_LABELS["claude-desktop"],
|
|
162
|
+
path: claudeDesktopPath,
|
|
163
|
+
format: "json",
|
|
164
|
+
detected: existsSync(claudeDesktopPath) || existsSync(path.dirname(claudeDesktopPath)),
|
|
165
|
+
note: "Claude Desktop developer config",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "cursor",
|
|
169
|
+
name: CLIENT_LABELS.cursor,
|
|
170
|
+
path: path.join(home, ".cursor", "mcp.json"),
|
|
171
|
+
format: "json",
|
|
172
|
+
detected: existsSync(path.join(home, ".cursor")),
|
|
173
|
+
note: "Global Cursor MCP config",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "antigravity",
|
|
177
|
+
name: CLIENT_LABELS.antigravity,
|
|
178
|
+
path: path.join(home, ".gemini", "antigravity", "mcp_config.json"),
|
|
179
|
+
format: "json",
|
|
180
|
+
detected: existsSync(path.join(home, ".gemini", "antigravity")),
|
|
181
|
+
note: "Antigravity raw MCP config",
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
return clients;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function chooseClients(
|
|
189
|
+
clients: SetupClient[],
|
|
190
|
+
options: SetupOptions,
|
|
191
|
+
): Promise<SetupClient[]> {
|
|
192
|
+
if (options.clients?.length) {
|
|
193
|
+
return clients.filter((client) => options.clients?.includes(client.id));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (options.all) {
|
|
197
|
+
return clients;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const detected = clients.filter((client) => client.detected);
|
|
201
|
+
if (options.yes) {
|
|
202
|
+
return detected.length > 0 ? detected : clients.filter((client) => client.id === "codex");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log("Agent Wonderland MCP setup");
|
|
206
|
+
console.log("");
|
|
207
|
+
console.log("Detected clients:");
|
|
208
|
+
for (const client of clients) {
|
|
209
|
+
const marker = client.detected ? "found" : "not found";
|
|
210
|
+
console.log(` ${client.id.padEnd(15)} ${marker.padEnd(9)} ${client.path}`);
|
|
211
|
+
}
|
|
212
|
+
console.log("");
|
|
213
|
+
|
|
214
|
+
const rl = createInterface({ input, output });
|
|
215
|
+
try {
|
|
216
|
+
const selected: SetupClient[] = [];
|
|
217
|
+
for (const client of clients) {
|
|
218
|
+
const defaultAnswer = client.detected ? "Y/n" : "y/N";
|
|
219
|
+
const answer = (await rl.question(`Install for ${client.name}? (${defaultAnswer}) `)).trim().toLowerCase();
|
|
220
|
+
const yes = answer === ""
|
|
221
|
+
? client.detected
|
|
222
|
+
: answer === "y" || answer === "yes";
|
|
223
|
+
if (yes) selected.push(client);
|
|
224
|
+
}
|
|
225
|
+
return selected;
|
|
226
|
+
} finally {
|
|
227
|
+
rl.close();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function installClient(client: SetupClient, options: SetupOptions = {}): Promise<InstallResult> {
|
|
232
|
+
try {
|
|
233
|
+
if (client.format === "claude-code-cli") {
|
|
234
|
+
return installClaudeCode(client, options);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const existing = await readExisting(client.path);
|
|
238
|
+
const next = client.format === "json"
|
|
239
|
+
? mergeJsonMcpConfig(existing)
|
|
240
|
+
: mergeCodexTomlConfig(existing);
|
|
241
|
+
|
|
242
|
+
if (next === existing) {
|
|
243
|
+
return { client, status: "unchanged", message: "already configured" };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const backupPath = existing ? `${client.path}.bak-${timestamp()}` : undefined;
|
|
247
|
+
if (!options.dryRun) {
|
|
248
|
+
await mkdir(path.dirname(client.path), { recursive: true });
|
|
249
|
+
if (backupPath) {
|
|
250
|
+
await writeFile(backupPath, existing, "utf8");
|
|
251
|
+
}
|
|
252
|
+
await writeFile(client.path, next, "utf8");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
client,
|
|
257
|
+
status: existing ? "updated" : "created",
|
|
258
|
+
backupPath,
|
|
259
|
+
};
|
|
260
|
+
} catch (error) {
|
|
261
|
+
return {
|
|
262
|
+
client,
|
|
263
|
+
status: "failed",
|
|
264
|
+
message: error instanceof Error ? error.message : String(error),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function installClaudeCode(client: SetupClient, options: SetupOptions): InstallResult {
|
|
270
|
+
if (!commandExists("claude")) {
|
|
271
|
+
return {
|
|
272
|
+
client,
|
|
273
|
+
status: "failed",
|
|
274
|
+
message: [
|
|
275
|
+
"Claude Code CLI was not found on PATH.",
|
|
276
|
+
"Run manually after installing Claude Code:",
|
|
277
|
+
`claude mcp add-json ${SERVER_NAME} '${JSON.stringify({ type: "stdio", ...SERVER_CONFIG })}' --scope user`,
|
|
278
|
+
].join(" "),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!options.dryRun) {
|
|
283
|
+
execFileSync("claude", [
|
|
284
|
+
"mcp",
|
|
285
|
+
"add-json",
|
|
286
|
+
SERVER_NAME,
|
|
287
|
+
JSON.stringify({ type: "stdio", ...SERVER_CONFIG }),
|
|
288
|
+
"--scope",
|
|
289
|
+
"user",
|
|
290
|
+
], { stdio: "pipe" });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
client,
|
|
295
|
+
status: options.dryRun ? "skipped" : "updated",
|
|
296
|
+
message: options.dryRun ? "would run Claude Code CLI installer" : "installed with Claude Code CLI",
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function readExisting(filePath: string): Promise<string> {
|
|
301
|
+
try {
|
|
302
|
+
return await readFile(filePath, "utf8");
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return "";
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function mergeJsonMcpConfig(existing: string): string {
|
|
310
|
+
const parsed = existing.trim()
|
|
311
|
+
? JSON.parse(existing) as Record<string, unknown>
|
|
312
|
+
: {};
|
|
313
|
+
|
|
314
|
+
const mcpServers = typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers)
|
|
315
|
+
? parsed.mcpServers as Record<string, unknown>
|
|
316
|
+
: {};
|
|
317
|
+
|
|
318
|
+
const next = {
|
|
319
|
+
...parsed,
|
|
320
|
+
mcpServers: {
|
|
321
|
+
...mcpServers,
|
|
322
|
+
[SERVER_NAME]: SERVER_CONFIG,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return `${JSON.stringify(next, null, 2)}\n`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function mergeCodexTomlConfig(existing: string): string {
|
|
330
|
+
const block = [
|
|
331
|
+
`[mcp_servers.${SERVER_NAME}]`,
|
|
332
|
+
`command = "npx"`,
|
|
333
|
+
`args = ["-y", "@agentwonderland/mcp"]`,
|
|
334
|
+
"",
|
|
335
|
+
].join("\n");
|
|
336
|
+
|
|
337
|
+
const pattern = new RegExp(
|
|
338
|
+
`\\n?\\[mcp_servers\\.${SERVER_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\n\\[[^\\]]+\\]|$)`,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const trimmed = existing.trimEnd();
|
|
342
|
+
if (!trimmed) return block;
|
|
343
|
+
if (pattern.test(trimmed)) {
|
|
344
|
+
const next = trimmed.replace(pattern, `\n${block.trimEnd()}`);
|
|
345
|
+
return `${next.trimEnd()}\n`;
|
|
346
|
+
}
|
|
347
|
+
return `${trimmed}\n\n${block}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function printResults(results: InstallResult[], options: SetupOptions): void {
|
|
351
|
+
console.log(options.dryRun ? "Dry run complete:" : "Setup complete:");
|
|
352
|
+
for (const result of results) {
|
|
353
|
+
const suffix = result.backupPath ? ` (backup: ${result.backupPath})` : "";
|
|
354
|
+
const detail = result.message ? ` - ${result.message}` : "";
|
|
355
|
+
console.log(` ${result.status.padEnd(9)} ${result.client.name}: ${result.client.path}${suffix}${detail}`);
|
|
356
|
+
}
|
|
357
|
+
console.log("");
|
|
358
|
+
console.log("Restart any clients you updated, then ask your coding agent:");
|
|
359
|
+
console.log(' "Check my Agent Wonderland wallet status."');
|
|
360
|
+
console.log("");
|
|
361
|
+
console.log("If no wallet is configured yet, ask:");
|
|
362
|
+
console.log(' "Set up payment for Agent Wonderland."');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function printSetupHelp(): void {
|
|
366
|
+
console.log([
|
|
367
|
+
"Agent Wonderland MCP setup",
|
|
368
|
+
"",
|
|
369
|
+
"Usage:",
|
|
370
|
+
" npx @agentwonderland/mcp setup",
|
|
371
|
+
" npx @agentwonderland/mcp setup --clients codex,claude-code",
|
|
372
|
+
" npx @agentwonderland/mcp setup --all --yes",
|
|
373
|
+
"",
|
|
374
|
+
"Supported clients:",
|
|
375
|
+
` ${CLIENT_ORDER.join(", ")}`,
|
|
376
|
+
"",
|
|
377
|
+
"Options:",
|
|
378
|
+
" --clients <list> Comma-separated client ids",
|
|
379
|
+
" --all Offer/install every supported config target",
|
|
380
|
+
" -y, --yes Accept defaults without prompts",
|
|
381
|
+
" --dry-run Show what would be changed without writing files",
|
|
382
|
+
" -h, --help Show this help",
|
|
383
|
+
].join("\n"));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function printManualConfig(): void {
|
|
387
|
+
console.log("");
|
|
388
|
+
console.log("Manual MCP config:");
|
|
389
|
+
console.log(JSON.stringify({ mcpServers: { [SERVER_NAME]: SERVER_CONFIG } }, null, 2));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function timestamp(): string {
|
|
393
|
+
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function commandExists(command: string): boolean {
|
|
397
|
+
try {
|
|
398
|
+
if (platform() === "win32") {
|
|
399
|
+
execFileSync("where", [command], { stdio: "ignore" });
|
|
400
|
+
} else {
|
|
401
|
+
execFileSync("sh", ["-lc", `command -v ${shellQuote(command)}`], { stdio: "ignore" });
|
|
402
|
+
}
|
|
403
|
+
return true;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function shellQuote(value: string): string {
|
|
410
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
411
|
+
}
|