@arispay/payagent-mcp
Advanced tools
+120
-0
@@ -10,2 +10,3 @@ #!/usr/bin/env node | ||
| bootstrapAgent, | ||
| discover, | ||
| formatUSDC, | ||
@@ -16,2 +17,3 @@ getAgent, | ||
| getUSDCBalance, | ||
| inspectChallenge, | ||
| launchAgent, | ||
@@ -26,2 +28,95 @@ listAgents, | ||
| // src/discover-tools.ts | ||
| function formatMoneyCents(cents, currency) { | ||
| const whole = Math.floor(cents / 100); | ||
| const frac = String(cents % 100).padStart(2, "0"); | ||
| const symbol = (currency ?? "USD").toUpperCase() === "EUR" ? "\u20AC" : "$"; | ||
| return `${symbol}${whole}.${frac}`; | ||
| } | ||
| function candidatePrice(c) { | ||
| const p = c.pricing; | ||
| if (!p || p.model === "free") return "free"; | ||
| if (typeof p.amount === "number" && Number.isInteger(p.amount)) { | ||
| const per = p.per ? ` / ${p.per}` : ""; | ||
| return `${p.amount}\xA2 (${formatMoneyCents(p.amount, p.currency)})${per}`; | ||
| } | ||
| return p.model; | ||
| } | ||
| async function runDiscoverPaidApi({ query, budgetCentsMax, category, limit }, discoverImpl) { | ||
| try { | ||
| const input = { intent: query }; | ||
| if (budgetCentsMax !== void 0) { | ||
| if (!Number.isInteger(budgetCentsMax) || budgetCentsMax < 0) { | ||
| return { | ||
| text: `discover_paid_api: budgetCentsMax must be a non-negative integer number of cents (got ${budgetCentsMax}).`, | ||
| isError: true | ||
| }; | ||
| } | ||
| input.budgetCentsMax = budgetCentsMax; | ||
| } | ||
| if (category) input.category = category; | ||
| input.limit = Math.min(Math.max(1, Math.trunc(limit ?? 5)), 20); | ||
| const { candidates } = await discoverImpl(input); | ||
| if (!candidates.length) { | ||
| return { | ||
| text: `No paid APIs matched "${query}". Try a broader query or a higher budget (integer cents).`, | ||
| isError: false | ||
| }; | ||
| } | ||
| const lines = candidates.map( | ||
| (c, i) => [ | ||
| `${i + 1}. ${c.name}`, | ||
| ...c.endpoint.url ? [` url: ${c.endpoint.url}`] : [], | ||
| ` price: ${candidatePrice(c)}`, | ||
| ` healthy: ${c.healthy === false ? "no" : "yes"} verified: ${c.verified ? "yes" : "no"}${c.source ? ` source: ${c.source}` : ""}` | ||
| ].join("\n") | ||
| ); | ||
| lines.push("", "Read-only \u2014 nothing was paid. Call one with pay_api({ url })."); | ||
| return { text: lines.join("\n"), isError: false }; | ||
| } catch (err) { | ||
| return { | ||
| text: `discover_paid_api failed: ${err instanceof Error ? err.message : String(err)}`, | ||
| isError: true | ||
| }; | ||
| } | ||
| } | ||
| async function runInspectPaidApi({ url }, inspectImpl) { | ||
| try { | ||
| const result = await inspectImpl(url); | ||
| if (!result.gated) { | ||
| return { | ||
| text: `${url} does not appear to be x402-gated: it returned HTTP ${result.status}, not 402. Nothing to pay here \u2014 try fetching it normally.`, | ||
| isError: true | ||
| }; | ||
| } | ||
| const lines = [`x402 challenge for ${url}:`]; | ||
| for (const a of result.accepts) { | ||
| lines.push( | ||
| "", | ||
| ` scheme: ${a.scheme}`, | ||
| ` network: ${a.network}`, | ||
| ` asset: ${a.asset}${a.assetName ? ` (${a.assetName})` : ""}`, | ||
| a.amountCents !== void 0 ? ` amount: ${a.amountBaseUnits} base units = ${a.amountCents}\xA2 (${formatMoneyCents(a.amountCents, a.currency)})` : ` amount: ${a.amountBaseUnits} base units`, | ||
| ` payTo: ${a.payTo}` | ||
| ); | ||
| if (a.description) lines.push(` about: ${a.description}`); | ||
| if (a.mimeType) lines.push(` type: ${a.mimeType}`); | ||
| } | ||
| if (result.serverDeclaredFacilitator) { | ||
| lines.push( | ||
| "", | ||
| ` Server-declared facilitator: ${result.serverDeclaredFacilitator}`, | ||
| " (The server settles through this facilitator \u2014 its choice, not the payer's.)" | ||
| ); | ||
| } | ||
| lines.push("", "Read-only \u2014 no payment was sent. Pay with pay_api({ url })."); | ||
| return { text: lines.join("\n"), isError: false }; | ||
| } catch (err) { | ||
| return { | ||
| text: `inspect_paid_api failed: ${err instanceof Error ? err.message : String(err)}`, | ||
| isError: true | ||
| }; | ||
| } | ||
| } | ||
| // src/pay-api-helpers.ts | ||
@@ -598,2 +693,27 @@ function looksLikeInsufficientFunds(message) { | ||
| ); | ||
| server.tool( | ||
| "discover_paid_api", | ||
| "Search the ArisPay marketplace for paid APIs by capability or plain-language intent, optionally budget-bounded (integer cents). Read-only \u2014 costs nothing and needs no API key. Returns ranked candidates with endpoint URL, price in cents, health and verification flags. Follow up with pay_api to actually call one, or inspect_paid_api to see a URL's exact payment requirements first.", | ||
| { | ||
| query: z.string().describe("Capability or plain-language intent, e.g. 'flight search'."), | ||
| budgetCentsMax: z.number().int().nonnegative().optional().describe("Maximum acceptable price in INTEGER CENTS (500 = $5.00). Free listings always pass."), | ||
| category: z.string().optional().describe("Optional category filter (inference, data, media, search, social, infrastructure, trading, other)."), | ||
| limit: z.number().int().min(1).max(20).default(5).describe("Max results (default 5, max 20).") | ||
| }, | ||
| async ({ query, budgetCentsMax, category, limit }) => { | ||
| const result = await runDiscoverPaidApi({ query, budgetCentsMax, category, limit }, discover); | ||
| return textResult(result.text, result.isError); | ||
| } | ||
| ); | ||
| server.tool( | ||
| "inspect_paid_api", | ||
| "Fetch an x402-protected URL WITHOUT paying to see its price, asset, network, and payment requirements. Read-only, never pays \u2014 no payment header, no auth, no API key. Use before pay_api to know what a resource costs.", | ||
| { | ||
| url: z.string().describe("The URL to inspect. Expected to return HTTP 402 with an x402 challenge.") | ||
| }, | ||
| async ({ url }) => { | ||
| const result = await runInspectPaidApi({ url }, inspectChallenge); | ||
| return textResult(result.text, result.isError); | ||
| } | ||
| ); | ||
| if (platformToolsEnabled()) { | ||
@@ -600,0 +720,0 @@ server.tool( |
+12
-11
| { | ||
| "name": "@arispay/payagent-mcp", | ||
| "version": "3.1.2", | ||
| "description": "MCP server that gives AI agents a wallet: pay x402 APIs (USD + EUR), check balances, self-onboard with one tool call. Use with Claude, Cursor, or any MCP client — delegated custody, no private keys on the agent.", | ||
| "version": "3.2.0", | ||
| "description": "MCP server that gives AI agents a wallet: pay x402 APIs (USD + EUR), check balances, self-onboard with one tool call. Use with Claude, Cursor, or any MCP client \u2014 delegated custody, no private keys on the agent.", | ||
| "type": "module", | ||
@@ -18,2 +18,8 @@ "bin": { | ||
| }, | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "type-check": "tsc --noEmit", | ||
| "prepublishOnly": "npm run build" | ||
| }, | ||
| "keywords": [ | ||
@@ -55,4 +61,4 @@ "arispay", | ||
| "@modelcontextprotocol/sdk": "^1.12.1", | ||
| "zod": "^3.25.0", | ||
| "payagent": "^2.13.4" | ||
| "payagent": "^2.14.0", | ||
| "zod": "^3.25.0" | ||
| }, | ||
@@ -65,8 +71,3 @@ "devDependencies": { | ||
| "private": false, | ||
| "mcpName": "io.github.stevemilton/payagent-mcp", | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "type-check": "tsc --noEmit" | ||
| } | ||
| } | ||
| "mcpName": "io.github.stevemilton/payagent-mcp" | ||
| } |
Sorry, the diff of this file is too big to display
104357
19.63%863
16.15%Updated