@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.
package/dist/tools/run.js CHANGED
@@ -2,12 +2,11 @@ import { z } from "zod";
2
2
  import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
3
3
  import { uploadLocalFiles } from "../core/file-upload.js";
4
4
  import { formatCreditPackOffer, getActiveCreditPack, getCreditPackInventory, getCreditPackProgram, } from "../core/passes.js";
5
- import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, getWalletAddress, normalizePaymentMethod, isCardPaymentEnabled, } from "../core/payments.js";
5
+ import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, getWalletAddress, normalizePaymentMethod, } from "../core/payments.js";
6
6
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
7
7
  import { formatRunResult } from "../core/formatters.js";
8
8
  import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
9
9
  import { storeFeedbackToken } from "./_token-cache.js";
10
- import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
11
10
  import { formatPaymentLabel, formatRunConfirmationCommand, resolveConfirmationMethod, } from "./_payment-confirmation.js";
12
11
  const POLL_INTERVAL_MS = 3000;
13
12
  const POLL_MAX_MS = 300000;
@@ -88,30 +87,17 @@ export function registerRunTools(server) {
88
87
  server.tool("run_agent", "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead — that keeps the bytes out of the conversation context.", {
89
88
  agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
90
89
  input: z.record(z.string(), z.unknown()).describe("Input payload for the agent"),
91
- pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base, solana), 'link', or 'card'. Auto-detected if omitted."),
90
+ pay_with: z.string().trim().min(1).optional().describe("Launch payment method — wallet ID or chain name (tempo, base, solana). Auto-detected if omitted."),
92
91
  confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
93
92
  }, async ({ agent_id, input, pay_with, confirmed }) => {
94
93
  if (!hasWalletConfigured()) {
95
- if (isCardPaymentEnabled()) {
96
- try {
97
- const { url } = await getOrCreatePendingCardSetup();
98
- return multiText(...formatCardSetupBlocks(url));
99
- }
100
- catch {
101
- // Fall through to the setup message below.
102
- }
103
- }
104
94
  const setupLines = [
105
95
  "No payment method configured.",
106
96
  "",
107
- "Recommended: wallet_setup({ action: \"add-link\" }) to connect a Link card or bank account.",
108
- "You can also run wallet_setup({ action: \"start\" }) to see all payment options.",
97
+ "Run wallet_setup({ action: \"start\" }) to configure a launch USDC payment method.",
109
98
  "",
110
- "USDC options: Tempo, Base, or Solana.",
99
+ "Launch options: Tempo USDC, Base USDC, or Solana USDC.",
111
100
  ];
112
- if (isCardPaymentEnabled()) {
113
- setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
114
- }
115
101
  return text(setupLines.join("\n"));
116
102
  }
117
103
  let agent;
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
3
- import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
4
- import { getCompatiblePaymentMethods, hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, normalizePaymentMethod, toRegistryPaymentMethod, isCardPaymentEnabled, } from "../core/payments.js";
3
+ import { getCompatiblePaymentMethods, hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, normalizePaymentMethod, toRegistryPaymentMethod, } from "../core/payments.js";
5
4
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
6
5
  import { agentList, formatRunResult } from "../core/formatters.js";
7
6
  import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
@@ -126,33 +125,20 @@ export function registerSolveTools(server) {
126
125
  .trim()
127
126
  .min(1)
128
127
  .optional()
129
- .describe("Payment method — wallet ID, chain name (tempo, base, solana), 'link', or 'card'. Auto-detected if omitted."),
128
+ .describe("Launch payment method — wallet ID or chain name (tempo, base, solana). Auto-detected if omitted."),
130
129
  confirmed: z
131
130
  .boolean()
132
131
  .optional()
133
132
  .describe("Set to true to confirm spending after seeing the price quote."),
134
133
  }, async ({ intent, input, budget, pay_with, confirmed }) => {
135
134
  if (!hasWalletConfigured()) {
136
- if (isCardPaymentEnabled()) {
137
- try {
138
- const { url } = await getOrCreatePendingCardSetup();
139
- return multiText(...formatCardSetupBlocks(url));
140
- }
141
- catch {
142
- // Fall through to the setup message below.
143
- }
144
- }
145
135
  const setupLines = [
146
136
  "No payment method configured.",
147
137
  "",
148
- "Recommended: wallet_setup({ action: \"add-link\" }) to connect a Link card or bank account.",
149
- "You can also run wallet_setup({ action: \"start\" }) to see all payment options.",
138
+ "Run wallet_setup({ action: \"start\" }) to configure a launch USDC payment method.",
150
139
  "",
151
- "USDC options: Tempo, Base, or Solana.",
140
+ "Launch options: Tempo USDC, Base USDC, or Solana USDC.",
152
141
  ];
153
- if (isCardPaymentEnabled()) {
154
- setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
155
- }
156
142
  return text(setupLines.join("\n"));
157
143
  }
158
144
  const pendingKey = makeSolvePendingKey(intent, input, budget);
@@ -30,21 +30,18 @@ function isFreshLinkSetup(pending) {
30
30
  }
31
31
  function formatPaymentSetupMenu() {
32
32
  return [
33
- "Choose a payment method to set up:",
33
+ "Choose a launch payment method to set up:",
34
34
  "",
35
- "1. Link card or bank account (recommended)",
36
- " wallet_setup({ action: \"add-link\" })",
37
- "",
38
- "2. Tempo USDC",
35
+ "1. Tempo USDC",
39
36
  " wallet_setup({ action: \"create\", chain: \"tempo\" })",
40
37
  "",
41
- "3. Base USDC",
38
+ "2. Base USDC",
42
39
  " wallet_setup({ action: \"create\", chain: \"base\" })",
43
40
  "",
44
- "4. Solana USDC",
41
+ "3. Solana USDC",
45
42
  " wallet_setup({ action: \"create\", chain: \"solana\" })",
46
43
  "",
47
- "5. Import an existing wallet",
44
+ "4. Import an existing wallet",
48
45
  " wallet_setup({ action: \"import\", chain: \"base\", key: \"<private key>\" })",
49
46
  ].join("\n");
50
47
  }
@@ -53,7 +50,7 @@ function setDefaultCryptoPaymentMethod(chain) {
53
50
  }
54
51
  export function registerWalletTools(server) {
55
52
  // ── wallet_status (extracted from check_wallet) ─────────────────
56
- server.tool("wallet_status", "Check payment readiness. Shows all configured wallets, their chains, addresses, and card status.", {}, async () => {
53
+ server.tool("wallet_status", "Check launch payment readiness. Shows configured wallets, chains, addresses, and USDC balances.", {}, async () => {
57
54
  const wallets = getWallets();
58
55
  const card = getCardConfig();
59
56
  const link = getLinkConfig();
@@ -133,10 +130,10 @@ export function registerWalletTools(server) {
133
130
  return text(lines.join("\n"));
134
131
  });
135
132
  // ── wallet_setup ────────────────────────────────────────────────
136
- server.tool("wallet_setup", "Set up or manage an Agent Wonderland payment method. Use 'start' for a guided setup menu. Link card/bank is recommended for most users. 'add-link' connects a Link card or bank account for agent payments. 'create' makes a new crypto wallet (encrypted via OWS if available, otherwise plaintext — run 'enable-ows' to upgrade). 'import' takes an existing private key. 'enable-ows' installs the Open Wallet Standard native module for encrypted at-rest storage. Tempo/Base share one EVM key; Solana uses a separate ed25519 key. NEVER delete or rotate keys programmatically; direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.", {
133
+ server.tool("wallet_setup", "Set up or manage an Agent Wonderland launch payment method. Use 'start' for a guided USDC setup menu. 'create' makes a new crypto wallet (encrypted via OWS if available, otherwise plaintext — run 'enable-ows' to upgrade). 'import' takes an existing private key. 'enable-ows' installs the Open Wallet Standard native module for encrypted at-rest storage. Tempo/Base share one EVM key; Solana uses a separate ed25519 key. Card and Link actions are present for non-launch compatibility only and should not be recommended for launch runs. NEVER delete or rotate keys programmatically; direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.", {
137
134
  action: z
138
135
  .enum(["start", "create", "import", "add-card", "remove-card", "add-link", "remove-link", "enable-ows"])
139
- .describe("'start' shows the guided payment setup menu, 'add-link' connects Link card/bank, 'create' makes a crypto wallet, 'import' imports an existing key, 'enable-ows' installs encrypted key storage"),
136
+ .describe("'start' shows the guided launch USDC setup menu, 'create' makes a crypto wallet, 'import' imports an existing key, 'enable-ows' installs encrypted key storage; add-card/add-link are non-launch compatibility actions"),
140
137
  name: z
141
138
  .string()
142
139
  .optional()
@@ -150,7 +147,7 @@ export function registerWalletTools(server) {
150
147
  link_payment_method_id: z.string().optional()
151
148
  .describe("Link payment method ID from `npx @stripe/link-cli payment-methods list`; used with action 'add-link'."),
152
149
  link_payment_method: z.string().optional()
153
- .describe("Friendly Link payment method selector, such as a card/bank name or last4; used with action 'add-link'."),
150
+ .describe("Friendly Link payment method selector, such as a saved payment method label or last4; used with action 'add-link'."),
154
151
  }, async ({ action, name, key, chain, link_payment_method_id, link_payment_method }) => {
155
152
  if (action === "start") {
156
153
  return text(formatPaymentSetupMenu());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentwonderland/mcp",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "type": "module",
5
5
  "description": "MCP server for the Agent Wonderland AI agent marketplace",
6
6
  "bin": {
@@ -10,6 +10,7 @@ const {
10
10
  mockGetConsumerPrincipalForMethod,
11
11
  mockGetBaseRebatePrincipal,
12
12
  mockPaymentFetch,
13
+ mockPayMppWithApprovedLinkSpendRequest,
13
14
  } = vi.hoisted(() => ({
14
15
  mockGetApiUrl: vi.fn(),
15
16
  mockGetApiKey: vi.fn(),
@@ -19,6 +20,7 @@ const {
19
20
  mockGetConsumerPrincipalForMethod: vi.fn(),
20
21
  mockGetBaseRebatePrincipal: vi.fn(),
21
22
  mockPaymentFetch: vi.fn(),
23
+ mockPayMppWithApprovedLinkSpendRequest: vi.fn(),
22
24
  }));
23
25
 
24
26
  vi.mock("../config.js", () => ({
@@ -39,6 +41,10 @@ vi.mock("../principal.js", () => ({
39
41
  getBaseRebatePrincipal: (...args: unknown[]) => mockGetBaseRebatePrincipal(...args),
40
42
  }));
41
43
 
44
+ vi.mock("../link-cli.js", () => ({
45
+ payMppWithApprovedLinkSpendRequest: (...args: unknown[]) => mockPayMppWithApprovedLinkSpendRequest(...args),
46
+ }));
47
+
42
48
  describe("api-client headers", () => {
43
49
  beforeEach(() => {
44
50
  vi.clearAllMocks();
@@ -61,6 +67,7 @@ describe("api-client headers", () => {
61
67
  status: 200,
62
68
  headers: { "content-type": "application/json" },
63
69
  }));
70
+ mockPayMppWithApprovedLinkSpendRequest.mockResolvedValue({ ok: true });
64
71
  });
65
72
 
66
73
  it("includes the Base rebate principal alongside the method-specific consumer principal", async () => {
@@ -86,4 +93,74 @@ describe("api-client headers", () => {
86
93
  }),
87
94
  );
88
95
  });
96
+
97
+ it("labels playbook API calls with MCP surface, version, tool, and action headers", async () => {
98
+ const fetchMock = vi.fn(async () => new Response(JSON.stringify({ ok: true }), {
99
+ status: 200,
100
+ headers: { "content-type": "application/json" },
101
+ }));
102
+ vi.stubGlobal("fetch", fetchMock);
103
+ const { apiGet, apiPost } = await import("../api-client.js");
104
+
105
+ await apiGet("/playbooks?limit=1");
106
+ await apiGet("/playbooks/competitor-ads");
107
+ await apiGet("/playbooks/competitor-ads/versions");
108
+ await apiGet("/playbooks/competitor-ads/versions/1");
109
+ await apiPost("/playbooks/competitor-ads/favorite", {});
110
+ await apiPost("/playbooks/competitor-ads/feedback", { rating: 5 });
111
+ await apiPost("/playbook-quotes", { slug: "competitor-ads", budget: 5 });
112
+ await apiPost("/playbook-runs", { slug: "competitor-ads", budget: 5 });
113
+ await apiGet("/playbook-runs/run-1");
114
+ await apiPost("/playbook-runs/run-1/steps/ads", { status: "running" });
115
+
116
+ const calls = (fetchMock.mock.calls as unknown as Array<[unknown, { headers?: unknown }]>).map(([url, init]) => ({
117
+ url: String(url).replace("https://api.agentwonderland.test", ""),
118
+ headers: init.headers as Record<string, string>,
119
+ }));
120
+
121
+ for (const call of calls) {
122
+ expect(call.headers).toMatchObject({
123
+ "Content-Type": "application/json",
124
+ Accept: "application/json",
125
+ "X-AW-Surface": "mcp",
126
+ "X-AW-MCP-Version": MCP_PACKAGE_VERSION,
127
+ });
128
+ }
129
+ expect(calls[0]).toMatchObject({ url: "/playbooks?limit=1", headers: { "X-AW-MCP-Tool": "search_playbooks", "X-AW-MCP-Action": "search" } });
130
+ expect(calls[1]).toMatchObject({ url: "/playbooks/competitor-ads", headers: { "X-AW-MCP-Tool": "get_playbook", "X-AW-MCP-Action": "view" } });
131
+ expect(calls[2]).toMatchObject({ url: "/playbooks/competitor-ads/versions", headers: { "X-AW-MCP-Tool": "get_playbook", "X-AW-MCP-Action": "view" } });
132
+ expect(calls[3]).toMatchObject({ url: "/playbooks/competitor-ads/versions/1", headers: { "X-AW-MCP-Tool": "get_playbook", "X-AW-MCP-Action": "view" } });
133
+ expect(calls[4]).toMatchObject({ url: "/playbooks/competitor-ads/favorite", headers: { "X-AW-MCP-Tool": "favorite_playbook", "X-AW-MCP-Action": "favorite" } });
134
+ expect(calls[5]).toMatchObject({ url: "/playbooks/competitor-ads/feedback", headers: { "X-AW-MCP-Tool": "rate_playbook", "X-AW-MCP-Action": "feedback" } });
135
+ expect(calls[6]).toMatchObject({ url: "/playbook-quotes", headers: { "X-AW-MCP-Tool": "run_playbook", "X-AW-MCP-Action": "quote" } });
136
+ expect(calls[7]).toMatchObject({ url: "/playbook-runs", headers: { "X-AW-MCP-Tool": "run_playbook", "X-AW-MCP-Action": "execute" } });
137
+ expect(calls[8]).toMatchObject({ url: "/playbook-runs/run-1", headers: { "X-AW-MCP-Tool": "run_playbook", "X-AW-MCP-Action": "poll" } });
138
+ expect(calls[9]).toMatchObject({ url: "/playbook-runs/run-1/steps/ads", headers: { "X-AW-MCP-Tool": "run_playbook", "X-AW-MCP-Action": "execute" } });
139
+ });
140
+
141
+ it("uses Link MPP pay with run-agent headers for an approved spend request", async () => {
142
+ const { apiPostWithApprovedLinkSpendRequest } = await import("../api-client.js");
143
+
144
+ await apiPostWithApprovedLinkSpendRequest(
145
+ "/agents/agent-1/run",
146
+ { input: { text: "hello" } },
147
+ "lsrq_test_123",
148
+ );
149
+
150
+ expect(mockPayMppWithApprovedLinkSpendRequest).toHaveBeenCalledWith(expect.objectContaining({
151
+ url: "https://api.agentwonderland.test/agents/agent-1/run",
152
+ method: "POST",
153
+ body: { input: { text: "hello" } },
154
+ spendRequestId: "lsrq_test_123",
155
+ headers: expect.objectContaining({
156
+ "Content-Type": "application/json",
157
+ Accept: "application/json",
158
+ "X-AW-Surface": "mcp",
159
+ "X-AW-MCP-Version": MCP_PACKAGE_VERSION,
160
+ "X-AW-MCP-Tool": "run_agent",
161
+ "X-AW-MCP-Action": "execute",
162
+ }),
163
+ }));
164
+ expect(mockEnsureConsumerPrincipalForMethod).toHaveBeenCalledWith("link");
165
+ });
89
166
  });
@@ -122,4 +122,114 @@ describe("Link CLI spend requests", () => {
122
122
  expect(execCalls[0]?.args).toEqual(expect.arrayContaining(["spend-request", "retrieve", "lsrq_test_123"]));
123
123
  expect(state.pendingWrites).toEqual([null]);
124
124
  });
125
+
126
+ it("reuses an approved spend request id for playbook MPP payments", async () => {
127
+ const expiresAt = Math.floor(Date.now() / 1000) + 300;
128
+ state.pending = {
129
+ id: "lsrq_test_123",
130
+ approvalUrl: "https://link.example/approve/lsrq_test_123",
131
+ amount: "500",
132
+ currency: "usd",
133
+ context: "Agent Wonderland playbook test context that is long enough for Link",
134
+ expiresAt,
135
+ networkId: "profile_test",
136
+ paymentMethodId: "csmrpd_test_123",
137
+ createdAt: new Date().toISOString(),
138
+ };
139
+ outputs.push({ id: "lsrq_test_123", status: "approved" });
140
+
141
+ const { ensureApprovedLinkSpendRequest } = await import("../link-cli.js");
142
+
143
+ await expect(ensureApprovedLinkSpendRequest({
144
+ amount: "500",
145
+ currency: "usd",
146
+ context: "Agent Wonderland playbook test context that is long enough for Link",
147
+ expiresAt,
148
+ networkId: "profile_test",
149
+ paymentMethodId: "csmrpd_test_123",
150
+ })).resolves.toBe("lsrq_test_123");
151
+
152
+ expect(execCalls).toHaveLength(1);
153
+ expect(execCalls[0]?.args).toEqual(expect.arrayContaining(["spend-request", "retrieve", "lsrq_test_123"]));
154
+ expect(state.pendingWrites).toEqual([]);
155
+ });
156
+
157
+ it("pauses when the reusable Link spend request is still pending approval", async () => {
158
+ const expiresAt = Math.floor(Date.now() / 1000) + 300;
159
+ state.pending = {
160
+ id: "lsrq_test_123",
161
+ approvalUrl: "https://link.example/approve/lsrq_test_123",
162
+ amount: "500",
163
+ currency: "usd",
164
+ context: "Agent Wonderland playbook test context that is long enough for Link",
165
+ expiresAt,
166
+ networkId: "profile_test",
167
+ paymentMethodId: "csmrpd_test_123",
168
+ createdAt: new Date().toISOString(),
169
+ };
170
+ outputs.push({ id: "lsrq_test_123", status: "pending_approval" });
171
+
172
+ const { ensureApprovedLinkSpendRequest, LinkApprovalRequiredError } = await import("../link-cli.js");
173
+
174
+ await expect(ensureApprovedLinkSpendRequest({
175
+ amount: "500",
176
+ currency: "usd",
177
+ context: "Agent Wonderland playbook test context that is long enough for Link",
178
+ expiresAt,
179
+ networkId: "profile_test",
180
+ paymentMethodId: "csmrpd_test_123",
181
+ })).rejects.toBeInstanceOf(LinkApprovalRequiredError);
182
+
183
+ expect(execCalls).toHaveLength(1);
184
+ expect(state.pendingWrites).toEqual([]);
185
+ });
186
+
187
+ it("runs Link MPP pay with an approved spend request and returns the response body", async () => {
188
+ outputs.push({
189
+ response: {
190
+ body: {
191
+ status: "success",
192
+ job_id: "job_123",
193
+ },
194
+ },
195
+ });
196
+
197
+ const { payMppWithApprovedLinkSpendRequest } = await import("../link-cli.js");
198
+
199
+ await expect(payMppWithApprovedLinkSpendRequest({
200
+ url: "https://api.agentwonderland.test/agents/agent-1/run",
201
+ method: "POST",
202
+ headers: {
203
+ Accept: "application/json",
204
+ "Content-Type": "application/json",
205
+ },
206
+ body: { input: { text: "hello" } },
207
+ spendRequestId: "lsrq_test_123",
208
+ })).resolves.toEqual({
209
+ status: "success",
210
+ job_id: "job_123",
211
+ });
212
+
213
+ expect(execCalls[0]?.args).toEqual(expect.arrayContaining([
214
+ "mpp",
215
+ "pay",
216
+ "https://api.agentwonderland.test/agents/agent-1/run",
217
+ "--spend-request-id",
218
+ "lsrq_test_123",
219
+ "--method",
220
+ "POST",
221
+ "--data",
222
+ JSON.stringify({ input: { text: "hello" } }),
223
+ ]));
224
+ expect(execCalls[0]?.args).toContain("Accept: application/json");
225
+ });
226
+
227
+ it("decodes the MPP network id from a payment challenge", async () => {
228
+ outputs.push({ accepts: [{ network_id: "profile_test" }] });
229
+
230
+ const { decodeMppNetworkId } = await import("../link-cli.js");
231
+
232
+ await expect(decodeMppNetworkId("Payment challenge")).resolves.toBe("profile_test");
233
+ expect(execCalls[0]?.args).toEqual(expect.arrayContaining(["mpp", "decode", "--challenge", "Payment challenge"]));
234
+ });
125
235
  });
@@ -8,6 +8,7 @@ import {
8
8
  getConsumerPrincipalForMethod,
9
9
  } from "./principal.js";
10
10
  import { MCP_PACKAGE_VERSION } from "./version.js";
11
+ import { payMppWithApprovedLinkSpendRequest } from "./link-cli.js";
11
12
 
12
13
  // ── Error class ────────────────────────────────────────────────────
13
14
 
@@ -16,6 +17,7 @@ export class ApiError extends Error {
16
17
  public readonly status: number,
17
18
  message: string,
18
19
  public readonly body?: unknown,
20
+ public readonly headers?: Headers,
19
21
  ) {
20
22
  super(message);
21
23
  this.name = "ApiError";
@@ -40,6 +42,24 @@ function inferToolHeaders(path: string, method: string): Record<string, string>
40
42
  if (path.startsWith("/agents?") || path === "/agents" || path === "/agents/search") {
41
43
  return { "X-AW-MCP-Tool": "search_agents", "X-AW-MCP-Action": "search" };
42
44
  }
45
+ if (path.startsWith("/playbooks?") || path === "/playbooks") {
46
+ return { "X-AW-MCP-Tool": "search_playbooks", "X-AW-MCP-Action": "search" };
47
+ }
48
+ if (/^\/playbooks\/[^/]+(?:\/versions(?:\/[^/]+)?)?$/.test(path)) {
49
+ return { "X-AW-MCP-Tool": "get_playbook", "X-AW-MCP-Action": "view" };
50
+ }
51
+ if (/^\/playbooks\/[^/]+\/favorite$/.test(path)) {
52
+ return { "X-AW-MCP-Tool": "favorite_playbook", "X-AW-MCP-Action": "favorite" };
53
+ }
54
+ if (/^\/playbooks\/[^/]+\/feedback$/.test(path)) {
55
+ return { "X-AW-MCP-Tool": "rate_playbook", "X-AW-MCP-Action": "feedback" };
56
+ }
57
+ if (path === "/playbook-quotes") {
58
+ return { "X-AW-MCP-Tool": "run_playbook", "X-AW-MCP-Action": "quote" };
59
+ }
60
+ if (path === "/playbook-runs" || path.startsWith("/playbook-runs/")) {
61
+ return { "X-AW-MCP-Tool": "run_playbook", "X-AW-MCP-Action": method === "GET" ? "poll" : "execute" };
62
+ }
43
63
  if (/^\/agents\/[^/]+$/.test(path)) {
44
64
  return { "X-AW-MCP-Tool": "get_agent", "X-AW-MCP-Action": "view" };
45
65
  }
@@ -103,7 +123,7 @@ async function handleResponse<T>(response: Response): Promise<T> {
103
123
  : typeof body === "string"
104
124
  ? body
105
125
  : `Request failed with status ${response.status}`;
106
- throw new ApiError(response.status, message, body);
126
+ throw new ApiError(response.status, message, body, response.headers);
107
127
  }
108
128
 
109
129
  return body as T;
@@ -187,6 +207,28 @@ export async function apiPostWithPayment<T>(
187
207
  return attachResponseMetadata(result, response);
188
208
  }
189
209
 
210
+ export async function apiPostWithApprovedLinkSpendRequest<T>(
211
+ path: string,
212
+ body: unknown,
213
+ spendRequestId: string,
214
+ options?: RequestOptions,
215
+ ): Promise<T> {
216
+ const url = `${getApiUrl()}${path}`;
217
+ const headers = await buildHeaders(path, "POST", {
218
+ ensureConsumerPrincipal: true,
219
+ principalMethod: "link",
220
+ ...options,
221
+ });
222
+ const result = await payMppWithApprovedLinkSpendRequest({
223
+ url,
224
+ method: "POST",
225
+ headers,
226
+ body,
227
+ spendRequestId,
228
+ });
229
+ return result as T;
230
+ }
231
+
190
232
  export async function apiPut<T>(path: string, body: unknown, options?: RequestOptions): Promise<T> {
191
233
  const url = `${getApiUrl()}${path}`;
192
234
  const response = await fetch(url, {