@agentwonderland/mcp 0.1.40 → 0.1.42

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.
@@ -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.37",
49
+ "X-AW-MCP-Version": "0.1.42",
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
+ });
@@ -12,7 +12,7 @@ export class ApiError extends Error {
12
12
  this.name = "ApiError";
13
13
  }
14
14
  }
15
- const MCP_VERSION = "0.1.37";
15
+ const MCP_VERSION = "0.1.42";
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" };
@@ -7,6 +7,9 @@
7
7
  * The OWS SDK (`@open-wallet-standard/core`) is a NAPI native module.
8
8
  * ALL imports are dynamic so the MCP server works without OWS installed.
9
9
  */
10
+ import { createHmac } from "node:crypto";
11
+ import { mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
12
+ import { wordlist } from "@scure/bip39/wordlists/english";
10
13
  // ── Helpers ──────────────────────────────────────────────────────
11
14
  const OWS_INSTALL_HINT = "OWS is not installed. Install with: npm install -g @open-wallet-standard/core";
12
15
  async function loadOws() {
@@ -58,6 +61,51 @@ function keypairFromEd25519Hex(privateKeyHex, KeypairCtor) {
58
61
  }
59
62
  throw new Error(`Unsupported ed25519 key length: ${bytes.length} bytes.`);
60
63
  }
64
+ function parseExportedSecret(exported) {
65
+ const secret = exported.trim();
66
+ try {
67
+ const parsed = JSON.parse(secret);
68
+ if (parsed.secp256k1 || parsed.ed25519 || parsed.mnemonic)
69
+ return parsed;
70
+ }
71
+ catch {
72
+ // OWS-created wallets export a BIP-39 mnemonic, not a JSON key object.
73
+ }
74
+ const hex = secret.startsWith("0x") ? secret.slice(2) : secret;
75
+ if (/^[0-9a-fA-F]+$/.test(hex)) {
76
+ if (hex.length === 64)
77
+ return { secp256k1: hex, ed25519: hex };
78
+ if (hex.length === 128)
79
+ return { ed25519: hex };
80
+ }
81
+ if (validateMnemonic(secret, wordlist)) {
82
+ return { mnemonic: secret };
83
+ }
84
+ return {};
85
+ }
86
+ function deriveSolanaSeedFromMnemonic(mnemonic, path = "m/44'/501'/0'/0'") {
87
+ const seed = mnemonicToSeedSync(mnemonic);
88
+ let digest = createHmac("sha512", "ed25519 seed").update(seed).digest();
89
+ let key = digest.subarray(0, 32);
90
+ let chainCode = digest.subarray(32);
91
+ for (const part of path.split("/").slice(1)) {
92
+ if (!part.endsWith("'")) {
93
+ throw new Error(`Unsupported non-hardened ed25519 path segment: ${part}`);
94
+ }
95
+ const index = Number(part.slice(0, -1));
96
+ if (!Number.isInteger(index) || index < 0) {
97
+ throw new Error(`Unsupported ed25519 path segment: ${part}`);
98
+ }
99
+ const data = Buffer.alloc(37);
100
+ data[0] = 0;
101
+ Buffer.from(key).copy(data, 1);
102
+ data.writeUInt32BE(index + 0x80000000, 33);
103
+ digest = createHmac("sha512", chainCode).update(data).digest();
104
+ key = digest.subarray(0, 32);
105
+ chainCode = digest.subarray(32);
106
+ }
107
+ return Uint8Array.from(key);
108
+ }
61
109
  // ── Public API ───────────────────────────────────────────────────
62
110
  /**
63
111
  * Check whether the OWS native module can be loaded.
@@ -160,7 +208,11 @@ export async function owsAccountFromWalletId(walletId, _chain) {
160
208
  // (EIP-5806 delegate calls) which OWS's native signing can't serialize.
161
209
  // OWS provides encrypted storage at rest; viem handles signing in memory.
162
210
  const exported = ows.exportWallet(walletId);
163
- const keys = JSON.parse(exported);
211
+ const keys = parseExportedSecret(exported);
212
+ if (keys.mnemonic) {
213
+ const { mnemonicToAccount } = await import("viem/accounts");
214
+ return mnemonicToAccount(keys.mnemonic);
215
+ }
164
216
  if (!keys.secp256k1) {
165
217
  throw new Error(`Wallet "${wallet.name}" has no secp256k1 key for EVM signing.`);
166
218
  }
@@ -172,11 +224,14 @@ export async function owsSolanaKeypairFromWalletId(walletId) {
172
224
  const wallet = ows.getWallet(walletId);
173
225
  findSolanaAccount(wallet);
174
226
  const exported = ows.exportWallet(walletId);
175
- const keys = JSON.parse(exported);
227
+ const keys = parseExportedSecret(exported);
228
+ const { Keypair } = await import("@solana/web3.js");
229
+ if (keys.mnemonic) {
230
+ return Keypair.fromSeed(deriveSolanaSeedFromMnemonic(keys.mnemonic));
231
+ }
176
232
  if (!keys.ed25519) {
177
233
  throw new Error(`Wallet "${wallet.name}" has no ed25519 key for Solana signing.`);
178
234
  }
179
- const { Keypair } = await import("@solana/web3.js");
180
235
  return keypairFromEd25519Hex(keys.ed25519, Keypair);
181
236
  }
182
237
  export async function getOwsWalletAddress(walletId, chain = "evm") {
@@ -53,52 +53,42 @@ function clearStaleCardCache(activeKey) {
53
53
  }
54
54
  // ── Per-protocol initializers ───────────────────────────────────
55
55
  async function initEvmMppForChain(wallet, chain) {
56
- try {
57
- const { Mppx } = await import("./mpp-client.js");
58
- let account;
59
- if (wallet.keyType === "ows" && wallet.owsWalletId) {
60
- const { owsAccountFromWalletId } = await import("./ows-adapter.js");
61
- account = await owsAccountFromWalletId(wallet.owsWalletId);
62
- }
63
- else if (wallet.key) {
64
- const { privateKeyToAccount } = await import("viem/accounts");
65
- account = privateKeyToAccount(normalizeKey(wallet.key));
66
- }
67
- else {
68
- return null;
69
- }
70
- const methods = [];
71
- if (chain === "tempo") {
72
- const { tempoChargeClient } = await import("./tempo-charge.js");
73
- methods.push(tempoChargeClient({ account }));
74
- }
75
- else {
76
- const { baseChargeClient } = await import("./base-charge.js");
77
- methods.push(baseChargeClient({ account }));
78
- }
79
- const mppx = Mppx.create({ methods: methods, polyfill: false });
80
- return mppx.fetch.bind(mppx);
56
+ const { Mppx } = await import("./mpp-client.js");
57
+ let account;
58
+ if (wallet.keyType === "ows" && wallet.owsWalletId) {
59
+ const { owsAccountFromWalletId } = await import("./ows-adapter.js");
60
+ account = await owsAccountFromWalletId(wallet.owsWalletId);
81
61
  }
82
- catch {
62
+ else if (wallet.key) {
63
+ const { privateKeyToAccount } = await import("viem/accounts");
64
+ account = privateKeyToAccount(normalizeKey(wallet.key));
65
+ }
66
+ else {
83
67
  return null;
84
68
  }
69
+ const methods = [];
70
+ if (chain === "tempo") {
71
+ const { tempoChargeClient } = await import("./tempo-charge.js");
72
+ methods.push(tempoChargeClient({ account }));
73
+ }
74
+ else {
75
+ const { baseChargeClient } = await import("./base-charge.js");
76
+ methods.push(baseChargeClient({ account }));
77
+ }
78
+ const mppx = Mppx.create({ methods: methods, polyfill: false });
79
+ return mppx.fetch.bind(mppx);
85
80
  }
86
81
  async function initSolanaMpp(wallet) {
87
- try {
88
- if (wallet.keyType !== "ows" && !wallet.key) {
89
- return null;
90
- }
91
- const { Mppx } = await import("./mpp-client.js");
92
- const { solanaChargeClient } = await import("./solana-charge.js");
93
- const mppx = Mppx.create({
94
- methods: [solanaChargeClient({ wallet })],
95
- polyfill: false,
96
- });
97
- return mppx.fetch.bind(mppx);
98
- }
99
- catch {
82
+ if (wallet.keyType !== "ows" && !wallet.key) {
100
83
  return null;
101
84
  }
85
+ const { Mppx } = await import("./mpp-client.js");
86
+ const { solanaChargeClient } = await import("./solana-charge.js");
87
+ const mppx = Mppx.create({
88
+ methods: [solanaChargeClient({ wallet })],
89
+ polyfill: false,
90
+ });
91
+ return mppx.fetch.bind(mppx);
102
92
  }
103
93
  async function initCard() {
104
94
  const cardConfig = getCardConfig();
@@ -149,6 +139,10 @@ async function initForChain(wallet, chain) {
149
139
  }
150
140
  return null;
151
141
  }
142
+ function initFailureMessage(method, wallet, chain, err) {
143
+ const reason = err instanceof Error ? err.message : String(err);
144
+ return `Payment method "${method}" is configured as ${chain} on wallet "${wallet.id}", but the signer could not initialize: ${reason}`;
145
+ }
152
146
  // ── Public API ──────────────────────────────────────────────────
153
147
  /**
154
148
  * Returns a payment-aware fetch for a specific method, or the best
@@ -185,7 +179,13 @@ export async function getPaymentFetch(method) {
185
179
  const ck = cacheKey(resolved.wallet.id, resolved.chain);
186
180
  if (fetchCache.has(ck))
187
181
  return fetchCache.get(ck);
188
- const pf = await initForChain(resolved.wallet, resolved.chain);
182
+ let pf;
183
+ try {
184
+ pf = await initForChain(resolved.wallet, resolved.chain);
185
+ }
186
+ catch (err) {
187
+ throw new Error(initFailureMessage(method, resolved.wallet, resolved.chain, err));
188
+ }
189
189
  if (pf) {
190
190
  fetchCache.set(ck, pf);
191
191
  return pf;
@@ -234,7 +234,18 @@ export async function getPaymentFetch(method) {
234
234
  const ck = cacheKey(resolved.wallet.id, resolved.chain);
235
235
  if (fetchCache.has(ck))
236
236
  return fetchCache.get(ck);
237
- const pf = await initForChain(resolved.wallet, resolved.chain);
237
+ let pf;
238
+ try {
239
+ pf = await initForChain(resolved.wallet, resolved.chain);
240
+ }
241
+ catch (err) {
242
+ if (m === defaultMethod) {
243
+ const others = configured.filter((x) => x !== m);
244
+ const altText = others.length > 0 ? ` Alternatives: ${others.join(", ")}` : "";
245
+ throw new Error(`${initFailureMessage(m, resolved.wallet, resolved.chain, err)}.${altText}`);
246
+ }
247
+ continue;
248
+ }
238
249
  if (pf) {
239
250
  fetchCache.set(ck, pf);
240
251
  return pf;
package/dist/index.js CHANGED
@@ -21,7 +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
- const PACKAGE_VERSION = "0.1.40";
24
+ import { runSetupCli } from "./setup.js";
25
+ const PACKAGE_VERSION = "0.1.42";
25
26
  export async function startMcpServer() {
26
27
  const server = new McpServer({
27
28
  name: "agentwonderland",
@@ -95,19 +96,26 @@ const isCli = process.argv[1]?.endsWith("agentwonderland-mcp") ||
95
96
  process.argv[1]?.includes("packages/mcp");
96
97
  if (isCli) {
97
98
  const args = process.argv.slice(2);
98
- if (args.includes("--help") || args.includes("-h")) {
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")) {
99
106
  console.log([
100
107
  "Agent Wonderland MCP server",
101
108
  "",
102
109
  "Usage:",
103
- " npx @agentwonderland/mcp",
110
+ " npx @agentwonderland/mcp setup Configure MCP clients",
111
+ " npx @agentwonderland/mcp Start the stdio MCP server",
104
112
  "",
105
- "This command starts a stdio MCP server. It is meant to be launched by",
113
+ "The bare command starts a stdio MCP server. It is meant to be launched by",
106
114
  "Codex, Cursor, Claude Desktop, or another MCP client. When run directly",
107
115
  "in a terminal it waits silently for MCP JSON-RPC messages on stdin.",
108
116
  "",
109
117
  "MCP client config:",
110
- ' { "mcpServers": { "agentwonderland": { "command": "npx", "args": ["@agentwonderland/mcp"] } } }',
118
+ ' { "mcpServers": { "agentwonderland": { "command": "npx", "args": ["-y", "@agentwonderland/mcp"] } } }',
111
119
  "",
112
120
  "Options:",
113
121
  " -h, --help Show this help",
@@ -115,20 +123,22 @@ if (isCli) {
115
123
  ].join("\n"));
116
124
  process.exit(0);
117
125
  }
118
- if (args.includes("--version") || args.includes("-v")) {
126
+ else if (args.includes("--version") || args.includes("-v")) {
119
127
  console.log(PACKAGE_VERSION);
120
128
  process.exit(0);
121
129
  }
122
- if (process.stdin.isTTY && process.stdout.isTTY) {
123
- console.error([
124
- "Agent Wonderland MCP server is running.",
125
- "This process speaks MCP over stdio, so it waits for a client instead of printing a prompt.",
126
- "Add it to an MCP client config, or press Ctrl+C to stop.",
127
- "Run `npx @agentwonderland/mcp --help` for setup details.",
128
- ].join("\n"));
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
+ });
129
143
  }
130
- startMcpServer().catch((err) => {
131
- console.error("MCP server error:", err);
132
- process.exit(1);
133
- });
134
144
  }
@@ -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 {};