UNPKG

@thorium-dev-group/x402-mcp-extension

Version:
205 lines 8.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.X402MCPClient = void 0; const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js"); const schemes_1 = require("x402/schemes"); const shared_1 = require("x402/shared"); const services_1 = require("../shared/services"); const error_codes_1 = require("../shared/error-codes"); const schemas_1 = require("../shared/schemas"); const viem_1 = require("viem"); const PaymentError_1 = require("../shared/errors/PaymentError"); const PaymentAuditStore_1 = require("./PaymentAuditStore"); class GuardrailsService { maxPaymentPerCall; whitelistedServers; log; constructor(opts) { this.maxPaymentPerCall = opts.guardrails?.maxPaymentPerCall; this.whitelistedServers = opts.guardrails?.whitelistedServers; this.log = opts.loggerFactory.createLogger(GuardrailsService.name); } async enforce({ amount, payTo }) { if (this.maxPaymentPerCall !== undefined && amount > this.maxPaymentPerCall) { this.log.error('Payment exceeds max per-call limit', { amountRequested: amount, maxPaymentPerCall: this.maxPaymentPerCall }); throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.GUARDRAIL_VIOLATION, `Payment exceeds max per-call limit: $${amount} > $${this.maxPaymentPerCall}`, { amount, maxPaymentPerCall: this.maxPaymentPerCall }); } if (this.whitelistedServers && !this.whitelistedServers.includes(payTo)) { this.log.error('Payment recipient is not whitelisted', { payTo, whitelistedServers: this.whitelistedServers }); throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.WHITELIST_VIOLATION, `Payment recipient ${payTo} is not whitelisted`, { payTo, whitelistedServers: this.whitelistedServers }); } } } class X402MCPClient extends index_js_1.Client { walletProvider; guardrailsService; auditStorage; logger; constructor(options) { super({ name: options.name, version: options.version, }, { capabilities: { ...options.capabilities, experimental: { x402: { paymentsEnabled: true, }, } } }); if (typeof options.wallet === 'string') { const { X402PKWalletProvider } = require('./x402PKWalletProvider'); this.walletProvider = new X402PKWalletProvider(options.wallet); } else { this.walletProvider = options.wallet; } if (!options.logFactory) { options.logFactory = new services_1.ConsoleLoggerFactory(); } if (options.logFactory) { this.logger = options.logFactory.createLogger(X402MCPClient.name); } else { const loggerFactory = new services_1.ConsoleLoggerFactory(); this.logger = loggerFactory.createLogger(X402MCPClient.name); } if (options.guardrails) { this.guardrailsService = new GuardrailsService({ guardrails: options.guardrails, loggerFactory: options.logFactory, }); } if (options.auditStorage) { this.auditStorage = new PaymentAuditStore_1.PaymentAuditStorage({ storage: options.auditStorage }); } this.setRequestHandler(schemas_1.PaymentRequiredRequestSchema, this.handlePaymentRequired.bind(this)); this.setNotificationHandler(schemas_1.PaymentResultNotificationSchema, this.handleNotification.bind(this)); } async connect(transport) { if (this.auditStorage) { const originalSend = transport.send.bind(transport); const serverUrl = transport.url; transport.send = async (message, options) => { const requestId = message.id; const method = message.method; const params = message.params; if (requestId && method) { await this.trackRequest(requestId, serverUrl, method, params); } try { const result = await originalSend(message, options); if (requestId) { await this.markRequestCompleted(requestId); } return result; } catch (error) { this.logger.error('Error sending request', { operation: 'send_request', error: error }); if (requestId) { await this.markRequestCompleted(requestId); } throw error; } }; } return super.connect(transport); } async trackRequest(requestId, serverId, method, params) { if (this.auditStorage) { await this.auditStorage.storePendingRequest({ requestId, serverId, method, params, }); } } async markRequestCompleted(requestId) { if (this.auditStorage) { await this.auditStorage.markRequestCompleted(requestId); } } async handlePaymentRequired(request) { const params = request.params; if (!params || !params.payTo || !params.maxAmountRequired || !params.network) { throw new Error('Invalid payment_required request: missing required fields'); } if (request.params.scheme !== 'exact') { throw new Error('Unsupported scheme: ' + request.params.scheme); } if (request.params.x402Version !== 1) { throw new Error('Unsupported x402 version: ' + request.params.x402Version); } const requestId = params.requestId; if (!requestId) { throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, 'Payment request missing requestId'); } let matchingRequest = null; if (this.auditStorage) { matchingRequest = await this.auditStorage.getPendingRequest(requestId); if (!matchingRequest) { throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, `Payment request for unknown payment: ${requestId}`); } this.logger.debug('Making payment for request:', matchingRequest, params); await this.auditStorage.updatePaymentStatus(requestId, 'pending', { completedAt: new Date(), }); } if (this.guardrailsService) { const atomicAmount = (0, shared_1.processPriceToAtomicAmount)(params.maxAmountRequired, params.network); if ('error' in atomicAmount) { this.logger.error('Failed to process payment amount', { operation: 'handle_payment_required', error: atomicAmount.error }); throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, `Failed to process payment amount: ${atomicAmount.error}`); } const usdAmount = parseFloat((0, viem_1.formatUnits)(BigInt(params.maxAmountRequired), atomicAmount.asset.decimals)); await this.guardrailsService.enforce({ amount: usdAmount, payTo: params.payTo, }); } const account = await this.walletProvider.createAccount(); const paymentPayload = await schemes_1.exact.evm.createPayment(account, params.x402Version, params); const response = { jsonrpc: '2.0', id: request.id, result: { payment: paymentPayload, }, }; return response; } async handleNotification(notification) { const params = notification.params; if (params.requestId && this.auditStorage) { const matchingRequest = await this.auditStorage.getPendingRequest(params.requestId); if (matchingRequest) { await this.auditStorage.updatePaymentStatus(matchingRequest.requestId, params.success ? 'completed' : 'failed', { transactionHash: params.transaction, payerAddress: params.payer, errorReason: params.errorReason, completedAt: new Date(), }); } } this.logger.debug('Received payment result notification:', notification); } } exports.X402MCPClient = X402MCPClient; //# sourceMappingURL=X402MCPClient.js.map