@agentwonderland/mcp 0.1.53 → 0.1.55

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.
@@ -21,6 +21,15 @@ export class LinkApprovalRequiredError extends Error {
21
21
  }
22
22
  }
23
23
 
24
+ export interface ApprovedLinkSpendRequestParams {
25
+ amount: string;
26
+ currency: string;
27
+ context: string;
28
+ expiresAt: number;
29
+ networkId: string;
30
+ paymentMethodId: string;
31
+ }
32
+
24
33
  function formatLinkApprovalRequiredMessage(spendRequestId: string, approvalUrl?: string): string {
25
34
  return [
26
35
  "Link approval required.",
@@ -137,6 +146,16 @@ function extractSpendRequestApproval(output: unknown): { id: string; approvalUrl
137
146
  return null;
138
147
  }
139
148
 
149
+ function extractSpendRequestStatus(output: unknown): { id?: string; approvalUrl?: string; status?: string } {
150
+ const record = Array.isArray(output) ? asRecord(output[0]) : asRecord(output);
151
+ if (!record) return {};
152
+ return {
153
+ id: typeof record.id === "string" ? record.id : undefined,
154
+ approvalUrl: typeof record.approval_url === "string" ? record.approval_url : undefined,
155
+ status: typeof record.status === "string" ? record.status : undefined,
156
+ };
157
+ }
158
+
140
159
  function isMatchingPendingSpendRequest(
141
160
  pending: PendingLinkSpendRequest | null,
142
161
  params: {
@@ -162,6 +181,14 @@ function isProjectedSpendCapError(message: string): boolean {
162
181
  return /projected daily spend|projected spend|exceeds limit/i.test(message);
163
182
  }
164
183
 
184
+ function isApprovedSpendRequestStatus(status?: string): boolean {
185
+ return /approved|active|ready/i.test(status ?? "");
186
+ }
187
+
188
+ function isPendingSpendRequestStatus(status?: string): boolean {
189
+ return /pending/i.test(status ?? "");
190
+ }
191
+
165
192
  function recordLinkCooldown(reason: string): void {
166
193
  const now = new Date();
167
194
  setLinkCooldown({
@@ -370,6 +397,168 @@ export async function createLinkSharedPaymentToken(params: {
370
397
  }
371
398
  }
372
399
 
400
+ export async function ensureApprovedLinkSpendRequest(params: ApprovedLinkSpendRequestParams): Promise<string> {
401
+ const existing = getPendingLinkSpendRequest();
402
+ if (isMatchingPendingSpendRequest(existing, params)) {
403
+ const retrieved = await runLinkCli([
404
+ "spend-request",
405
+ "retrieve",
406
+ existing.id,
407
+ ], 30_000);
408
+ const status = extractSpendRequestStatus(retrieved);
409
+ if (isApprovedSpendRequestStatus(status.status)) {
410
+ return existing.id;
411
+ }
412
+ if (isPendingSpendRequestStatus(status.status)) {
413
+ throw new LinkApprovalRequiredError(existing.id, existing.approvalUrl ?? status.approvalUrl);
414
+ }
415
+ setPendingLinkSpendRequest(null);
416
+ }
417
+
418
+ const args = [
419
+ "spend-request",
420
+ "create",
421
+ "--credential-type",
422
+ "shared_payment_token",
423
+ "--network-id",
424
+ params.networkId,
425
+ "--amount",
426
+ params.amount,
427
+ "--currency",
428
+ params.currency,
429
+ "--payment-method-id",
430
+ params.paymentMethodId,
431
+ "--context",
432
+ params.context,
433
+ "--request-approval",
434
+ ];
435
+
436
+ if (process.env.AGENTWONDERLAND_LINK_TEST_MODE === "1") {
437
+ args.push("--test");
438
+ }
439
+
440
+ let output: unknown;
441
+ try {
442
+ output = await runLinkCli(args);
443
+ } catch (err) {
444
+ const message = err instanceof Error ? err.message : String(err);
445
+ if (isProjectedSpendCapError(message)) {
446
+ recordLinkCooldown(message);
447
+ throw new Error(
448
+ [
449
+ "Link is temporarily blocked by Stripe's projected-spend cap.",
450
+ "Reauthing Link or switching cards in the same Link account will not fix this.",
451
+ "Use USDC for now, wait for the rolling Link window to clear, or ask Stripe to raise/clear the merchant projected-spend cap.",
452
+ "",
453
+ message,
454
+ ].join("\n"),
455
+ );
456
+ }
457
+ throw err;
458
+ }
459
+
460
+ const status = extractSpendRequestStatus(output);
461
+ const id = status.id ?? extractSpendRequestApproval(output)?.id;
462
+ if (id && isApprovedSpendRequestStatus(status.status)) {
463
+ setPendingLinkSpendRequest({
464
+ id,
465
+ approvalUrl: status.approvalUrl,
466
+ amount: params.amount,
467
+ currency: params.currency,
468
+ context: params.context,
469
+ expiresAt: params.expiresAt,
470
+ networkId: params.networkId,
471
+ paymentMethodId: params.paymentMethodId,
472
+ createdAt: new Date().toISOString(),
473
+ });
474
+ return id;
475
+ }
476
+
477
+ const approval = extractSpendRequestApproval(output);
478
+ if (approval?.id && isPendingSpendRequestStatus(approval.status)) {
479
+ setPendingLinkSpendRequest({
480
+ id: approval.id,
481
+ approvalUrl: approval.approvalUrl,
482
+ amount: params.amount,
483
+ currency: params.currency,
484
+ context: params.context,
485
+ expiresAt: params.expiresAt,
486
+ networkId: params.networkId,
487
+ paymentMethodId: params.paymentMethodId,
488
+ createdAt: new Date().toISOString(),
489
+ });
490
+ if (approval.approvalUrl) {
491
+ console.error(`Link approval required: ${approval.approvalUrl}`);
492
+ }
493
+ throw new LinkApprovalRequiredError(approval.id, approval.approvalUrl);
494
+ }
495
+
496
+ throw new Error("Link spend request did not return an approved or pending approval state.");
497
+ }
498
+
499
+ function extractMppPayBody(output: unknown): unknown {
500
+ if (typeof output === "string") {
501
+ try {
502
+ return JSON.parse(output);
503
+ } catch {
504
+ return output;
505
+ }
506
+ }
507
+ if (Array.isArray(output)) {
508
+ return output.length === 1 ? extractMppPayBody(output[0]) : output;
509
+ }
510
+ const record = asRecord(output);
511
+ if (!record) return output;
512
+ for (const key of ["body", "json", "data", "response_body"]) {
513
+ const value = record[key];
514
+ if (value !== undefined) return extractMppPayBody(value);
515
+ }
516
+ const response = asRecord(record.response);
517
+ if (response) return extractMppPayBody(response.body ?? response.json ?? response.data ?? response);
518
+ return output;
519
+ }
520
+
521
+ export async function payMppWithApprovedLinkSpendRequest(params: {
522
+ url: string;
523
+ method: string;
524
+ headers: Record<string, string>;
525
+ body?: unknown;
526
+ spendRequestId: string;
527
+ }): Promise<unknown> {
528
+ const args = [
529
+ "mpp",
530
+ "pay",
531
+ params.url,
532
+ "--spend-request-id",
533
+ params.spendRequestId,
534
+ "--method",
535
+ params.method,
536
+ ];
537
+
538
+ for (const [name, value] of Object.entries(params.headers)) {
539
+ args.push("--header", `${name}: ${value}`);
540
+ }
541
+ if (params.body !== undefined) {
542
+ args.push("--data", typeof params.body === "string" ? params.body : JSON.stringify(params.body));
543
+ }
544
+
545
+ const output = await runLinkCli(args);
546
+ return extractMppPayBody(output);
547
+ }
548
+
549
+ export async function decodeMppNetworkId(challenge: string): Promise<string> {
550
+ const output = await runLinkCli(["mpp", "decode", "--challenge", challenge], 30_000);
551
+ const found = walk(output, (value, key) => {
552
+ if (typeof value !== "string") return null;
553
+ if (key && /network[_-]?id/i.test(key)) return value;
554
+ return null;
555
+ });
556
+ if (!found) {
557
+ throw new Error("Could not decode Link MPP network_id from payment challenge.");
558
+ }
559
+ return found;
560
+ }
561
+
373
562
  async function retrieveSharedPaymentToken(spendRequestId: string, approvalUrl?: string): Promise<string> {
374
563
  const retrieved = await runLinkCli([
375
564
  "spend-request",
@@ -1 +1 @@
1
- export const MCP_PACKAGE_VERSION = "0.1.51";
1
+ export const MCP_PACKAGE_VERSION = "0.1.55";
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ import { registerRebateTools } from "./tools/rebates.js";
18
18
  import { registerUploadTools } from "./tools/upload.js";
19
19
  import { registerProbeTools } from "./tools/probe.js";
20
20
  import { registerProviderTools } from "./tools/providers.js";
21
+ import { registerPlaybookTools } from "./tools/playbooks.js";
21
22
 
22
23
  // ── Resources ────────────────────────────────────────────────────
23
24
  import { registerAgentResources } from "./resources/agents.js";
@@ -48,16 +49,17 @@ export async function startMcpServer(): Promise<void> {
48
49
  "WORKFLOW:",
49
50
  "1. solve(intent, input, budget) — one call: find best agent + pay + run. Use when the task is clear.",
50
51
  " OR: search_agents() → get_agent() to inspect schema → run_agent() with required fields.",
52
+ " For multi-agent workflows, use search_playbooks() → get_playbook() → run_playbook().",
51
53
  "2. If the agent returns status 'processing', poll with get_job(). Async runs resolve automatically.",
52
- "3. After a successful run, rate_agent() and optionally tip_agent() if the result was useful.",
54
+ "3. After a successful agent run, rate_agent() and optionally tip_agent() if the result was useful.",
55
+ " Playbooks return one run-level receipt; do not ask users to rate or tip individual child-agent steps.",
53
56
  "4. Use list_jobs() to recover state across sessions (it checks every configured wallet).",
54
57
  "",
55
58
  "PAYMENT:",
56
- "- Supported payment methods: Link card/bank via @stripe/link-cli, Tempo USDC, Base USDC, and Solana USDC.",
57
- "- Link payments may ask the user to approve a spend request in Link before the first charge.",
59
+ "- Supported launch payment methods: Tempo USDC, Base USDC, and Solana USDC.",
58
60
  "- Tempo and Base share one EVM wallet key. Solana uses a separate ed25519 key. One OWS wallet can manage both.",
59
61
  "- If pay_with is omitted, the MCP auto-selects a compatible configured rail. Pass pay_with explicitly",
60
- " (tempo | base | solana | link | wallet-id) for deterministic behavior.",
62
+ " (tempo | base | solana | wallet-id) for deterministic behavior.",
61
63
  "- Payment is automatic: on a 402 challenge the MCP signs on-chain, submits, then retries. Failed runs are refunded.",
62
64
  "- If a specific rail fails, surface the real reason — do NOT silently retry with a different method.",
63
65
  "- Headless/automation: set wallet_set_policy() to cap max_per_tx and max_per_day so a runaway loop can't drain funds.",
@@ -73,7 +75,7 @@ export async function startMcpServer(): Promise<void> {
73
75
  "WALLET HYGIENE:",
74
76
  "- wallet_status shows per-chain USDC balance and the active network (mainnet vs testnet).",
75
77
  "- rebate_status shows accrued, pending, paid, and blocked consumer rebates plus recent payout transactions.",
76
- "- To set up payments: wallet_setup({ action: \"start\" }). Link card/bank is recommended for most users.",
78
+ "- To set up payments: wallet_setup({ action: \"start\" }). Use Tempo, Base, or Solana USDC for launch runs.",
77
79
  "- To create or import a crypto wallet directly: wallet_setup({ action: \"create\" }) or { action: \"import\", key }.",
78
80
  "- NEVER delete or rotate keys programmatically. Direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.",
79
81
  ].join("\n"),
@@ -95,6 +97,7 @@ export async function startMcpServer(): Promise<void> {
95
97
  registerUploadTools(server);
96
98
  registerProbeTools(server);
97
99
  registerProviderTools(server);
100
+ registerPlaybookTools(server);
98
101
 
99
102
  // Register resources
100
103
  registerAgentResources(server);