Skip to main content
Glama
tas1337

MCP A2A AP2 Food Delivery & Payments

by tas1337
stripe-agent.ts10.5 kB
// ============================================================================= // STRIPE AGENT (AP2 Payment Processor) // ============================================================================= // This is a MOCK Stripe agent that demonstrates AP2 mandate verification. // // In REAL WORLD: This would be Stripe's actual payment API. // In this DEMO: We verify mandates and simulate payment processing. // // KEY POINT: Stripe REFUSES payment without a valid mandate. // The mandate proves the user approved this specific payment. // ============================================================================= import { createHmac } from 'crypto'; import { createServer } from 'http'; import type { AgentCard, JSONRPCRequest, JSONRPCResponse, Mandate, PaymentResponse, PaymentStatusResponse, PaymentRequestWithMandate, RefundResponse, RefundRequestWithMandate, } from '../../src/types.js'; // ============================================================================= // CONFIG // ============================================================================= const REGISTRY_URL = `http://${process.env.REGISTRY_HOST || 'localhost'}:8004`; // User's secret key - Stripe knows this to verify mandates // In real world: This would be a public key verification, not shared secret const USER_SECRET = process.env.USER_SECRET || 'user-secret-key'; // In-memory payment store (real Stripe has a database) const payments = new Map<string, PaymentStatusResponse>(); // ============================================================================= // MANDATE VERIFICATION // ============================================================================= // Stripe MUST verify the mandate before processing any payment. // This is the core of AP2 security - no valid mandate = no payment. function createMandatePayload(mandate: Omit<Mandate, 'signature'>): string { return JSON.stringify({ userId: mandate.userId, agentId: mandate.agentId, action: mandate.action, maxAmount: mandate.maxAmount, currency: mandate.currency, orderId: mandate.orderId, issuedAt: mandate.issuedAt, expiresAt: mandate.expiresAt, }); } function verifyMandateSignature(mandate: Mandate): boolean { const payload = createMandatePayload(mandate); const expectedSignature = createHmac('sha256', USER_SECRET).update(payload).digest('hex'); return mandate.signature === expectedSignature; } function verifyMandate(mandate: Mandate, requestedAmount?: number): { valid: boolean; reason?: string } { // Check 1: Signature valid? (proves user signed it) if (!verifyMandateSignature(mandate)) { return { valid: false, reason: 'Invalid signature - mandate tampered!' }; } // Check 2: Not expired? (mandates are time-limited) if (new Date() > new Date(mandate.expiresAt)) { return { valid: false, reason: 'Mandate expired' }; } // Check 3: Amount within limit? (can't charge more than approved) if (requestedAmount !== undefined && requestedAmount > mandate.maxAmount) { return { valid: false, reason: `Amount ${requestedAmount} exceeds limit ${mandate.maxAmount}` }; } return { valid: true }; } // ============================================================================= // STRIPE AGENT CLASS // ============================================================================= class StripeAgent { private agentId: string; private name: string; private port: number; private httpServer: ReturnType<typeof createServer> | null = null; constructor(agentId: string, name: string, port: number) { this.agentId = agentId; this.name = name; this.port = port; } // --------------------------------------------------------------------------- // START SERVER & REGISTER WITH REGISTRY // --------------------------------------------------------------------------- async start(): Promise<void> { // Create HTTP server for JSON-RPC requests this.httpServer = createServer(async (req, res) => { if (req.method === 'POST') { const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(chunk); const body = JSON.parse(Buffer.concat(chunks).toString()); const response = await this.handleRequest(body); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(response)); } else { res.writeHead(404); res.end(); } }); this.httpServer.listen(this.port, '0.0.0.0', () => { console.log(`💳 ${this.name} running on port ${this.port}`); }); // Register with A2A registry so other agents can find us const card: AgentCard = { agentId: this.agentId, name: this.name, version: '1.0.0', endpoint: `http://stripe-agent:${this.port}`, capabilities: ['process_payment', 'check_payment_status', 'process_refund'], description: 'Stripe Payment Agent (AP2 with Mandates)', }; await fetch(`${REGISTRY_URL}/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(card), }); console.log(`💳 ${this.name} registered with registry`); } // --------------------------------------------------------------------------- // HANDLE JSON-RPC REQUEST // --------------------------------------------------------------------------- private async handleRequest(request: JSONRPCRequest): Promise<JSONRPCResponse> { const { method, params, id } = request; let result: unknown; try { switch (method) { case 'payment/process': result = await this.processPayment(params as unknown as PaymentRequestWithMandate); break; case 'payment/status': result = await this.checkPaymentStatus((params as unknown as { transactionId: string }).transactionId); break; case 'payment/refund': result = await this.processRefund(params as unknown as RefundRequestWithMandate); break; default: return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown method: ${method}` } }; } return { jsonrpc: '2.0', id, result }; } catch (error) { return { jsonrpc: '2.0', id, error: { code: -32000, message: error instanceof Error ? error.message : 'Unknown error' } }; } } // --------------------------------------------------------------------------- // PROCESS PAYMENT (requires mandate) // --------------------------------------------------------------------------- private async processPayment(request: PaymentRequestWithMandate): Promise<PaymentResponse> { // Step 1: Mandate required if (!request.mandate) { console.log('❌ REJECTED: No mandate'); throw new Error('Payment rejected: No mandate. AP2 requires authorization.'); } // Step 2: Verify mandate console.log('🔐 Verifying mandate...'); const verification = verifyMandate(request.mandate, request.amount); if (!verification.valid) { console.log(`❌ REJECTED: ${verification.reason}`); throw new Error(`Payment rejected: ${verification.reason}`); } console.log('✅ Mandate verified'); // Step 3: Process payment (simulated) const transactionId = `txn-${Date.now()}-${Math.random().toString(36).slice(2)}`; const timestamp = new Date().toISOString(); const success = Math.random() > 0.1; // 90% success rate const status = success ? 'completed' : 'failed'; // Store payment for status checks payments.set(transactionId, { transactionId, status, amount: request.amount, currency: request.currency, timestamp, paymentMethod: request.paymentMethod, orderId: request.orderId, }); console.log(`💳 Payment ${status}: ${transactionId}`); return { transactionId, status, amount: request.amount, currency: request.currency, timestamp, message: success ? 'Payment processed via Stripe (mandate verified)' : 'Payment failed', }; } // --------------------------------------------------------------------------- // CHECK PAYMENT STATUS (no mandate needed) // --------------------------------------------------------------------------- private async checkPaymentStatus(transactionId: string): Promise<PaymentStatusResponse> { const payment = payments.get(transactionId); if (!payment) throw new Error(`Payment not found: ${transactionId}`); return payment; } // --------------------------------------------------------------------------- // PROCESS REFUND (requires mandate) // --------------------------------------------------------------------------- private async processRefund(request: RefundRequestWithMandate): Promise<RefundResponse> { // Step 1: Mandate required for refunds too if (!request.mandate) { console.log('❌ REJECTED: No mandate for refund'); throw new Error('Refund rejected: No mandate.'); } // Step 2: Verify mandate console.log('🔐 Verifying refund mandate...'); const verification = verifyMandate(request.mandate, request.amount); if (!verification.valid) { console.log(`❌ REJECTED: ${verification.reason}`); throw new Error(`Refund rejected: ${verification.reason}`); } console.log('✅ Refund mandate verified'); // Step 3: Find original payment const payment = payments.get(request.transactionId); if (!payment) throw new Error(`Payment not found: ${request.transactionId}`); // Step 4: Process refund (simulated) const refundAmount = request.amount || payment.amount; const refundId = `refund-${Date.now()}-${Math.random().toString(36).slice(2)}`; console.log(`💸 Refund processed: ${refundId}`); return { refundId, transactionId: request.transactionId, amount: refundAmount, currency: payment.currency, status: 'completed', timestamp: new Date().toISOString(), message: `Refund of ${refundAmount} ${payment.currency} processed via Stripe`, }; } stop(): void { this.httpServer?.close(); } } // ============================================================================= // START AGENT // ============================================================================= const stripeAgent = new StripeAgent('stripe-agent-001', 'Stripe Agent', 8005); stripeAgent.start().catch((error) => { console.error('Failed to start Stripe agent:', error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tas1337/mcp-a2a-ap2-im-hungry'

If you have feedback or need assistance with the MCP directory API, please join our Discord server