# Authentication Source: https://docs.payo.dev/concepts/authentication Understanding API keys and tokens in Payo ## Overview Payo uses two types of credentials: | Credential | Used By | Purpose | | -------------------- | ------------- | ---------------------------- | | **Provider API Key** | MCP Providers | Authenticate charge requests | | **Agent Token** | AI Agents | Identify who's being charged | Both are secret keys that should never be shared publicly. ## Provider API Keys ### What They Are Provider keys authenticate your MCP server to the Payo platform. When your server charges an agent, the provider key proves it's really you. ### Format ``` sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx... ``` * `sk_` - Secret key prefix * `live_` - Production environment * 64 hex characters - Unique identifier ### Scope Provider keys have `provider:charge` scope, which allows: * Charging agents via `/api/v1/charge` * No other actions (can't manage agents, can't access admin features) ### Usage Pass as environment variable to your MCP server: ```bash theme={null} PAYO_API_KEY=sk_live_xxx node server.js ``` Used in SDK configuration: ```typescript theme={null} withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { ... } }); ``` ### Security Your provider key is a secret. Exposure allows anyone to charge agents on your behalf. Best practices: * Store in environment variables, never in code * Use secrets management (Vercel, Railway, AWS Secrets Manager) * Rotate periodically * Different keys for development vs production *** ## Agent Tokens ### What They Are Agent tokens identify which agent is making a tool call. When an agent calls a paid tool, their token is used to charge their account. ### Format Same as provider keys: ``` sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx... ``` ### Scope Agent tokens have `agent:connect` scope, which allows: * Being charged for tool calls * No other actions (can't charge others, can't access platform features) ### Usage **stdio transport** (Claude Desktop, Cursor): ```json theme={null} { "mcpServers": { "my-server": { "command": "node", "args": ["server.js"], "env": { "AGENT_TOKEN": "sk_live_agent_xxx" } } } } ``` **HTTP transport** (custom agents): ```typescript theme={null} const response = await fetch(url, { headers: { 'Authorization': 'Bearer sk_live_agent_xxx' } }); ``` ### Security Agent tokens control spending. Exposure allows anyone to spend your credits. Best practices: * Store securely (not in version control) * Use different tokens for different agents/environments * Monitor for unexpected charges * Rotate if compromised *** ## Key Storage Keys are stored securely: ``` Raw Key: sk_live_abc123xyz789... ↓ SHA256 Hash: 5d41402abc4b2a76b9719d911017c592... ↓ Stored in DB: key_hash = "5d41402abc..." key_prefix = "sk_live_" key_last4 = "9..." ``` The raw key is: * Shown once at creation * Never stored * Cannot be retrieved Only the hash is stored for validation. *** ## Key Validation When a key is used: ``` 1. Receive: sk_live_abc123xyz789... 2. Compute: SHA256("sk_live_abc123xyz789...") = "5d41402..." 3. Query: SELECT * FROM api_keys WHERE key_hash = "5d41402..." AND deleted_at IS NULL 4. Check: scopes @> '{required_scope}' 5. Return: { workspace_id, key_name } or error ``` This allows validation without ever storing the raw key. *** ## Scopes Each key type has a specific scope: | Key Type | Scope | Permissions | | -------- | ----------------- | ------------------------- | | Provider | `provider:charge` | Call `/api/v1/charge` | | Agent | `agent:connect` | Be charged for tool calls | Keys cannot perform actions outside their scope: * An agent token cannot charge other agents * A provider key cannot be charged *** ## Key Lifecycle ### Creation 1. User clicks "Create Key" in dashboard 2. System generates 64 random hex bytes 3. Key is hashed and stored 4. Raw key is displayed once ### Usage 1. Key is passed to API or MCP server 2. System hashes and looks up 3. Validates scope and status 4. Authorizes the action ### Rotation 1. Create a new key 2. Update your configuration 3. Delete the old key ### Deletion 1. User clicks "Delete" in dashboard 2. Key is soft-deleted (`deleted_at` set) 3. Key immediately stops working 4. Key cannot be recovered *** ## Multiple Keys You can create multiple keys for different purposes: **Agents:** * Separate keys per agent instance * Separate keys per environment * Easy to revoke one without affecting others **Providers:** * Separate keys per MCP server * Separate keys per environment * Independent rotation schedules *** ## Common Issues ### "Invalid provider key" * Key was deleted * Key doesn't exist * Key is from wrong account * Key was typed incorrectly **Fix**: Create a new key in dashboard. ### "Invalid agent token" * Token was deleted * Token doesn't exist * Token was typed incorrectly **Fix**: Agent must create new token. ### "Token missing" * `AGENT_TOKEN` env var not set * `Authorization` header not sent * Configuration syntax error **Fix**: Check agent configuration. # Managing API Keys Source: https://docs.payo.dev/guides/agent/api-keys Create, rotate, and manage your agent API keys ## Overview Agent API keys (tokens) authenticate your agent when calling paid MCP tools. Each key has scope `agent:connect` which allows connecting to MCP servers and being charged for tool calls. ## Creating Keys Navigate to **API Keys** in your dashboard sidebar. Click the **Create Key** button. Give it a descriptive name like "Production", "Development", or "Claude Desktop". The full key is shown **once**. Copy it and store it securely. You cannot retrieve a key after creation. If you lose it, create a new one and delete the old one. ## Key Format Agent keys follow this format: ``` sk_live_a1b2c3d4e5f6... ``` * `sk_` - Secret key prefix * `live_` - Production environment * `a1b2...` - 64 random hex characters The dashboard shows only the prefix and last 4 characters for identification: ``` sk_live_...x7z9 ``` ## Multiple Keys Create separate keys for different purposes: | Key Name | Use Case | | ---------------- | ------------------- | | `Production` | Your deployed agent | | `Development` | Local testing | | `Claude Desktop` | Personal use | | `Cursor` | IDE integration | Each key shares your account balance but can be revoked independently. ## Rotating Keys To rotate a key: Create a new key with the same name (add "v2" or date). Update your agent configurations to use the new key. Once all agents are updated, delete the old key. Deleting a key is immediate and permanent. Any agents still using it will receive `TOKEN_INVALID` errors. ## Deleting Keys To delete a key: 1. Go to **API Keys** 2. Find the key you want to delete 3. Click the **Delete** button (trash icon) 4. Confirm deletion Deleted keys cannot be recovered. ## Troubleshooting ### "TOKEN\_INVALID" Error Your key may be: * Deleted from the dashboard * Incorrectly copied (missing characters) * From a different account **Fix**: Create a new key and update your configuration. ### "TOKEN\_MISSING" Error The MCP server didn't receive your token. Check: * `AGENT_TOKEN` environment variable is set * HTTP `Authorization` header is being sent * No typos in your configuration See [Configuring Your Agent](/guides/agent/configuration) for setup help. # Configuring Your Agent Source: https://docs.payo.dev/guides/agent/configuration Set up your MCP client to use Payo authentication ## Overview Payo-enabled MCP servers need your agent token to process payments. How you pass this token depends on the **transport** your MCP client uses. ## Transport Methods | Transport | Token Method | Common Clients | | --------- | -------------------- | ----------------------------------- | | **stdio** | Environment variable | Claude Code, Claude Desktop, Cursor | | **HTTP** | Authorization header | Custom agents, web apps | ## stdio Transport Most MCP clients spawn MCP servers as subprocesses. Pass your token as an environment variable. Use the Claude CLI to add MCP servers with your token. **For HTTP servers:** ```bash theme={null} claude mcp add weather-api --transport http https://mcp.example.com/mcp \ --header "Authorization: Bearer sk_live_your_token_here" ``` **For stdio servers:** ```bash theme={null} claude mcp add weather-api -- npx @example/weather-mcp ``` Then set the environment variable: ```bash theme={null} export AGENT_TOKEN=sk_live_your_token_here ``` Or add it to your shell profile (`~/.bashrc`, `~/.zshrc`). Edit your Claude Desktop config: **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` ```json claude_desktop_config.json theme={null} { "mcpServers": { "weather-api": { "command": "npx", "args": ["-y", "@example/weather-mcp"], "env": { "AGENT_TOKEN": "sk_live_your_token_here" } } } } ``` Restart Claude Desktop after editing the config file. Edit your Cursor MCP config: **macOS**: `~/.cursor/mcp.json` **Windows**: `%USERPROFILE%\.cursor\mcp.json` ```json mcp.json theme={null} { "mcpServers": { "weather-api": { "command": "npx", "args": ["-y", "@example/weather-mcp"], "env": { "AGENT_TOKEN": "sk_live_your_token_here" } } } } ``` When running MCP servers directly: ```bash theme={null} AGENT_TOKEN=sk_live_your_token_here npx @example/weather-mcp ``` Or export first: ```bash theme={null} export AGENT_TOKEN=sk_live_your_token_here npx @example/weather-mcp ``` ## HTTP Transport If your agent connects to MCP servers over HTTP, pass the token in the Authorization header. ```typescript theme={null} const response = await fetch('https://mcp.example.com/mcp', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer sk_live_your_token_here' }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params: { name: 'get_weather', arguments: { city: 'New York' } }, id: 1 }) }); ``` ### Using MCP Client Libraries If you're using an MCP client library, configure the auth header: ```typescript theme={null} import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; const transport = new StreamableHTTPClientTransport( new URL('https://mcp.example.com/mcp'), { requestInit: { headers: { 'Authorization': 'Bearer sk_live_your_token_here' } } } ); const client = new Client({ name: 'my-agent', version: '1.0.0' }); await client.connect(transport); ``` ## Multiple Servers You can use the same token for multiple MCP servers: ```json theme={null} { "mcpServers": { "weather": { "command": "npx", "args": ["-y", "@example/weather-mcp"], "env": { "AGENT_TOKEN": "sk_live_abc123" } }, "data": { "command": "npx", "args": ["-y", "@example/data-mcp"], "env": { "AGENT_TOKEN": "sk_live_abc123" } } } } ``` All charges go to the same account balance. ## Verifying Your Setup To verify your token is configured correctly: 1. Call a free tool first (if available) to confirm connectivity 2. Call a paid tool and check your wallet for the charge 3. If you see `TOKEN_MISSING`, your token isn't reaching the server ## Troubleshooting The server didn't receive your token. Check: * Environment variable name is exactly `AGENT_TOKEN` * Value includes the full key (starts with `sk_live_`) * Config file syntax is valid JSON * You restarted your MCP client after changes The token was received but is invalid. Check: * Key wasn't deleted from your dashboard * No typos or missing characters * Key is from the correct Payo account Your token is valid but you need more credits. Check: * Your current balance in the Wallet page * Deposit more funds (or contact [cheng@payo.dev](mailto:cheng@payo.dev) during beta) This isn't a Payo error. Check: * The MCP server URL/command is correct * Server is running and accessible * No firewall blocking the connection # Deposits & Balance Source: https://docs.payo.dev/guides/agent/deposits Add funds and manage your agent balance ## Overview Your **credits balance** is the pool of funds your agents can spend on paid MCP tools. When a tool is called, its price is deducted from your balance. ## Viewing Your Balance Go to **Wallet** in your dashboard to see: * **Current balance** - Available credits in USD * **Recent transactions** - Charges and deposits * **Spending trends** - Usage over time ## Adding Funds Deposits are coming soon. During beta, contact [cheng@payo.dev](mailto:cheng@payo.dev) to add credits to your account. Once available, depositing will work like this: Navigate to **Wallet** in your dashboard. Click the **Deposit** button. Enter the USD amount you want to deposit (minimum \$5). Pay via credit card or bank transfer. Credits are added instantly after payment confirmation. ## Understanding Charges Each tool call deducts its price from your balance: | Tool | Price | 100 calls | | ----------------- | ------ | --------- | | `get_weather` | \$0.01 | \$1.00 | | `analyze_image` | \$0.05 | \$5.00 | | `generate_report` | \$0.10 | \$10.00 | Free tools (price = \$0) don't affect your balance. ## Transaction History Your wallet shows all transactions: ``` -$0.05 get_forecast WeatherAPI 2 min ago -$0.01 get_weather WeatherAPI 5 min ago -$0.02 analyze_sentiment TextAPI 1 hour ago +$50.00 Deposit - Yesterday ``` Each entry includes: * **Amount** - Positive for deposits, negative for charges * **Tool/Type** - What the charge was for * **Provider** - Who received the payment * **Time** - When it occurred ## Low Balance Warnings When your balance drops below \$1.00, you'll see a warning in your dashboard. If your balance reaches \$0, tool calls will fail with `INSUFFICIENT_BALANCE`. Set up a reminder to check your balance weekly, or deposit larger amounts less frequently. ## Budgeting To estimate monthly costs: 1. **Identify tools you use** - List the MCP tools your agent calls 2. **Check their prices** - Ask the provider or check their docs 3. **Estimate call volume** - How many calls per day/week? 4. **Calculate**: `calls × price = cost` **Example:** * 1,000 weather calls/day × $0.01 = $10/day = \$300/month * 100 analysis calls/day × $0.05 = $5/day = \$150/month * **Total**: \~\$450/month ## FAQs It can't. Payo checks your balance before each tool call. If you don't have enough, the call fails with `INSUFFICIENT_BALANCE`. Contact [cheng@payo.dev](mailto:cheng@payo.dev) for refund requests. Unused credits can typically be refunded within 30 days of deposit. No, credits don't expire. Your balance remains until spent. Coming soon. We're working on daily/monthly limits and alerts. # Error Handling Source: https://docs.payo.dev/guides/provider/error-handling Customize error messages for better agent experience ## Overview When a charge fails, the SDK returns an error to the agent instead of executing the tool. You can customize these messages to help agents resolve issues. ## Error Types | Error Code | When It Happens | | ---------------------- | --------------------------------- | | `TOKEN_MISSING` | No agent token provided | | `TOKEN_INVALID` | Token is invalid or deleted | | `INSUFFICIENT_BALANCE` | Agent doesn't have enough credits | | `PLATFORM_UNAVAILABLE` | Payo platform is down | | `CHARGE_FAILED` | Other charge failures | ## Error Verbosity Control how detailed error messages are: ```typescript theme={null} const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01 }, errorVerbosity: 'detailed', // or 'concise' }); ``` ### Detailed Errors (default) ``` Payment required. WeatherAPI has enabled Payo micropayments for 'get_weather' ($0.01). To use this tool: 1. Get your agent token at https://payo.dev/agent/api-keys 2. Add the token to your MCP client's Authorization header 3. Documentation: https://docs.payo.dev/quickstart-agent Questions? Contact cheng@payo.dev ``` ### Concise Errors ``` Payment required for 'get_weather' ($0.01). Get your token at https://payo.dev/agent/api-keys ``` ## Provider Name Set `providerName` to identify your service in error messages: ```typescript theme={null} const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'get_weather': 0.01 }, providerName: 'WeatherAPI', // Shown in errors }); ``` Error message: ``` Payment required. WeatherAPI has enabled Payo micropayments... ^^^^^^^^^^ ``` Without `providerName`, errors say "The provider has enabled...". ## Fail Open vs Fail Closed What happens when Payo is unavailable? ### Fail Closed (default) ```typescript theme={null} failOpen: false // default ``` If Payo is down, tools return `PLATFORM_UNAVAILABLE`. Agents can't use paid tools, but you don't give away free calls. ### Fail Open ```typescript theme={null} failOpen: true ``` If Payo is down, tools execute without charging. Agents can still use your tools, but you lose revenue during outages. `failOpen: true` means you provide free service during Payo outages. Only use this if availability is more important than revenue. ## Catching Errors in Your Tools The SDK handles payment errors before your tool runs. But if your tool itself throws an error, it passes through normally: ```typescript theme={null} paidServer.tool('my_tool', schema, async (args) => { // Payment already succeeded if we get here try { const result = await doSomething(args); return result; } catch (error) { // Your error, not a payment error throw new Error('Tool failed: ' + error.message); } }); ``` ## Custom Error Handling For advanced use cases, catch `PaymentError` in your server: ```typescript theme={null} import { PaymentError, PaymentErrorCode } from '@payo/mcp'; // The SDK throws PaymentError for payment failures // These are returned to the agent, not your tool // You can use PaymentError for type checking: if (error instanceof PaymentError) { console.log('Payment failed:', error.code, error.message); } ``` ## Logging Errors Enable debug logging to see error details: ```typescript theme={null} import { LogLevel } from '@payo/mcp'; const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01 }, logLevel: LogLevel.DEBUG, // See all SDK activity }); ``` Log levels: * `DEBUG` - Everything * `INFO` - Charges and important events * `WARN` - Warnings (missing tokens with `failOpen`) * `ERROR` - Errors only * `NONE` - Silent ## Example: Complete Configuration ```typescript theme={null} const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'premium_tool': 0.10, 'basic_tool': 0.01, 'free_tool': 0, }, // Error configuration providerName: 'MyAPI', errorVerbosity: 'detailed', // Availability failOpen: false, // Debugging logLevel: LogLevel.INFO, }); ``` ## Agent Experience Design your error handling with agents in mind: Errors should tell agents exactly what to do: "Get a token at...", "Deposit funds at...". Always mention the tool's price so agents know the cost. Include how to get help for persistent issues. Error messages shouldn't expose your implementation details or API key status. # Setting Prices Source: https://docs.payo.dev/guides/provider/pricing How to set and update prices for your MCP tools ## Overview You define prices per tool in your SDK configuration. Prices are in USD and can range from \$0 (free) to any amount. ```typescript theme={null} const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'simple_tool': 0.01, // $0.01 'complex_tool': 0.10, // $0.10 'premium_tool': 1.00, // $1.00 'free_tool': 0, // Free } }); ``` ## Cost-Based Pricing Price based on your costs to serve the tool: | Cost Factor | Example | | ------------------ | ----------------------------------------------------- | | API calls you make | If an external API costs \$0.001/call, charge \$0.002 | | Compute time | Heavy processing → higher price | | Data transfer | Large responses → higher price | A good formula: **your\_cost × 1.5-3x = price** ## Price Communication Help agents understand your pricing by including it in tool descriptions: ```typescript theme={null} paidServer.tool('analyze_document', { description: 'Analyze a document for key insights ($0.10 per call)', // ... }, handler); ``` ## Changing Prices To change prices, update the `pricing` config in your code and redeploy your server. New prices take effect immediately. Communicate price changes to your users. Agents may have budgets based on your old prices. # SDK Setup Source: https://docs.payo.dev/guides/provider/sdk-setup Install and configure the Payo SDK ## Installation Install the Payo SDK in your MCP server project: ```bash theme={null} npm install @payo/mcp ``` ```bash theme={null} pnpm add @payo/mcp ``` ```bash theme={null} yarn add @payo/mcp ``` ## Requirements * Node.js 18+ * `@modelcontextprotocol/sdk` (peer dependency) * A Payo provider API key ## Basic Setup Import `withPayments` and wrap your MCP server: ```typescript server.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { withPayments } from '@payo/mcp'; // 1. Create your MCP server const server = new McpServer({ name: 'my-api', version: '1.0.0' }); // 2. Wrap with payments const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'tool_a': 0.01, 'tool_b': 0.05, } }); // 3. Register tools (same as before) paidServer.tool('tool_a', { /* schema */ }, async (args) => { // Implementation }); // 4. Connect transport paidServer.connect(transport); ``` ## How It Works `withPayments()` returns your server with payment logic injected: 1. **Intercepts tool registration** - Wraps your tool handlers 2. **Checks pricing on each call** - Looks up the tool's price 3. **Charges before execution** - Calls Payo API for paid tools 4. **Executes your handler** - Only after successful charge Your tool code doesn't change at all. ## Environment Variables Set your API key as an environment variable: ```bash theme={null} # Development export PAYO_API_KEY=sk_live_your_key_here # Or in .env file PAYO_API_KEY=sk_live_your_key_here ``` Never commit your API key to version control. Use environment variables or secrets management. ## Production Deployment ### Vercel ```json vercel.json theme={null} { "env": { "PAYO_API_KEY": "@payo-api-key" } } ``` Add the secret via Vercel dashboard or CLI: ```bash theme={null} vercel secrets add payo-api-key sk_live_xxx ``` ### Railway Add environment variable in your Railway dashboard under **Variables**. ### Docker ```dockerfile Dockerfile theme={null} ENV PAYO_API_KEY="" ``` Pass at runtime: ```bash theme={null} docker run -e PAYO_API_KEY=sk_live_xxx my-mcp-server ``` ## TypeScript Support The SDK is fully typed. Import types if needed: ```typescript theme={null} import { withPayments, PaymentConfig, PricingConfig, PaymentError, PaymentErrorCode } from '@payo/mcp'; const config: PaymentConfig = { apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01 } }; ``` ## Verifying Setup Test your integration: 1. **Start your server locally** ```bash theme={null} PAYO_API_KEY=sk_live_xxx node server.js ``` 2. **Call a tool without a token** You should see a `TOKEN_MISSING` error (expected behavior) 3. **Call with a valid agent token** Set `AGENT_TOKEN` in your test client. The tool should execute and you should see a charge in your dashboard. ## Troubleshooting Ensure you've installed the package: ```bash theme={null} npm install @payo/mcp ``` The SDK requires an API key. Check: * `PAYO_API_KEY` environment variable is set * You're reading it correctly: `process.env.PAYO_API_KEY` * Verify the API key is a provider key (not agent) * Check you're calling paid tools (not free ones) * Ensure the agent token is valid ## Next Steps How to set and update prices All SDK options # How It Works Source: https://docs.payo.dev/how-it-works Technical overview of the Payo payment flow ## Architecture Overview Payo sits between AI agents and MCP tool providers, handling authentication, charging, and settlement. ```mermaid theme={null} flowchart TB subgraph Platform["Payo Platform"] Wallets["Wallets"] ChargeAPI["Charge API"] end subgraph Agents["Agents"] AgentDash["Agent Dashboard"] AgentApp["AI Agent"] end subgraph Providers["Providers"] ProvDash["Provider Dashboard"] MCPServer["MCP Server + SDK"] end AgentDash -->|deposit funds| Wallets ProvDash -->|withdraw earnings| Wallets AgentApp -->|tool call + token| MCPServer MCPServer -->|charge request| ChargeAPI ChargeAPI -->|validate & transfer| Wallets ``` ## The Charge Flow When an agent calls a paid tool, here's what happens: The agent's MCP client sends a `tools/call` request to the provider's server. The request includes the agent's token in the `Authorization` header. The `withPayments()` wrapper intercepts the request before the tool executes. The SDK looks up the tool's price. If price is `0`, the tool executes immediately (no charge). For paid tools, the SDK calls the Payo platform with the agent token, tool name, and price. Payo validates both keys, checks the agent's balance, and transfers funds from the agent to the provider. If the charge succeeds, the tool runs and returns its result to the agent. ## Atomic Transactions Every charge is atomic and uses double-entry bookkeeping: * The agent's credits are debited * The provider's earnings are credited * Both entries are recorded in the ledger This ensures money is never created or destroyed, every transaction is logged, and partial transfers are impossible. ## Authentication Payo uses two types of keys: | Key Type | Format | Used By | Purpose | | ---------------- | ------------- | --------- | ------------------------------ | | **Agent Token** | `sk_live_...` | Agents | Identifies who's being charged | | **Provider Key** | `sk_live_...` | Providers | Authenticates charge requests | Keys are stored securely as hashes. The raw key is shown once at creation and cannot be retrieved again. ## Transport Support The SDK extracts agent tokens from: ``` Authorization: Bearer sk_live_xxx ``` Standard HTTP header. Used by most MCP clients. ```bash theme={null} AGENT_TOKEN=sk_live_xxx npx my-mcp-server ``` Environment variable. Used when MCP runs as a subprocess. ## Error Handling When a charge fails, the tool **never executes**. This protects both parties: | Error | Meaning | Resolution | | ---------------------- | --------------------------------- | -------------------------------- | | `TOKEN_MISSING` | No agent token provided | Agent must configure their token | | `TOKEN_INVALID` | Token doesn't exist or is deleted | Agent must get a new token | | `INSUFFICIENT_BALANCE` | Agent doesn't have enough credits | Agent must deposit more funds | | `PLATFORM_UNAVAILABLE` | Payo platform is down | Retry later (or use `failOpen`) | ## Security * **Charge-before-execute**: Tools only run after successful payment * **Scoped keys**: Agent and provider keys have separate permissions * **No stored secrets**: Raw keys are never stored, only secure hashes # What is Payo? Source: https://docs.payo.dev/introduction The payment rail for AI agents and MCP tool providers Payo enables **micropayments between AI agents and MCP tool providers**. Agents pay per tool call, providers earn revenue automatically. Learn how to enable your agent to use paid MCP tools Learn how to monetize your MCP tools ## The Problem MCP (Model Context Protocol) lets AI agents use external tools. But there's no built-in way for providers to charge for their tools, or for agents to pay. **For providers:** You've built valuable MCP tools but can't monetize them. **For agents:** You want to use premium tools but there's no payment mechanism. ## How Payo Works Add `withPayments()` from the Payo SDK to enable charging. Define prices per tool. Sign up at payo.dev, deposit credits, and get an agent token. When an agent calls a paid tool, Payo automatically charges their balance and credits the provider. ```mermaid theme={null} sequenceDiagram participant Agent participant MCP Server (SDK) participant Payo Platform Agent->>MCP Server (SDK): Tool call + token MCP Server (SDK)->>Payo Platform: Charge request Payo Platform-->>MCP Server (SDK): Charge success MCP Server (SDK)->>MCP Server (SDK): Execute tool MCP Server (SDK)-->>Agent: Return result ``` ## Key Concepts A secret key (`sk_live_...`) that identifies the agent making tool calls. Agents pass this token when calling MCP tools. Each call is charged to the agent's balance. A secret key that providers use to authenticate with the Payo platform. Used server-side in the SDK configuration. Providers set a USD price for each tool. Example: `{ "get_weather": 0.01, "analyze_image": 0.05 }`. Free tools can have price `0`. Agents have a **credits balance** (deposited funds). Providers have an **earnings balance** (revenue earned). Payo handles the transfer automatically. ## Next Steps Get started in 5 minutes Start earning in 10 minutes Deep dive into the architecture Full API documentation # Quickstart: Agents Source: https://docs.payo.dev/quickstart-agent Enable your AI agent to use paid MCP tools in 5 minutes **Prerequisites**: An AI agent that uses MCP tools (Claude Code, Claude Desktop, Cursor, or custom agent) ## 1. Create an Account Visit [payo.dev](https://payo.dev) and click **Get Started**. Authenticate using your Google account. Choose **Agent** to access the agent dashboard. ## 2. Create an API Key On your first visit, you'll be prompted to create your first API key. Give it a name like "Production Agent" or "Claude Code". Copy the key immediately. It starts with `sk_live_` and **won't be shown again**. Store your key securely. If you lose it, you'll need to create a new one. ## 3. Deposit Funds Go to **Wallet** in the sidebar. Click the **Deposit** button to add credits to your account. Deposits are coming soon. During beta, contact [cheng@payo.dev](mailto:cheng@payo.dev) for credits. ## 4. Configure Your Agent Configure your MCP client to pass the agent token when connecting to paid MCP servers. Use the Claude CLI to add MCP servers with your token: ```bash theme={null} claude mcp add weather-api --transport http https://mcp.example.com/mcp \ --header "Authorization: Bearer sk_live_your_token_here" ``` For servers using stdio transport: ```bash theme={null} claude mcp add weather-api -- npx @example/weather-mcp ``` Then set the environment variable in your Claude Code settings or export it: ```bash theme={null} export AGENT_TOKEN=sk_live_your_token_here ``` Edit your Claude Desktop config file: **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` ```json claude_desktop_config.json theme={null} { "mcpServers": { "weather-api": { "command": "npx", "args": ["-y", "@example/weather-mcp"], "env": { "AGENT_TOKEN": "sk_live_your_token_here" } } } } ``` The `AGENT_TOKEN` environment variable is passed to the MCP server, which the Payo SDK reads for authentication. Edit your Cursor MCP config: **macOS**: `~/.cursor/mcp.json` **Windows**: `%USERPROFILE%\.cursor\mcp.json` ```json mcp.json theme={null} { "mcpServers": { "weather-api": { "command": "npx", "args": ["-y", "@example/weather-mcp"], "env": { "AGENT_TOKEN": "sk_live_your_token_here" } } } } ``` If your agent connects to MCP servers over HTTP, pass the token in the Authorization header: ```typescript theme={null} const response = await fetch('https://mcp.example.com/mcp', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer sk_live_your_token_here' }, body: JSON.stringify({ method: 'tools/call', params: { name: 'get_weather', arguments: { city: 'NYC' } } }) }); ``` ## 5. Make a Tool Call Now when your agent calls a paid tool, Payo will automatically: 1. Validate your token 2. Check your balance 3. Charge the tool's price 4. Execute the tool If the tool costs \$0.01 and you have \$10 in credits, you can make 1,000 calls. ## Monitoring Usage View your transaction history in the **Wallet** page. Each charge shows: * Tool name * Amount charged * Provider * Timestamp ## Error Handling If a tool call fails due to payment issues, you'll see one of these errors: | Error | Meaning | Fix | | ---------------------- | --------------------------- | -------------------------------- | | `TOKEN_MISSING` | Token not configured | Add `AGENT_TOKEN` to your config | | `TOKEN_INVALID` | Token is invalid or deleted | Create a new key at payo.dev | | `INSUFFICIENT_BALANCE` | Not enough credits | Deposit more funds | ## Next Steps Learn about different transport methods Create, rotate, and delete keys Track spending and top up credits Understand the payment flow # Quickstart: Providers Source: https://docs.payo.dev/quickstart-provider Monetize your MCP tools in 10 minutes **Prerequisites**: An existing MCP server built with the official `@modelcontextprotocol/sdk` package. Cloudflare MCP package (`@cloudflare/agents`) support coming soon. Want your MCP framework supported? Contact us at [cheng@payo.dev](mailto:cheng@payo.dev) ## 1. Create an Account Visit [payo.dev](https://payo.dev) and click **Get Started**. Authenticate using your Google account. Choose **Provider** to access the provider dashboard. ## 2. Create an API Key On your first visit, you'll be prompted to create your first API key. Give it a name like "Production Server". Copy the key immediately. It starts with `sk_live_` and **won't be shown again**. This key authenticates charge requests. Keep it secret and never expose it in client-side code. ## 3. Install the SDK ```bash theme={null} npm install @payo/mcp ``` ## 4. Wrap Your Server Import `withPayments` and wrap your MCP server: ```typescript server.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { withPayments } from '@payo/mcp'; // Create your MCP server as usual const server = new McpServer({ name: 'weather-api', version: '1.0.0' }); // Wrap with payments const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY, pricing: { 'get_weather': 0.01, // $0.01 per call 'get_forecast': 0.05, // $0.05 per call 'get_location': 0, // Free } }); // Register tools as normal paidServer.tool('get_weather', { description: 'Get current weather for a city', inputSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } }, async ({ city }) => { // Your tool implementation return { temperature: 72, conditions: 'sunny' }; }); // Start the server paidServer.connect(transport); ``` ## 5. Set Environment Variable Store your API key as an environment variable: ```bash theme={null} export PAYO_API_KEY=sk_live_your_provider_key_here ``` For production, use your hosting provider's secrets management (Vercel, Railway, etc.). ## 6. Deploy Deploy your server as you normally would. The SDK handles payment automatically: 1. **Free tools** (price: 0) execute immediately 2. **Paid tools** charge the agent first, then execute 3. **No token?** Returns a helpful error message ## How Charging Works When an agent calls `get_weather`: 1. Agent sends a tool call request with their token 2. SDK intercepts and sees the price is \$0.01 3. SDK calls Payo to charge the agent 4. Payo validates and transfers \$0.01 from agent to provider 5. SDK executes your tool handler 6. Result returned to agent ## Example: Complete Server Here's a full example with multiple tools: ```typescript weather-server.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments } from '@payo/mcp'; const server = new McpServer({ name: 'weather-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'get_weather': 0.01, 'get_forecast': 0.05, 'get_alerts': 0.02, 'list_cities': 0, // Free discovery tool }, providerName: 'WeatherAPI', // Shown in error messages errorVerbosity: 'detailed', // Helpful errors for agents }); // Free tool - no payment required paidServer.tool('list_cities', { description: 'List supported cities (free)', }, async () => { return ['New York', 'Los Angeles', 'Chicago', 'Houston']; }); // Paid tools paidServer.tool('get_weather', { description: 'Get current weather ($0.01)', inputSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } }, async ({ city }) => { return { city, temp: 72, conditions: 'sunny' }; }); paidServer.tool('get_forecast', { description: 'Get 7-day forecast ($0.05)', inputSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } }, async ({ city }) => { return { city, forecast: ['sunny', 'cloudy', 'rain', 'sunny', 'sunny', 'cloudy', 'sunny'] }; }); // Connect via stdio const transport = new StdioServerTransport(); paidServer.connect(transport); ``` ## Monitoring Earnings View your earnings in the **Wallet** page: * Total earnings * Transaction history (tool, amount, agent, timestamp) * Withdrawal options (coming soon) ## Next Steps All configuration options How to set and update prices Customize error messages Full API documentation # Configuration Source: https://docs.payo.dev/sdk/configuration All SDK configuration options ## PaymentConfig The full configuration interface: ```typescript theme={null} interface PaymentConfig { // Required apiKey: string; pricing: PricingConfig; // Optional - Platform platformUrl?: string; // Optional - Behavior failOpen?: boolean; requireAuthForAllTools?: boolean; // Optional - Logging logLevel?: LogLevel; logger?: PaymentLogger; // Optional - Error Messages providerName?: string; errorVerbosity?: ErrorVerbosity; } ``` *** ## Required Options ### apiKey **Type**: `string` **Required**: Yes Your Payo provider API key. Get this from your [Payo dashboard](https://payo.dev). ```typescript theme={null} { apiKey: process.env.PAYO_API_KEY!, } ``` Never hardcode your API key. Use environment variables. ### pricing **Type**: `Record` **Required**: Yes Maps tool names to USD prices: ```typescript theme={null} { pricing: { 'get_weather': 0.01, // $0.01 'analyze_data': 0.05, // $0.05 'generate_report': 0.10, // $0.10 'free_tool': 0, // Free } } ``` Tools not in this object default to free (price = 0). *** ## Platform Options ### platformUrl **Type**: `string` **Default**: `"https://payo.dev"` The Payo platform URL. Only change this for testing. ```typescript theme={null} { platformUrl: 'https://staging.payo.dev', // Staging } ``` *** ## Behavior Options ### failOpen **Type**: `boolean` **Default**: `false` What happens when Payo is unreachable: | Value | Behavior | | ------- | -------------------------------------------- | | `false` | Tools fail with `PLATFORM_UNAVAILABLE` error | | `true` | Tools execute without charging | ```typescript theme={null} { failOpen: true, // Continue working during outages } ``` `failOpen: true` means free service during Payo outages. Use only if availability > revenue. ### requireAuthForAllTools **Type**: `boolean` **Default**: `false` Whether free tools also require an agent token: | Value | Behavior | | ------- | ------------------------------------------ | | `false` | Free tools work without a token | | `true` | All tools require a token (even free ones) | ```typescript theme={null} { requireAuthForAllTools: true, // Track all tool usage } ``` Use this to track usage of free tools or enforce authentication. *** ## Logging Options ### logLevel **Type**: `LogLevel` **Default**: `LogLevel.INFO` Controls logging verbosity: ```typescript theme={null} import { LogLevel } from '@payo/mcp'; { logLevel: LogLevel.DEBUG, // Most verbose } ``` | Level | Value | Description | | ------- | ----- | ------------------------------------------- | | `DEBUG` | 0 | Everything: requests, responses, timing | | `INFO` | 1 | Charges, connections, important events | | `WARN` | 2 | Warnings: missing tokens, failOpen triggers | | `ERROR` | 3 | Errors only | | `NONE` | 4 | Silent | ### logger **Type**: `PaymentLogger` **Default**: Built-in stderr logger Custom logger implementation: ```typescript theme={null} interface PaymentLogger { debug(message: string, meta?: Record): void; info(message: string, meta?: Record): void; warn(message: string, meta?: Record): void; error(message: string, meta?: Record): void; } ``` Example with Winston: ```typescript theme={null} import winston from 'winston'; const winstonLogger = winston.createLogger({ /* config */ }); { logger: { debug: (msg, meta) => winstonLogger.debug(msg, meta), info: (msg, meta) => winstonLogger.info(msg, meta), warn: (msg, meta) => winstonLogger.warn(msg, meta), error: (msg, meta) => winstonLogger.error(msg, meta), } } ``` *** ## Error Message Options ### providerName **Type**: `string` **Default**: `undefined` Your service name, shown in error messages: ```typescript theme={null} { providerName: 'WeatherAPI', } ``` Error message: ``` Payment required. WeatherAPI has enabled Payo micropayments... ``` Without `providerName`: ``` Payment required. The provider has enabled Payo micropayments... ``` ### errorVerbosity **Type**: `'detailed' | 'concise'` **Default**: `'detailed'` How much detail in error messages: ```typescript theme={null} { errorVerbosity: 'concise', } ``` **Detailed** (default): ``` Payment required. WeatherAPI has enabled Payo micropayments for 'get_weather' ($0.01). To use this tool: 1. Get your agent token at https://payo.dev/agent/api-keys 2. Add the token to your MCP client's Authorization header 3. Documentation: https://docs.payo.dev/quickstart-agent Questions? Contact cheng@payo.dev ``` **Concise**: ``` Payment required for 'get_weather' ($0.01). Get your token at https://payo.dev/agent/api-keys ``` *** ## Complete Example ```typescript theme={null} import { withPayments, LogLevel } from '@payo/mcp'; const paidServer = withPayments(server, { // Required apiKey: process.env.PAYO_API_KEY!, pricing: { 'premium_analysis': 0.25, 'basic_query': 0.01, 'list_options': 0, }, // Platform platformUrl: 'https://payo.dev', // Behavior failOpen: false, requireAuthForAllTools: false, // Logging logLevel: LogLevel.INFO, // Error messages providerName: 'DataAPI', errorVerbosity: 'detailed', }); ``` *** ## Environment-Based Configuration ```typescript theme={null} const isDev = process.env.NODE_ENV === 'development'; const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01 }, // Dev-friendly settings logLevel: isDev ? LogLevel.DEBUG : LogLevel.WARN, failOpen: isDev, // Don't block dev if Payo is down platformUrl: isDev ? 'https://staging.payo.dev' : 'https://payo.dev', }); ``` # Error Handling Source: https://docs.payo.dev/sdk/errors Payment error types and codes ## PaymentError All payment-related errors are instances of `PaymentError`: ```typescript theme={null} class PaymentError extends Error { code: PaymentErrorCode; details?: Record; } ``` ### Properties | Property | Type | Description | | --------- | ------------------ | ----------------------------- | | `message` | `string` | Human-readable error message | | `code` | `PaymentErrorCode` | Machine-readable error code | | `details` | `object` | Additional context (optional) | ### Usage ```typescript theme={null} import { PaymentError, PaymentErrorCode } from '@payo/mcp'; try { // Tool call that might fail } catch (error) { if (error instanceof PaymentError) { switch (error.code) { case PaymentErrorCode.INSUFFICIENT_BALANCE: console.log('Agent needs more credits'); break; case PaymentErrorCode.TOKEN_INVALID: console.log('Agent token is invalid'); break; } } } ``` *** ## Error Codes ```typescript theme={null} enum PaymentErrorCode { TOKEN_MISSING = 'TOKEN_MISSING', TOKEN_INVALID = 'TOKEN_INVALID', INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE', PLATFORM_UNAVAILABLE = 'PLATFORM_UNAVAILABLE', CHARGE_FAILED = 'CHARGE_FAILED', } ``` ### TOKEN\_MISSING **Meaning**: The agent didn't provide a token. **When**: Agent calls a paid tool without `Authorization` header or `AGENT_TOKEN` env var. **User Message** (detailed): ``` Payment required. [Provider] has enabled Payo micropayments for '[tool]' ($X.XX). To use this tool: 1. Get your agent token at https://payo.dev/agent/api-keys 2. Add the token to your MCP client's Authorization header 3. Documentation: https://docs.payo.dev/quickstart-agent Questions? Contact cheng@payo.dev ``` **Resolution**: Agent must configure their token. *** ### TOKEN\_INVALID **Meaning**: The token exists but is invalid. **When**: * Token was deleted from dashboard * Token is malformed * Token doesn't exist **User Message**: ``` Invalid or expired agent token. Get a new one at https://payo.dev/agent/api-keys ``` **Resolution**: Agent must create a new token. *** ### INSUFFICIENT\_BALANCE **Meaning**: Agent doesn't have enough credits. **When**: Agent's `credits_balance` is less than the tool's price. **User Message** (detailed): ``` Insufficient balance for '[tool]' (requires $X.XX, current balance: $Y.YY). To add funds: 1. Visit https://payo.dev/agent/wallet 2. Top up your account balance Documentation: https://docs.payo.dev/guides/agent/deposits ``` **Details**: ```typescript theme={null} { required: 0.05, // Price of the tool available: 0.02, // Agent's current balance tool: 'get_weather' } ``` **Resolution**: Agent must deposit more funds. *** ### PLATFORM\_UNAVAILABLE **Meaning**: Payo platform couldn't be reached. **When**: * Network error * Payo is down * Request timeout **User Message**: ``` Payment service temporarily unavailable. Please try again shortly. Status: https://status.payo.dev ``` **Behavior**: * With `failOpen: false` (default): Tool fails * With `failOpen: true`: Tool executes without charging **Resolution**: Retry later or wait for Payo status update. *** ### CHARGE\_FAILED **Meaning**: Charge was rejected for another reason. **When**: * Provider API key is invalid * Server-side validation error * Unexpected platform error **User Message**: ``` Payment processing failed. Please try again or contact support. ``` **Resolution**: Provider should check their API key. If persistent, contact Payo support. *** ## Error Flow ``` Agent calls paid tool │ ▼ Extract token ─────────────────────┐ │ │ │ No token │ ▼ │ TOKEN_MISSING ◄────────────────────┘ │ │ Has token ▼ Call /api/v1/charge │ ┌────┴────┬─────────┬──────────────┐ │ │ │ │ ▼ ▼ ▼ ▼ Success Invalid Balance Network Token Too Low Error │ │ │ │ │ ▼ ▼ ▼ │ TOKEN_INVALID INSUFFICIENT PLATFORM_ │ _BALANCE UNAVAILABLE │ ▼ Execute tool │ ▼ Return result ``` *** ## Handling Errors in Agents Agents receive errors as tool call failures. The error message tells them what to do: ```json theme={null} { "jsonrpc": "2.0", "id": 1, "error": { "code": -32603, "message": "Payment required for 'get_weather' ($0.01). Get your token at https://payo.dev/agent/api-keys" } } ``` Well-behaved agents should: 1. Parse the error message 2. Show it to the user or log it 3. Not retry immediately (except for `PLATFORM_UNAVAILABLE`) *** ## Logging Errors Enable logging to see error details: ```typescript theme={null} import { LogLevel } from '@payo/mcp'; const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01 }, logLevel: LogLevel.DEBUG, }); ``` Example log output: ``` [2024-01-15T10:30:45Z] [payo:info] Charge failed { "code": "INSUFFICIENT_BALANCE", "tool": "get_weather", "agentToken": "sk_l****xyz9", "required": 0.01 } ``` Note: Tokens are automatically masked in logs. # Examples Source: https://docs.payo.dev/sdk/examples Complete code examples for common scenarios ## Basic MCP Server A minimal paid MCP server: ```typescript basic-server.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments } from '@payo/mcp'; const server = new McpServer({ name: 'basic-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'hello': 0.01, } }); paidServer.tool('hello', { description: 'Say hello ($0.01)', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } }, async ({ name }) => { return { message: `Hello, ${name}!` }; }); const transport = new StdioServerTransport(); paidServer.connect(transport); ``` Run it: ```bash theme={null} PAYO_API_KEY=sk_live_xxx node basic-server.js ``` *** ## Mixed Free and Paid Tools Offer some tools for free to attract users: ```typescript mixed-pricing.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments } from '@payo/mcp'; const server = new McpServer({ name: 'data-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { // Free discovery tools 'list_datasets': 0, 'get_schema': 0, // Paid data access 'query_data': 0.05, 'export_csv': 0.10, 'run_analysis': 0.25, }, providerName: 'DataAPI', }); // Free: Let users explore paidServer.tool('list_datasets', { description: 'List available datasets (free)', }, async () => { return ['sales', 'customers', 'products', 'orders']; }); paidServer.tool('get_schema', { description: 'Get dataset schema (free)', inputSchema: { type: 'object', properties: { dataset: { type: 'string' } }, required: ['dataset'] } }, async ({ dataset }) => { const schemas = { sales: { columns: ['date', 'amount', 'product_id'] }, customers: { columns: ['id', 'name', 'email'] }, }; return schemas[dataset] || { error: 'Dataset not found' }; }); // Paid: Actual data access paidServer.tool('query_data', { description: 'Query a dataset ($0.05)', inputSchema: { type: 'object', properties: { dataset: { type: 'string' }, limit: { type: 'number' } }, required: ['dataset'] } }, async ({ dataset, limit = 10 }) => { // Your data query logic return { rows: [], count: 0 }; }); const transport = new StdioServerTransport(); paidServer.connect(transport); ``` *** ## HTTP Transport Server For web-deployed MCP servers: ```typescript http-server.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { withPayments } from '@payo/mcp'; import express from 'express'; const app = express(); app.use(express.json()); const server = new McpServer({ name: 'http-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'analyze': 0.05, } }); paidServer.tool('analyze', { description: 'Analyze text ($0.05)', inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } }, async ({ text }) => { return { wordCount: text.split(/\s+/).length, charCount: text.length, }; }); // Create transport for each request app.post('/mcp', async (req, res) => { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID(), }); // Pass request headers to transport (includes Authorization) await paidServer.connect(transport); await transport.handleRequest(req, res, req.body); }); app.listen(3000, () => { console.log('MCP server running on http://localhost:3000/mcp'); }); ``` Agents connect with: ```typescript theme={null} const response = await fetch('http://localhost:3000/mcp', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer sk_live_agent_token' }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params: { name: 'analyze', arguments: { text: 'Hello world' } }, id: 1 }) }); ``` *** ## Custom Error Messages Customize how errors appear to agents: ```typescript custom-errors.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments } from '@payo/mcp'; const server = new McpServer({ name: 'premium-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'premium_feature': 1.00, }, // Custom branding providerName: 'Premium Data Co', // Concise errors (less verbose) errorVerbosity: 'concise', }); // Error will say: // "Payment required for 'premium_feature' ($1.00). Get your token at https://payo.dev/agent/api-keys" // Instead of the multi-line detailed version ``` *** ## Debug Logging Enable verbose logging for development: ```typescript debug-logging.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments, LogLevel } from '@payo/mcp'; const server = new McpServer({ name: 'debug-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01, }, // Enable debug logging logLevel: LogLevel.DEBUG, }); // Logs will show: // [payo:debug] Initializing payment wrapper // [payo:debug] Registered pricing for 1 tools // [payo:debug] Tool call: my_tool // [payo:debug] Extracting agent token // [payo:debug] Token found: sk_l****xyz9 // [payo:debug] Charging $0.01 for my_tool // [payo:debug] Charge successful: txn_abc123 // [payo:info] Charged $0.01 for my_tool ``` *** ## Custom Logger Integrate with your logging system: ```typescript custom-logger.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments, LogLevel } from '@payo/mcp'; import pino from 'pino'; const pinoLogger = pino({ level: 'debug' }); const server = new McpServer({ name: 'logged-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01 }, logLevel: LogLevel.DEBUG, logger: { debug: (msg, meta) => pinoLogger.debug(meta, msg), info: (msg, meta) => pinoLogger.info(meta, msg), warn: (msg, meta) => pinoLogger.warn(meta, msg), error: (msg, meta) => pinoLogger.error(meta, msg), }, }); ``` *** ## Fail Open Mode Keep working during Payo outages: ```typescript fail-open.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments, LogLevel } from '@payo/mcp'; const server = new McpServer({ name: 'resilient-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'critical_tool': 0.05 }, // If Payo is down, run the tool anyway (no charge) failOpen: true, // Log warnings when this happens logLevel: LogLevel.WARN, }); // If Payo is unreachable: // [payo:warn] Platform unavailable, executing tool without charge (failOpen=true) ``` Only use `failOpen: true` if availability is critical and you accept free usage during outages. *** ## TypeScript Types Using SDK types for type safety: ```typescript typed-server.ts theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { withPayments, PaymentConfig, PricingConfig, LogLevel, } from '@payo/mcp'; // Typed pricing config const pricing: PricingConfig = { 'tool_a': 0.01, 'tool_b': 0.05, }; // Typed full config const config: PaymentConfig = { apiKey: process.env.PAYO_API_KEY!, pricing, logLevel: LogLevel.INFO, providerName: 'TypedAPI', errorVerbosity: 'detailed', failOpen: false, }; const server = new McpServer({ name: 'typed-api', version: '1.0.0' }); const paidServer = withPayments(server, config); ``` # SDK Overview Source: https://docs.payo.dev/sdk/overview Payo SDK for MCP server monetization The Payo SDK enables MCP tool providers to charge for tool calls. Wrap your server with `withPayments()` and the SDK handles authentication, charging, and error handling automatically. ## Installation ```bash theme={null} npm install @payo/mcp ``` ```bash theme={null} pnpm add @payo/mcp ``` ```bash theme={null} yarn add @payo/mcp ``` ## Requirements * **Node.js 18+** * **@modelcontextprotocol/sdk** - The MCP SDK (peer dependency) * **Provider API key** - From your Payo dashboard ## Quick Example ```typescript theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { withPayments } from '@payo/mcp'; const server = new McpServer({ name: 'my-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'paid_tool': 0.05, 'free_tool': 0, } }); paidServer.tool('paid_tool', { /* schema */ }, async (args) => { // Executes only after successful charge return { result: 'data' }; }); ``` ## Exports The SDK exports the following: ### Main Function | Export | Description | | ---------------- | -------------------------------------- | | `withPayments()` | Wraps an MCP server with payment logic | ### Types | Export | Description | | ------------------ | ------------------------------------------ | | `PaymentConfig` | Configuration options for `withPayments()` | | `PricingConfig` | Tool name → USD price mapping | | `PaymentError` | Error class for payment failures | | `PaymentErrorCode` | Enum of error codes | | `LogLevel` | Logging verbosity levels | | `ErrorVerbosity` | `'detailed'` or `'concise'` | ### Utilities | Export | Description | | ---------------- | ------------------------------------- | | `PlatformClient` | Direct access to Payo API (advanced) | | `SessionManager` | Token extraction utilities (advanced) | ## TypeScript Support The SDK is written in TypeScript with full type definitions: ```typescript theme={null} import type { PaymentConfig, PricingConfig, PaymentError, PaymentErrorCode } from '@payo/mcp'; ``` ## Architecture ```mermaid theme={null} flowchart TB subgraph Server["Your MCP Server"] Wrapper["withPayments() Wrapper"] Handlers["Your Tool Handlers"] end subgraph Wrapper Proxy["Server Proxy"] HandlerWrap["Handler Wrapper"] Client["Platform Client"] end Handlers --> Wrapper Client -->|charge request| Platform["Payo Platform"] ``` The SDK: 1. **Proxies** your server to intercept handler registration 2. **Wraps** tool handlers with payment logic 3. **Calls** the Payo platform API for charges 4. **Executes** your handler only after successful payment ## Next Steps Full API documentation All configuration options Error types and handling Complete code examples # withPayments() Source: https://docs.payo.dev/sdk/with-payments Main function to enable payments on your MCP server ## Signature ```typescript theme={null} function withPayments( mcpServer: T, config: PaymentConfig ): T ``` ## Parameters ### mcpServer Your MCP server instance created with `@modelcontextprotocol/sdk`: ```typescript theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; const server = new McpServer({ name: 'my-server', version: '1.0.0' }); ``` The SDK is compatible with any object that has: * A `.server` property (the internal low-level server) * The `.server.setRequestHandler()` method ### config A `PaymentConfig` object with your settings: ```typescript theme={null} interface PaymentConfig { // Required apiKey: string; pricing: PricingConfig; // Optional platformUrl?: string; failOpen?: boolean; requireAuthForAllTools?: boolean; logLevel?: LogLevel; logger?: PaymentLogger; providerName?: string; errorVerbosity?: ErrorVerbosity; } ``` See [Configuration](/sdk/configuration) for details on each option. ## Return Value Returns the **same server instance** with payment logic injected. The server's type is preserved for TypeScript compatibility. ```typescript theme={null} const paidServer = withPayments(server, config); // paidServer === server (same reference, modified) ``` ## Basic Usage ```typescript theme={null} import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { withPayments } from '@payo/mcp'; const server = new McpServer({ name: 'weather-api', version: '1.0.0' }); const paidServer = withPayments(server, { apiKey: process.env.PAYO_API_KEY!, pricing: { 'get_weather': 0.01, 'get_forecast': 0.05, } }); // Register tools on the wrapped server paidServer.tool('get_weather', { description: 'Get current weather', inputSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } }, async ({ city }) => { return { temperature: 72, city }; }); // Connect as normal paidServer.connect(transport); ``` ## How It Works `withPayments()` modifies your server by: 1. **Creating a Proxy** - Intercepts the `.server` property 2. **Wrapping setRequestHandler** - Catches tool handler registration 3. **Injecting Payment Logic** - Wraps `tools/call` and `tools/list` handlers When a tool is called: ``` 1. Agent calls tools/call with name="get_weather" 2. Wrapped handler looks up price: pricing["get_weather"] = 0.01 3. If price > 0: a. Extract agent token from request b. Call POST /api/v1/charge c. If charge succeeds → run your handler d. If charge fails → return error 4. If price = 0: run your handler directly ``` ## Pricing Configuration The `pricing` object maps tool names to USD prices: ```typescript theme={null} type PricingConfig = Record; ``` ```typescript theme={null} pricing: { 'expensive_tool': 1.00, // $1.00 per call 'standard_tool': 0.05, // $0.05 per call 'cheap_tool': 0.01, // $0.01 per call 'free_tool': 0, // Free (no charge) } ``` Tools not in the `pricing` object are treated as free by default. ## Validations `withPayments()` validates your configuration at startup: | Validation | Error | | ------------------------ | --------------------------------------- | | Missing `apiKey` | `"apiKey is required"` | | Empty `apiKey` | `"apiKey is required"` | | Invalid `pricing` values | `"Price must be a non-negative number"` | ## Error Handling If initialization fails, `withPayments()` throws synchronously: ```typescript theme={null} try { const paidServer = withPayments(server, { apiKey: '', // Invalid pricing: {} }); } catch (error) { console.error('SDK init failed:', error.message); } ``` Runtime payment errors (during tool calls) are handled by the wrapper and returned to agents as tool errors. ## Multiple Servers You can wrap multiple servers independently: ```typescript theme={null} const weatherServer = withPayments(new McpServer({ name: 'weather' }), { apiKey: process.env.PAYO_API_KEY!, pricing: { 'get_weather': 0.01 } }); const dataServer = withPayments(new McpServer({ name: 'data' }), { apiKey: process.env.PAYO_API_KEY!, // Same or different key pricing: { 'query_data': 0.10 } }); ``` ## Chaining with Other Wrappers If you have other wrappers/middleware, apply `withPayments()` **last** so payment happens first: ```typescript theme={null} let server = new McpServer({ name: 'api', version: '1.0.0' }); server = withLogging(server); // Your logging wrapper server = withPayments(server, { // Payment wrapper (outermost) apiKey: process.env.PAYO_API_KEY!, pricing: { 'my_tool': 0.01 } }); ``` ## TypeScript Full type inference is preserved: ```typescript theme={null} const server = new McpServer({ name: 'api', version: '1.0.0' }); const paidServer = withPayments(server, config); // TypeScript knows paidServer has all McpServer methods paidServer.tool('test', schema, handler); // ✓ typed paidServer.connect(transport); // ✓ typed ```