@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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
return { status:
|
|
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
|
|
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 =>
|
|
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) =>
|
|
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
|
|
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
|
|
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();
|