@goplausible/algorand-mcp 4.3.1 → 4.3.2

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.
@@ -86,7 +86,7 @@ export class UtilityManager {
86
86
  type: 'text',
87
87
  text: JSON.stringify({
88
88
  name: 'Algorand MCP Server',
89
- version: '4.3.1',
89
+ version: '4.3.2',
90
90
  builder: 'GoPlausible',
91
91
  description: 'A Model Context Protocol (MCP) server providing comprehensive access to the Algorand blockchain. Supports account management, transaction building and signing, smart contract interaction, asset operations, ARC-26 URI generation, and deep integration with Algorand ecosystem services including NFDomains, Tinyman, Vestige, and Ultrade.',
92
92
  blockchain: 'Algorand — a carbon-negative, pure proof-of-stake Layer 1 blockchain delivering instant finality, low fees, and advanced smart contract capabilities via AVM (Algorand Virtual Machine).',
@@ -12,6 +12,15 @@ export declare class X402Manager {
12
12
  }>;
13
13
  private static handleDiscover;
14
14
  private static handlePaidRequest;
15
+ /**
16
+ * Returns the amount field from a PaymentRequirement, supporting both x402
17
+ * spec versions:
18
+ * - V2 (current): `amount`
19
+ * - V1 (legacy): `maxAmountRequired`
20
+ * Prefers V2 if both are set (V2 deployments may include both for compat).
21
+ */
22
+ private static getRequirementAmount;
23
+ private static isValidPaymentRequiredResponse;
15
24
  private static discover;
16
25
  private static selectRequirement;
17
26
  private static buildPaymentHeader;
@@ -108,7 +108,10 @@ export class X402Manager {
108
108
  throw new McpError(ErrorCode.InvalidRequest, `Payment rejected by server: ${X402Manager.snippet(response.bodyText)}`);
109
109
  }
110
110
  const result = X402Manager.tryParseJson(response.bodyText) ?? response.bodyText;
111
- const paymentResponseHeader = response.headers['x-payment-response'] ?? response.headers['X-PAYMENT-RESPONSE'];
111
+ // Algorand x402 V2 uses lowercase `payment-response`; older deployments
112
+ // may use Coinbase's `x-payment-response`. Accept either.
113
+ const paymentResponseHeader = response.headers['payment-response']
114
+ ?? response.headers['x-payment-response'];
112
115
  return X402Manager.textResponse({
113
116
  result,
114
117
  status: response.status,
@@ -116,27 +119,51 @@ export class X402Manager {
116
119
  paid: {
117
120
  network: mcpNetwork,
118
121
  asset: requirement.asset,
119
- amount: requirement.maxAmountRequired,
122
+ amount: X402Manager.getRequirementAmount(requirement),
120
123
  payTo: requirement.payTo,
121
124
  },
122
125
  extensions,
123
126
  });
124
127
  }
125
128
  // ── x402 protocol helpers ──────────────────────────────────────────────────
129
+ /**
130
+ * Returns the amount field from a PaymentRequirement, supporting both x402
131
+ * spec versions:
132
+ * - V2 (current): `amount`
133
+ * - V1 (legacy): `maxAmountRequired`
134
+ * Prefers V2 if both are set (V2 deployments may include both for compat).
135
+ */
136
+ static getRequirementAmount(req) {
137
+ return req.amount ?? req.maxAmountRequired;
138
+ }
139
+ static isValidPaymentRequiredResponse(v) {
140
+ return !!v
141
+ && typeof v === 'object'
142
+ && typeof v.x402Version === 'number'
143
+ && Array.isArray(v.accepts);
144
+ }
126
145
  static async discover(opts) {
127
146
  const response = await X402Manager.httpRequest(opts);
128
- if (response.status !== 402) {
129
- return { status: response.status, x402: false, rawBody: X402Manager.tryParseJson(response.bodyText) ?? response.bodyText };
147
+ // 1. Canonical Algorand x402 V2 transport: requirements arrive in the
148
+ // `payment-required` HTTP header as base64-encoded JSON. The body is
149
+ // usually empty (`{}`). Check this first.
150
+ const headerPayload = response.headers['payment-required'];
151
+ if (headerPayload) {
152
+ const decoded = X402Manager.decodeBase64Json(headerPayload);
153
+ if (X402Manager.isValidPaymentRequiredResponse(decoded)) {
154
+ return { status: response.status, x402: true, accepts: decoded.accepts, x402Version: decoded.x402Version };
155
+ }
130
156
  }
131
- const parsed = X402Manager.tryParseJson(response.bodyText);
132
- if (!parsed ||
133
- typeof parsed !== 'object' ||
134
- typeof parsed.x402Version !== 'number' ||
135
- !Array.isArray(parsed.accepts)) {
157
+ // 2. Fallback transport (some x402 servers put requirements in the 402 body).
158
+ if (response.status === 402) {
159
+ const parsed = X402Manager.tryParseJson(response.bodyText);
160
+ if (X402Manager.isValidPaymentRequiredResponse(parsed)) {
161
+ return { status: 402, x402: true, accepts: parsed.accepts, x402Version: parsed.x402Version };
162
+ }
136
163
  return { status: 402, x402: false, rawBody: parsed ?? response.bodyText };
137
164
  }
138
- const body = parsed;
139
- return { status: 402, x402: true, accepts: body.accepts, x402Version: body.x402Version };
165
+ // 3. Not an x402-protected response at all.
166
+ return { status: response.status, x402: false, rawBody: X402Manager.tryParseJson(response.bodyText) ?? response.bodyText };
140
167
  }
141
168
  static selectRequirement(accepts, preferredNetwork, maxAmountPerRequest) {
142
169
  // Map each accepts entry to its MCP network (or null if non-Algorand).
@@ -146,10 +173,11 @@ export class X402Manager {
146
173
  if (ranked.length === 0) {
147
174
  throw new McpError(ErrorCode.InvalidRequest, `No payment requirement is satisfiable on Algorand. Endpoint accepts networks: [${accepts.map(r => r.network).join(', ')}]`);
148
175
  }
149
- const inBudget = (req) => maxAmountPerRequest === undefined || Number(req.maxAmountRequired) <= maxAmountPerRequest;
176
+ const amountOf = (req) => Number(X402Manager.getRequirementAmount(req));
177
+ const inBudget = (req) => maxAmountPerRequest === undefined || amountOf(req) <= maxAmountPerRequest;
150
178
  const affordable = ranked.filter(e => inBudget(e.req));
151
179
  if (affordable.length === 0) {
152
- throw new McpError(ErrorCode.InvalidRequest, `All Algorand payment requirements exceed maxAmountPerRequest=${maxAmountPerRequest}. Cheapest: ${Math.min(...ranked.map(e => Number(e.req.maxAmountRequired)))}.`);
180
+ throw new McpError(ErrorCode.InvalidRequest, `All Algorand payment requirements exceed maxAmountPerRequest=${maxAmountPerRequest}. Cheapest: ${Math.min(...ranked.map(e => amountOf(e.req)))}.`);
153
181
  }
154
182
  if (preferredNetwork) {
155
183
  const preferred = affordable.find(e => e.mcpNetwork === preferredNetwork);
@@ -157,7 +185,7 @@ export class X402Manager {
157
185
  return { requirement: preferred.req, mcpNetwork: preferred.mcpNetwork };
158
186
  }
159
187
  // No preference (or preference not matched): pick the cheapest affordable.
160
- const chosen = affordable.reduce((a, b) => Number(a.req.maxAmountRequired) <= Number(b.req.maxAmountRequired) ? a : b);
188
+ const chosen = affordable.reduce((a, b) => amountOf(a.req) <= amountOf(b.req) ? a : b);
161
189
  return { requirement: chosen.req, mcpNetwork: chosen.mcpNetwork };
162
190
  }
163
191
  static async buildPaymentHeader(requirement, mcpNetwork) {
@@ -165,9 +193,10 @@ export class X402Manager {
165
193
  if (!feePayer || typeof feePayer !== 'string') {
166
194
  throw new McpError(ErrorCode.InvalidRequest, 'Payment requirement is missing `extra.feePayer` (facilitator address).');
167
195
  }
168
- const amount = Number(requirement.maxAmountRequired);
196
+ const amountStr = X402Manager.getRequirementAmount(requirement);
197
+ const amount = Number(amountStr);
169
198
  if (!Number.isFinite(amount) || amount < 0) {
170
- throw new McpError(ErrorCode.InvalidRequest, `Invalid maxAmountRequired: ${requirement.maxAmountRequired}`);
199
+ throw new McpError(ErrorCode.InvalidRequest, `Invalid payment amount: ${amountStr ?? '(missing `amount` / `maxAmountRequired` field)'}`);
171
200
  }
172
201
  const account = await WalletManager.getActiveWalletAccount();
173
202
  const sk = await WalletManager.getActiveWalletSecretKey();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goplausible/algorand-mcp",
3
- "version": "4.3.1",
3
+ "version": "4.3.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },