mcpay
Version:
SDK and CLI for MCPay functionality - MCP servers with payment capabilities
149 lines • 7.33 kB
JavaScript
import { createPaymentHeader } from 'x402/client';
import { SupportedEVMNetworks, SupportedSVMNetworks } from 'x402/types';
/**
* Wraps an MCP client with X402 payment capabilities
*/
export function withX402Client(client, x402Config) {
const { wallet: walletConfig, version } = x402Config;
const signer = { evm: walletConfig.evm, svm: walletConfig.svm };
const maxPaymentValue = x402Config.maxPaymentValue ?? BigInt(0.1 * 10 ** 6); // 0.10 USDC
const _listTools = client.listTools.bind(client);
// Wrap the original method to include payment information in the description
const listTools = async (params, options) => {
const toolsRes = await _listTools(params, options);
toolsRes.tools = toolsRes.tools.map((tool) => {
let description = tool.description;
if (tool.annotations?.paymentHint) {
const cost = tool.annotations?.paymentPriceUSD
? `$${tool.annotations?.paymentPriceUSD}`
: "an unknown amount";
let paymentDetails = ` (This is a paid tool, you will be charged ${cost} for its execution)`;
// Add detailed payment information if available
if (tool.annotations?.paymentNetworks && Array.isArray(tool.annotations.paymentNetworks)) {
const networks = tool.annotations.paymentNetworks;
if (networks.length > 0) {
paymentDetails += `\n\nPayment Details:`;
networks.forEach((net) => {
const amount = net.maxAmountRequired;
const symbol = net.asset.symbol || 'tokens';
const decimals = net.asset.decimals || 6;
const formattedAmount = (Number(amount) / Math.pow(10, decimals)).toFixed(decimals);
paymentDetails += `\n• ${net.network} (${net.type.toUpperCase()}): ${formattedAmount} ${symbol}`;
paymentDetails += `\n Recipient: ${net.recipient}`;
paymentDetails += `\n Asset: ${net.asset.address}`;
});
}
}
description += paymentDetails;
}
return {
...tool,
description
};
});
return toolsRes;
};
const _callTool = client.callTool.bind(client);
const callToolWithPayment = async (params, resultSchema, options) => {
// call the tool
const res = await _callTool(params, resultSchema, options);
// If it errored and returned accepts, we need to confirm payment
const maybeX402Error = res._meta?.["x402/error"];
if (res.isError &&
maybeX402Error &&
maybeX402Error.accepts &&
Array.isArray(maybeX402Error.accepts) &&
maybeX402Error.accepts.length > 0) {
const accepts = maybeX402Error.accepts;
const confirmationCallback = x402Config.confirmationCallback;
// Use the x402 confirmation callback if provided
// Build supported networks based on available signers using all x402-supported chains
const supportedNetworks = [];
if (signer.evm)
supportedNetworks.push(...SupportedEVMNetworks);
if (signer.svm)
supportedNetworks.push(...SupportedSVMNetworks);
// Resolve selection from confirmation callback (if provided)
let selectedReq;
if (confirmationCallback) {
const selection = await confirmationCallback(accepts);
if (selection === false) {
return {
isError: true,
content: [{ type: "text", text: "User declined payment" }]
};
}
// If boolean true, we just proceed to default selection below
if (selection !== true) {
if (typeof selection === 'number') {
const idx = selection;
if (Number.isInteger(idx) && idx >= 0 && idx < accepts.length) {
selectedReq = accepts[idx];
}
}
else if (typeof selection === 'object' && selection) {
if ('index' in selection) {
const idx = selection.index;
if (Number.isInteger(idx) && idx >= 0 && idx < accepts.length) {
selectedReq = accepts[idx];
}
}
else if ('network' in selection) {
const net = selection.network;
selectedReq = accepts.find((a) => a.network === net && a.scheme === 'exact');
}
else if ('requirement' in selection) {
const reqSel = selection.requirement;
selectedReq = accepts.find((a) => a.scheme === reqSel.scheme &&
a.network === reqSel.network &&
a.maxAmountRequired === reqSel.maxAmountRequired &&
'payTo' in a && 'payTo' in reqSel && String(a.payTo) === String(reqSel.payTo) &&
'asset' in a && 'asset' in reqSel && String(a.asset) === String(reqSel.asset)) ?? undefined;
}
}
}
}
// Default or fallback selection
const req = selectedReq ?? (accepts.find((a) => a?.scheme === "exact" && supportedNetworks.includes(a.network))
?? accepts.find((a) => a?.scheme === "exact")
?? accepts[0]);
if (!req || req.scheme !== "exact") {
return res;
}
const maxAmountRequired = BigInt(req.maxAmountRequired);
if (maxAmountRequired > maxPaymentValue) {
return {
isError: true,
content: [
{
type: "text",
text: `Payment exceeds client cap: ${maxAmountRequired} > ${maxPaymentValue}`
}
]
};
}
// Use x402/client to get the X-PAYMENT token with MultiNetworkSigner
const token = await createPaymentHeader(signer, version ?? 1, req);
// Call the tool with the payment token
const paidRes = await _callTool({
...params,
_meta: {
...(params._meta ?? {}),
"x402/payment": token
}
}, resultSchema, options);
return paidRes;
}
return res;
};
const _client = client;
_client.listTools = listTools;
Object.defineProperty(_client, "callTool", {
value: callToolWithPayment,
writable: false,
enumerable: false,
configurable: true
});
return _client;
}
//# sourceMappingURL=with-x402-client.js.map