@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.
@@ -76,52 +76,44 @@ async function initEvmMppForChain(
76
76
  wallet: WalletEntry,
77
77
  chain: "tempo" | "base",
78
78
  ): Promise<typeof fetch | null> {
79
- try {
80
- const { Mppx } = await import("./mpp-client.js");
81
- let account;
82
-
83
- if (wallet.keyType === "ows" && wallet.owsWalletId) {
84
- const { owsAccountFromWalletId } = await import("./ows-adapter.js");
85
- account = await owsAccountFromWalletId(wallet.owsWalletId);
86
- } else if (wallet.key) {
87
- const { privateKeyToAccount } = await import("viem/accounts");
88
- account = privateKeyToAccount(normalizeKey(wallet.key));
89
- } else {
90
- return null;
91
- }
92
-
93
- const methods = [];
94
- if (chain === "tempo") {
95
- const { tempoChargeClient } = await import("./tempo-charge.js");
96
- methods.push(tempoChargeClient({ account }));
97
- } else {
98
- const { baseChargeClient } = await import("./base-charge.js");
99
- methods.push(baseChargeClient({ account }));
100
- }
79
+ const { Mppx } = await import("./mpp-client.js");
80
+ let account;
101
81
 
102
- const mppx = Mppx.create({ methods: methods as any, polyfill: false });
103
- return mppx.fetch.bind(mppx) as typeof fetch;
104
- } catch {
82
+ if (wallet.keyType === "ows" && wallet.owsWalletId) {
83
+ const { owsAccountFromWalletId } = await import("./ows-adapter.js");
84
+ account = await owsAccountFromWalletId(wallet.owsWalletId);
85
+ } else if (wallet.key) {
86
+ const { privateKeyToAccount } = await import("viem/accounts");
87
+ account = privateKeyToAccount(normalizeKey(wallet.key));
88
+ } else {
105
89
  return null;
106
90
  }
91
+
92
+ const methods = [];
93
+ if (chain === "tempo") {
94
+ const { tempoChargeClient } = await import("./tempo-charge.js");
95
+ methods.push(tempoChargeClient({ account }));
96
+ } else {
97
+ const { baseChargeClient } = await import("./base-charge.js");
98
+ methods.push(baseChargeClient({ account }));
99
+ }
100
+
101
+ const mppx = Mppx.create({ methods: methods as any, polyfill: false });
102
+ return mppx.fetch.bind(mppx) as typeof fetch;
107
103
  }
108
104
 
109
105
  async function initSolanaMpp(wallet: WalletEntry): Promise<typeof fetch | null> {
110
- try {
111
- if (wallet.keyType !== "ows" && !wallet.key) {
112
- return null;
113
- }
114
-
115
- const { Mppx } = await import("./mpp-client.js");
116
- const { solanaChargeClient } = await import("./solana-charge.js");
117
- const mppx = Mppx.create({
118
- methods: [solanaChargeClient({ wallet })] as any,
119
- polyfill: false,
120
- });
121
- return mppx.fetch.bind(mppx) as typeof fetch;
122
- } catch {
106
+ if (wallet.keyType !== "ows" && !wallet.key) {
123
107
  return null;
124
108
  }
109
+
110
+ const { Mppx } = await import("./mpp-client.js");
111
+ const { solanaChargeClient } = await import("./solana-charge.js");
112
+ const mppx = Mppx.create({
113
+ methods: [solanaChargeClient({ wallet })] as any,
114
+ polyfill: false,
115
+ });
116
+ return mppx.fetch.bind(mppx) as typeof fetch;
125
117
  }
126
118
 
127
119
  async function initCard(): Promise<typeof fetch | null> {
@@ -178,6 +170,11 @@ async function initForChain(wallet: WalletEntry, chain: string): Promise<typeof
178
170
  return null;
179
171
  }
180
172
 
173
+ function initFailureMessage(method: string, wallet: WalletEntry, chain: string, err: unknown): string {
174
+ const reason = err instanceof Error ? err.message : String(err);
175
+ return `Payment method "${method}" is configured as ${chain} on wallet "${wallet.id}", but the signer could not initialize: ${reason}`;
176
+ }
177
+
181
178
  // ── Public API ──────────────────────────────────────────────────
182
179
 
183
180
  /**
@@ -214,7 +211,12 @@ export async function getPaymentFetch(method?: string): Promise<typeof fetch> {
214
211
  }
215
212
  const ck = cacheKey(resolved.wallet.id, resolved.chain);
216
213
  if (fetchCache.has(ck)) return fetchCache.get(ck)!;
217
- const pf = await initForChain(resolved.wallet, resolved.chain);
214
+ let pf: typeof fetch | null;
215
+ try {
216
+ pf = await initForChain(resolved.wallet, resolved.chain);
217
+ } catch (err) {
218
+ throw new Error(initFailureMessage(method, resolved.wallet, resolved.chain, err));
219
+ }
218
220
  if (pf) {
219
221
  fetchCache.set(ck, pf);
220
222
  return pf;
@@ -264,7 +266,17 @@ export async function getPaymentFetch(method?: string): Promise<typeof fetch> {
264
266
  }
265
267
  const ck = cacheKey(resolved.wallet.id, resolved.chain);
266
268
  if (fetchCache.has(ck)) return fetchCache.get(ck)!;
267
- const pf = await initForChain(resolved.wallet, resolved.chain);
269
+ let pf: typeof fetch | null;
270
+ try {
271
+ pf = await initForChain(resolved.wallet, resolved.chain);
272
+ } catch (err) {
273
+ if (m === defaultMethod) {
274
+ const others = configured.filter((x) => x !== m);
275
+ const altText = others.length > 0 ? ` Alternatives: ${others.join(", ")}` : "";
276
+ throw new Error(`${initFailureMessage(m, resolved.wallet, resolved.chain, err)}.${altText}`);
277
+ }
278
+ continue;
279
+ }
268
280
  if (pf) {
269
281
  fetchCache.set(ck, pf);
270
282
  return pf;
package/src/index.ts CHANGED
@@ -25,8 +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";
28
29
 
29
- const PACKAGE_VERSION = "0.1.40";
30
+ const PACKAGE_VERSION = "0.1.42";
30
31
 
31
32
  export async function startMcpServer(): Promise<void> {
32
33
  const server = new McpServer(
@@ -112,43 +113,47 @@ const isCli =
112
113
  if (isCli) {
113
114
  const args = process.argv.slice(2);
114
115
 
115
- if (args.includes("--help") || args.includes("-h")) {
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")) {
116
122
  console.log([
117
123
  "Agent Wonderland MCP server",
118
124
  "",
119
125
  "Usage:",
120
- " npx @agentwonderland/mcp",
126
+ " npx @agentwonderland/mcp setup Configure MCP clients",
127
+ " npx @agentwonderland/mcp Start the stdio MCP server",
121
128
  "",
122
- "This command starts a stdio MCP server. It is meant to be launched by",
129
+ "The bare command starts a stdio MCP server. It is meant to be launched by",
123
130
  "Codex, Cursor, Claude Desktop, or another MCP client. When run directly",
124
131
  "in a terminal it waits silently for MCP JSON-RPC messages on stdin.",
125
132
  "",
126
133
  "MCP client config:",
127
- ' { "mcpServers": { "agentwonderland": { "command": "npx", "args": ["@agentwonderland/mcp"] } } }',
134
+ ' { "mcpServers": { "agentwonderland": { "command": "npx", "args": ["-y", "@agentwonderland/mcp"] } } }',
128
135
  "",
129
136
  "Options:",
130
137
  " -h, --help Show this help",
131
138
  " -v, --version Show package version",
132
139
  ].join("\n"));
133
140
  process.exit(0);
134
- }
135
-
136
- if (args.includes("--version") || args.includes("-v")) {
141
+ } else if (args.includes("--version") || args.includes("-v")) {
137
142
  console.log(PACKAGE_VERSION);
138
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
+ });
139
158
  }
140
-
141
- if (process.stdin.isTTY && process.stdout.isTTY) {
142
- console.error([
143
- "Agent Wonderland MCP server is running.",
144
- "This process speaks MCP over stdio, so it waits for a client instead of printing a prompt.",
145
- "Add it to an MCP client config, or press Ctrl+C to stop.",
146
- "Run `npx @agentwonderland/mcp --help` for setup details.",
147
- ].join("\n"));
148
- }
149
-
150
- startMcpServer().catch((err) => {
151
- console.error("MCP server error:", err);
152
- process.exit(1);
153
- });
154
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
+ }
@@ -280,6 +280,8 @@ export function registerWalletTools(server: McpServer): void {
280
280
  "",
281
281
  chainStatus,
282
282
  "",
283
+ "No MCP restart is required for wallet changes.",
284
+ "",
283
285
  `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
284
286
  ].join("\n"));
285
287
  }
@@ -313,6 +315,8 @@ export function registerWalletTools(server: McpServer): void {
313
315
  ` Chains: solana`,
314
316
  ` Consumer principal: ${principal}`,
315
317
  "",
318
+ "No MCP restart is required for wallet changes.",
319
+ "",
316
320
  "Fund this address with USDC on Solana to start using agents.",
317
321
  ].join("\n") + owsNudge,
318
322
  );
@@ -340,6 +344,8 @@ export function registerWalletTools(server: McpServer): void {
340
344
  ` Chains: tempo, base`,
341
345
  ` Consumer principal: ${principal}`,
342
346
  "",
347
+ "No MCP restart is required for wallet changes.",
348
+ "",
343
349
  `Fund this address with USDC on Tempo or Base to start using agents.`,
344
350
  ].join("\n") + owsNudge,
345
351
  );
@@ -369,6 +375,8 @@ export function registerWalletTools(server: McpServer): void {
369
375
  ` Storage: ~/.ows/ (AES-256-GCM encrypted)`,
370
376
  ` Consumer principal: ${principal}`,
371
377
  "",
378
+ "No MCP restart is required for wallet changes.",
379
+ "",
372
380
  `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
373
381
  "For testnet: npx mppx account fund",
374
382
  ].join("\n"),
@@ -395,11 +403,13 @@ export function registerWalletTools(server: McpServer): void {
395
403
  `Key imported to OWS [encrypted]:`,
396
404
  ` ID: ${result.walletId}`,
397
405
  ` Address: ${result.address}`,
398
- ` Name: ${walletName}`,
399
- ` Chains: ${selectedChains.join(", ")}`,
400
- ` Consumer principal: ${principal}`,
401
- ].join("\n"),
402
- );
406
+ ` Name: ${walletName}`,
407
+ ` Chains: ${selectedChains.join(", ")}`,
408
+ ` Consumer principal: ${principal}`,
409
+ "",
410
+ "No MCP restart is required for wallet changes.",
411
+ ].join("\n"),
412
+ );
403
413
  }
404
414
 
405
415
  if (defaultCh === "solana") {
@@ -429,6 +439,8 @@ export function registerWalletTools(server: McpServer): void {
429
439
  ` Name: ${walletName}`,
430
440
  ` Chains: solana`,
431
441
  ` Consumer principal: ${principal}`,
442
+ "",
443
+ "No MCP restart is required for wallet changes.",
432
444
  ].join("\n") + owsNudge,
433
445
  );
434
446
  } catch (err) {
@@ -468,6 +480,8 @@ export function registerWalletTools(server: McpServer): void {
468
480
  ` Name: ${walletName}`,
469
481
  ` Chains: ${selectedChains.join(", ")}`,
470
482
  ` Consumer principal: ${principal}`,
483
+ "",
484
+ "No MCP restart is required for wallet changes.",
471
485
  ].join("\n") + owsNudge,
472
486
  );
473
487
  } catch {