UNPKG

@chainbound/payflow-sdk

Version:
1 lines 19.4 kB
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport { McpServer, type RegisteredTool, type ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type {\n CallToolResult,\n Implementation,\n ServerNotification,\n ServerRequest,\n ToolAnnotations,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { z, type ZodRawShape, type ZodTypeAny } from 'zod';\nimport debug from 'debug';\nimport type { Network, PaymentPayload, PaymentRequirements, SettleResponse, VerifyResponse } from 'x402/types';\nimport { ErrorReasons } from 'x402/types';\nimport { useFacilitator } from 'x402/verify';\nimport { createFacilitatorConfig } from '@coinbase/x402';\nimport { exact } from 'x402/schemes';\nimport { findMatchingPaymentRequirements, processPriceToAtomicAmount } from 'x402/shared';\n\n/**\n * Options for a paid tool.\n */\nexport type PaymentOptions = {\n /**\n * The price of the tool in the given asset.\n */\n price: number;\n /**\n * The recipient of the payment.\n */\n recipient: string;\n /**\n * The asset to use for the tool.\n */\n asset?: string;\n /**\n * The network to settle the payment on.\n */\n network?: number | string;\n};\n\ntype PayflowOptions = {\n x402?: {\n /**\n * The version of the x402 protocol to use.\n */\n version?: number;\n /**\n * The API key ID for the Coinbase X402 facilitator.\n */\n keyId?: string;\n /**\n * The API key secret for the Coinbase X402 facilitator.\n */\n keySecret?: string;\n };\n};\n\nexport type PayflowMcpServerOptions = ServerOptions & PayflowOptions;\n\n/**\n * An extended MCP server that supports paid tools.\n */\nexport class PayflowMcpServer extends McpServer {\n private readonly log: debug.Debugger;\n private readonly options: PayflowOptions;\n private readonly verify: (\n payload: PaymentPayload,\n paymentRequirements: PaymentRequirements\n ) => Promise<VerifyResponse>;\n private readonly settle: (\n payload: PaymentPayload,\n paymentRequirements: PaymentRequirements\n ) => Promise<SettleResponse>;\n /**\n * Creates a new PayflowMcpServer instance.\n *\n * @param serverInfo - The server implementation details including name and version\n * @param options - Optional configuration for the server including x402 payment settings\n *\n * @example\n * ```typescript\n * const server = new PayflowMcpServer({\n * name: 'my-paid-server',\n * version: '1.0.0'\n * }, {\n * x402: {\n * version: 1,\n * keyId: 'your-api-key-id',\n * keySecret: 'your-api-key-secret'\n * }\n * });\n * ```\n */\n constructor(serverInfo: Implementation, options?: PayflowMcpServerOptions) {\n super(serverInfo, options);\n this.log = debug('payflow-sdk');\n\n // Define default x402 options\n const defaultX402Options = {\n version: 1,\n keyId: process.env.CDP_API_KEY_ID,\n keySecret: process.env.CDP_API_KEY_SECRET,\n };\n\n // Merge user options with defaults\n this.options = {\n x402: {\n ...defaultX402Options,\n ...(options?.x402 || {}),\n },\n };\n\n const { verify, settle } = useFacilitator(\n createFacilitatorConfig(this.options.x402?.keyId, this.options.x402?.keySecret)\n );\n this.verify = verify;\n this.settle = settle;\n }\n\n private generateRequirements(\n payload: PaymentPayload,\n tool: string,\n price: number,\n recipient: string,\n network: Network\n ): PaymentRequirements {\n // TODO: support other assets\n const atomicAmountForAsset = processPriceToAtomicAmount(price, network);\n if ('error' in atomicAmountForAsset) {\n throw new Error(atomicAmountForAsset.error);\n }\n\n const { maxAmountRequired, asset } = atomicAmountForAsset;\n\n const paymentRequirements: PaymentRequirements[] = [\n {\n scheme: 'exact',\n network: network,\n maxAmountRequired,\n resource: tool,\n // TODO: support description through arguments\n description: '',\n // TODO: support other mime types through arguments\n mimeType: 'text/plain',\n payTo: recipient,\n maxTimeoutSeconds: 60,\n asset: asset.address,\n outputSchema: undefined,\n extra: {\n name: asset.eip712.name,\n version: asset.eip712.version,\n },\n },\n ];\n\n // NOTE: This ONLY finds matches on the scheme and the network. Nothing else!\n const selectedPaymentRequirements = findMatchingPaymentRequirements(paymentRequirements, payload);\n if (!selectedPaymentRequirements) {\n throw new Error('No matching payment requirements found');\n }\n\n return selectedPaymentRequirements;\n }\n\n private async verifyPayment(payload: PaymentPayload, requirements: PaymentRequirements) {\n const verifyResponse = await this.verify(payload, requirements);\n\n if (!verifyResponse.isValid) {\n throw enrichVerificationError(requirements, verifyResponse.invalidReason);\n }\n }\n\n private async settlePayment(\n payload: PaymentPayload,\n paymentRequirements: PaymentRequirements\n ): Promise<SettleResponse> {\n const settleResponse = await this.settle(payload, paymentRequirements);\n if (!settleResponse.success) {\n throw new Error(settleResponse.errorReason);\n }\n\n return settleResponse;\n }\n\n private createPaidCallback<ArgsSchema extends ZodTypeAny>(\n name: string,\n price: number,\n recipient: string,\n schema: ArgsSchema,\n cb: ToolCallback\n ): (\n args: z.infer<ArgsSchema> & { payment: string },\n context: RequestHandlerExtra<ServerRequest, ServerNotification>\n ) => Promise<any> {\n return async (args, context) => {\n this.log('Called paid tool:', name, args);\n\n // Validate args using the schema (excluding payment)\n const { payment, ...toolArgs } = args;\n schema.parse(toolArgs); // throws if invalid\n\n // Step 0: Decode the payment.\n const payload = exact.evm.decodePayment(payment);\n payload.x402Version = this.options.x402?.version ?? 1;\n\n let requirements: PaymentRequirements;\n try {\n requirements = this.generateRequirements(payload, name, price, recipient, 'base');\n } catch (error) {\n this.log('Error generating requirements:', error);\n return {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error), isError: true }],\n };\n }\n\n // Step 1: Verify the payment.\n try {\n await this.verifyPayment(payload, requirements);\n } catch (error) {\n this.log('Error verifying payment:', error);\n return {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error), isError: true }],\n };\n }\n\n // Step 2: Handle the tool call.\n let result: CallToolResult;\n try {\n if (cb.length > 1) {\n // Callback expects (args, context)\n result = await (cb as any)(toolArgs, context);\n } else {\n // Callback expects only (context)\n result = await cb(context);\n }\n } catch (error) {\n this.log('Error in tool call:', error);\n return {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error), isError: true }],\n };\n }\n\n // Step 3: Settle the payment.\n let settleResponse: SettleResponse;\n try {\n settleResponse = await this.settlePayment(payload, requirements);\n } catch (error) {\n this.log('Error settling payment:', error);\n return {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error), isError: true }],\n };\n }\n\n // Add the payment reference to the existing result.\n result.content.push({\n type: 'text',\n text: `Payment: ${settleResponse.transaction}`,\n });\n\n // Step 4: Return the result.\n return result;\n };\n }\n\n // Overload signatures\n public paidTool(name: string, options: PaymentOptions, cb: ToolCallback): RegisteredTool;\n public paidTool(name: string, description: string, options: PaymentOptions, cb: ToolCallback): RegisteredTool;\n public paidTool<Args extends ZodRawShape>(\n name: string,\n options: PaymentOptions,\n paramsSchema: Args,\n cb: ToolCallback<Args>\n ): RegisteredTool;\n public paidTool<Args extends ZodRawShape>(\n name: string,\n description: string,\n options: PaymentOptions,\n paramsSchema: Args,\n cb: ToolCallback<Args>\n ): RegisteredTool;\n public paidTool<Args extends ZodRawShape>(\n name: string,\n options: PaymentOptions,\n paramsSchema: Args,\n annotations: ToolAnnotations,\n cb: ToolCallback<Args>\n ): RegisteredTool;\n public paidTool<Args extends ZodRawShape>(\n name: string,\n description: string,\n options: PaymentOptions,\n paramsSchema: Args,\n annotations: ToolAnnotations,\n cb: ToolCallback<Args>\n ): RegisteredTool;\n\n /**\n * paidTool() implementation. Parses and processes the argument defined in the overloaded signatures.\n */\n public paidTool(name: string, ...rest: any[]): RegisteredTool {\n let description: string | undefined;\n let paymentOptions: PaymentOptions | undefined;\n let inputSchema: ZodRawShape | undefined;\n let annotations: ToolAnnotations | undefined;\n\n // Check for description as first argument\n if (rest.length >= 1 && typeof rest[0] === 'string') {\n description = rest.shift();\n }\n\n // Check for PaymentOptions object\n if (\n rest.length >= 1 &&\n typeof rest[0] === 'object' &&\n rest[0] !== null &&\n 'price' in rest[0] &&\n 'recipient' in rest[0]\n ) {\n paymentOptions = rest.shift() as PaymentOptions;\n }\n\n // Check for input schema or annotations\n if (rest.length >= 1) {\n if (this.isZodRawShape(rest[0])) {\n inputSchema = rest.shift();\n } else if (typeof rest[0] === 'object' && rest[0] !== null && 'title' in rest[0]) {\n annotations = rest.shift();\n }\n }\n\n // Check for annotations if not found yet\n if (rest.length >= 1 && typeof rest[0] === 'object' && rest[0] !== null && 'title' in rest[0]) {\n annotations = rest.shift();\n }\n\n if (rest.length > 1) {\n throw new Error('Too many arguments to paidTool()');\n }\n\n if (!paymentOptions) {\n throw new Error('PaymentOptions are required for paidTool()');\n }\n\n const cb = rest[0] as ToolCallback;\n\n // Create the schema that includes payment field\n const paymentSchema = z.object({\n payment: z.string().describe('The x402 payment header for the query.'),\n });\n\n let finalSchema: ZodRawShape;\n if (inputSchema) {\n // Merge the input schema with payment schema\n finalSchema = {\n ...inputSchema,\n payment: paymentSchema.shape.payment,\n };\n } else {\n finalSchema = paymentSchema.shape;\n }\n\n // Create the schema object for createPaidCallback\n const schemaForCallback = inputSchema ? z.object(inputSchema) : z.object({});\n\n // Create the paid callback\n const paidCb = this.createPaidCallback(\n name,\n paymentOptions.price,\n paymentOptions.recipient,\n schemaForCallback,\n cb\n ) as any;\n\n // Generate description\n const toolDescription = description || `Paid tool ${name}`;\n const fullDescription = `${toolDescription}\\nIMPORTANT: Payflow payment details:\\n-Price: ${paymentOptions.price}\\n- Recipient: ${paymentOptions.recipient}`;\n\n // Register the tool\n if (annotations) {\n return this.tool(name, fullDescription, finalSchema, annotations, paidCb);\n } else {\n return this.tool(name, fullDescription, finalSchema, paidCb);\n }\n }\n\n // Helper function from MCP SDK to check if an object is a Zod schema\n private isZodRawShape(obj: unknown): obj is ZodRawShape {\n if (typeof obj !== 'object' || obj === null) return false;\n const isEmptyObject = Object.keys(obj).length === 0;\n // Check if object is empty or at least one property is a ZodType instance\n return isEmptyObject || Object.values(obj as object).some(this.isZodTypeLike);\n }\n\n private isZodTypeLike(value: unknown): value is ZodTypeAny {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'parse' in value &&\n typeof value.parse === 'function' &&\n 'safeParse' in value &&\n typeof value.safeParse === 'function'\n );\n }\n}\n\nfunction enrichVerificationError(requirements: PaymentRequirements, reason?: (typeof ErrorReasons)[number]): Error {\n switch (reason) {\n case 'insufficient_funds':\n return new Error(`${reason}: Insufficient funds for payment. Required: ${requirements.maxAmountRequired}`);\n case 'invalid_exact_evm_payload_authorization_valid_after':\n return new Error(`${reason}: Invalid validAfter value in the payment`);\n case 'invalid_exact_evm_payload_authorization_valid_before':\n return new Error(`${reason}: Invalid validBefore value in the payment`);\n case 'invalid_exact_evm_payload_authorization_value':\n return new Error(\n `${reason}: The value of the payment is incorrect, it should be ${requirements.maxAmountRequired}`\n );\n case 'invalid_exact_evm_payload_signature':\n return new Error(`${reason}: Invalid signature in the payment`);\n case 'invalid_exact_evm_payload_recipient_mismatch':\n return new Error(`${reason}: Recipient mismatch in the payment. Pay to: ${requirements.payTo}`);\n case 'invalid_network':\n return new Error(`${reason}: Invalid network in the payment. Network: ${requirements.network}`);\n default:\n return new Error(`Payment verification failed: ${reason || 'unknown reason'}`);\n }\n}\n"],"mappings":"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","names":["index_exports","__export","PayflowMcpServer","__toCommonJS","import_mcp","import_zod","import_debug","import_types","import_verify","import_x402","import_schemes","import_shared","serverInfo","options","debug","defaultX402Options","verify","settle","payload","tool","price","recipient","network","atomicAmountForAsset","maxAmountRequired","asset","paymentRequirements","selectedPaymentRequirements","requirements","verifyResponse","enrichVerificationError","settleResponse","name","schema","cb","args","context","payment","toolArgs","error","result","rest","description","paymentOptions","inputSchema","annotations","paymentSchema","finalSchema","schemaForCallback","paidCb","fullDescription","obj","value","reason"]}