From 66b2a9a328f87cf2e2abd6f5d33e241c2d18116c Mon Sep 17 00:00:00 2001 From: "a.dacapo21" Date: Wed, 1 Apr 2026 12:02:44 +0300 Subject: [PATCH 1/4] docs: document x402 auto-payment, PAYMENT_SERVER alias, and new env vars --- README.md | 237 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 140 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 4590350..79b21dc 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ npx @indigoprotocol/indigo-mcp setup ``` This will: + 1. Ask which client you're using (Claude Desktop, Claude Code, Cursor, Windsurf) 2. Prompt for your Blockfrost API key 3. Automatically update your config file @@ -124,6 +125,7 @@ MCP_TRANSPORT=http PORT=3000 npx @indigoprotocol/indigo-mcp ``` This starts an HTTP server with: + - `POST /mcp` — MCP endpoint (Streamable HTTP with SSE) - `GET /health` — Health check @@ -144,6 +146,7 @@ notepad "$env:APPDATA\Claude\claude_desktop_config.json" ``` **Standard config:** + ```json { "mcpServers": { @@ -301,154 +304,161 @@ For any client that supports MCP over stdio, point it to the `npx @indigoprotoco ### Asset Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_assets` | Get all Indigo iAssets with prices and interest data | None | -| `get_asset` | Get details for a specific iAsset | `asset`: iUSD, iBTC, iETH, or iSOL | -| `get_asset_price` | Get the current price for a specific iAsset | `asset`: iUSD, iBTC, iETH, or iSOL | -| `get_ada_price` | Get the current ADA price in USD | None | -| `get_indy_price` | Get the current INDY token price in ADA and USD | None | +| Tool | Description | Parameters | +| ----------------- | ---------------------------------------------------- | ---------------------------------- | +| `get_assets` | Get all Indigo iAssets with prices and interest data | None | +| `get_asset` | Get details for a specific iAsset | `asset`: iUSD, iBTC, iETH, or iSOL | +| `get_asset_price` | Get the current price for a specific iAsset | `asset`: iUSD, iBTC, iETH, or iSOL | +| `get_ada_price` | Get the current ADA price in USD | None | +| `get_indy_price` | Get the current INDY token price in ADA and USD | None | ### CDP / Loan Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_all_cdps` | Get all CDPs/loans, optionally filtered by iAsset | `asset?`: iAsset filter; `limit?`: 1-500 (default 50); `offset?`: pagination offset | -| `get_cdps_by_owner` | Get CDPs for a specific owner | `owner`: payment key hash (56-char hex) or bech32 address | -| `get_cdps_by_address` | Get CDPs for a specific Cardano address | `address`: bech32 address (addr1... or addr_test1...) | -| `analyze_cdp_health` | Analyze collateral ratios and liquidation risk | `owner`: payment key hash or bech32 address | +| Tool | Description | Parameters | +| --------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `get_all_cdps` | Get all CDPs/loans, optionally filtered by iAsset | `asset?`: iAsset filter; `limit?`: 1-500 (default 50); `offset?`: pagination offset | +| `get_cdps_by_owner` | Get CDPs for a specific owner | `owner`: payment key hash (56-char hex) or bech32 address | +| `get_cdps_by_address` | Get CDPs for a specific Cardano address | `address`: bech32 address (addr1... or addr_test1...) | +| `analyze_cdp_health` | Analyze collateral ratios and liquidation risk | `owner`: payment key hash or bech32 address | ### CDP Write Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `open_cdp` | Open a new CDP position (returns unsigned CBOR tx) | `address`: bech32 address; `asset`: iUSD, iBTC, iETH, or iSOL; `collateralAmount`: lovelace; `mintAmount`: iAsset smallest unit | -| `deposit_cdp` | Deposit additional collateral into a CDP | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index; `amount`: lovelace | -| `withdraw_cdp` | Withdraw collateral from a CDP | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index; `amount`: lovelace | -| `close_cdp` | Close a CDP and reclaim collateral | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index | +| Tool | Description | Parameters | +| -------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `open_cdp` | Open a new CDP position (returns unsigned CBOR tx) | `address`: bech32 address; `asset`: iUSD, iBTC, iETH, or iSOL; `collateralAmount`: lovelace; `mintAmount`: iAsset smallest unit | +| `deposit_cdp` | Deposit additional collateral into a CDP | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index; `amount`: lovelace | +| `withdraw_cdp` | Withdraw collateral from a CDP | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index; `amount`: lovelace | +| `close_cdp` | Close a CDP and reclaim collateral | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index | ### CDP Mint/Burn Tools -| Tool | Description | Parameters | -|------|-------------|------------| +| Tool | Description | Parameters | +| ---------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mint_cdp` | Mint additional iAssets from an existing CDP (increases debt) | `address`: bech32 address; `asset`: iUSD, iBTC, iETH, or iSOL; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: CDP UTxO output index; `amount`: iAsset amount in smallest unit | -| `burn_cdp` | Burn iAssets to reduce CDP debt | `address`: bech32 address; `asset`: iUSD, iBTC, iETH, or iSOL; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: CDP UTxO output index; `amount`: iAsset amount in smallest unit | +| `burn_cdp` | Burn iAssets to reduce CDP debt | `address`: bech32 address; `asset`: iUSD, iBTC, iETH, or iSOL; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: CDP UTxO output index; `amount`: iAsset amount in smallest unit | ### CDP Liquidation & Redemption Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `liquidate_cdp` | Liquidate an undercollateralized CDP through the stability pool | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index | -| `redeem_cdp` | Redeem iAssets from a CDP | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index; `amount`: iAsset amount in smallest unit | -| `freeze_cdp` | Freeze a CDP to prevent further operations | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index | -| `merge_cdps` | Merge multiple CDPs into one | `address`: bech32 address; `cdpOutRefs`: array of `{txHash, outputIndex}` (min 2) | +| Tool | Description | Parameters | +| --------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `liquidate_cdp` | Liquidate an undercollateralized CDP through the stability pool | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index | +| `redeem_cdp` | Redeem iAssets from a CDP | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index; `amount`: iAsset amount in smallest unit | +| `freeze_cdp` | Freeze a CDP to prevent further operations | `address`: bech32 address; `asset`: iAsset; `cdpTxHash`: CDP UTxO tx hash; `cdpOutputIndex`: output index | +| `merge_cdps` | Merge multiple CDPs into one | `address`: bech32 address; `cdpOutRefs`: array of `{txHash, outputIndex}` (min 2) | ### Leverage CDP Tools -| Tool | Description | Parameters | -|------|-------------|------------| +| Tool | Description | Parameters | +| -------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `leverage_cdp` | Open a leveraged CDP by redeeming against ROB positions | `address`: bech32 address; `asset`: iAsset; `leverage`: multiplier (e.g. 2.0); `baseCollateral`: lovelace amount | ### Stability Pool Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_stability_pools` | Get the latest stability pool state for each iAsset | None | -| `get_stability_pool_accounts` | Get all open stability pool accounts, optionally filtered by iAsset | `asset?`: iUSD, iBTC, iETH, or iSOL | -| `get_sp_account_by_owner` | Get stability pool accounts for specific owners | `owners`: array of payment key hashes or bech32 addresses | +| Tool | Description | Parameters | +| ----------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------- | +| `get_stability_pools` | Get the latest stability pool state for each iAsset | None | +| `get_stability_pool_accounts` | Get all open stability pool accounts, optionally filtered by iAsset | `asset?`: iUSD, iBTC, iETH, or iSOL | +| `get_sp_account_by_owner` | Get stability pool accounts for specific owners | `owners`: array of payment key hashes or bech32 addresses | ### Staking Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_staking_info` | Get the current INDY staking manager state | None | -| `get_staking_positions` | Get all open INDY staking positions | None | -| `get_staking_positions_by_owner` | Get INDY staking positions for specific owners | `owners`: array of payment key hashes or bech32 addresses | -| `get_staking_position_by_address` | Get INDY staking positions for a single address | `address`: Cardano bech32 address | +| Tool | Description | Parameters | +| --------------------------------- | ----------------------------------------------- | --------------------------------------------------------- | +| `get_staking_info` | Get the current INDY staking manager state | None | +| `get_staking_positions` | Get all open INDY staking positions | None | +| `get_staking_positions_by_owner` | Get INDY staking positions for specific owners | `owners`: array of payment key hashes or bech32 addresses | +| `get_staking_position_by_address` | Get INDY staking positions for a single address | `address`: Cardano bech32 address | ### Stability Pool Request Tools -| Tool | Description | Parameters | -|------|-------------|------------| +| Tool | Description | Parameters | +| -------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `process_sp_request` | Process a pending stability pool request (protocol maintenance) | `address`: bech32 address; `asset`: iAsset; `accountTxHash`: account UTxO tx hash; `accountOutputIndex`: output index | -| `annul_sp_request` | Cancel a pending stability pool request | `address`: bech32 address; `accountTxHash`: account UTxO tx hash; `accountOutputIndex`: output index | +| `annul_sp_request` | Cancel a pending stability pool request | `address`: bech32 address; `accountTxHash`: account UTxO tx hash; `accountOutputIndex`: output index | ### Staking Write Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `open_staking_position` | Stake INDY tokens by creating a new staking position | `address`: bech32 address; `amount`: INDY amount in smallest unit | +| Tool | Description | Parameters | +| ------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `open_staking_position` | Stake INDY tokens by creating a new staking position | `address`: bech32 address; `amount`: INDY amount in smallest unit | | `adjust_staking_position` | Adjust an existing staking position (add or remove INDY) | `address`: bech32 address; `amount`: positive=stake more, negative=unstake; `positionTxHash`: UTxO tx hash; `positionOutputIndex`: UTxO output index | -| `close_staking_position` | Close a staking position and unstake all INDY | `address`: bech32 address; `positionTxHash`: UTxO tx hash; `positionOutputIndex`: UTxO output index | +| `close_staking_position` | Close a staking position and unstake all INDY | `address`: bech32 address; `positionTxHash`: UTxO tx hash; `positionOutputIndex`: UTxO output index | ### Staking Reward Tools -| Tool | Description | Parameters | -|------|-------------|------------| +| Tool | Description | Parameters | +| ---------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `distribute_staking_rewards` | Distribute collected ADA rewards from collector UTxOs to stakers | `address`: bech32 address; `collectorTxHashes`: array of `{txHash, outputIndex}` | ### Analytics & APR Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_tvl` | Get historical TVL data from DefiLlama | None | -| `get_apr_rewards` | Get all APR reward records | None | -| `get_apr_by_key` | Get APR for a specific key | `key`: APR key (e.g. sp_iUSD_indy, stake_ada) | -| `get_dex_yields` | Get DEX farm yields for iAsset pairs | None | -| `get_protocol_stats` | Get aggregated protocol statistics | None | +| Tool | Description | Parameters | +| -------------------- | -------------------------------------- | --------------------------------------------- | +| `get_tvl` | Get historical TVL data from DefiLlama | None | +| `get_apr_rewards` | Get all APR reward records | None | +| `get_apr_by_key` | Get APR for a specific key | `key`: APR key (e.g. sp_iUSD_indy, stake_ada) | +| `get_dex_yields` | Get DEX farm yields for iAsset pairs | None | +| `get_protocol_stats` | Get aggregated protocol statistics | None | ### Governance Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_protocol_params` | Get latest governance protocol parameters | None | -| `get_temperature_checks` | Get temperature check polls | None | -| `get_polls` | Get all governance polls | None | +| Tool | Description | Parameters | +| ------------------------ | ----------------------------------------- | ---------- | +| `get_protocol_params` | Get latest governance protocol parameters | None | +| `get_temperature_checks` | Get temperature check polls | None | +| `get_polls` | Get all governance polls | None | ### Redemption & Order Book Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_order_book` | Get open limited redemption positions | `asset?`: iAsset filter; `owners?`: array of payment key hashes | -| `get_redemption_orders` | Get redemption orders with optional filters | `timestamp?`: Unix ms; `in_range?`: filter by price range | -| `get_redemption_queue` | Get aggregated redemption queue for an iAsset | `asset`: iUSD, iBTC, iETH, or iSOL | +| Tool | Description | Parameters | +| ----------------------- | --------------------------------------------- | --------------------------------------------------------------- | +| `get_order_book` | Get open limited redemption positions | `asset?`: iAsset filter; `owners?`: array of payment key hashes | +| `get_redemption_orders` | Get redemption orders with optional filters | `timestamp?`: Unix ms; `in_range?`: filter by price range | +| `get_redemption_queue` | Get aggregated redemption queue for an iAsset | `asset`: iUSD, iBTC, iETH, or iSOL | ### ROB Write Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `open_rob` | Open a new ROB position with ADA and a max price limit | `address`: bech32 address; `asset`: iAsset; `lovelacesAmount`: lovelace to deposit; `maxPrice`: on-chain integer string | -| `cancel_rob` | Cancel an existing ROB position | `address`: bech32 address; `robTxHash`: ROB UTxO tx hash; `robOutputIndex`: output index | -| `adjust_rob` | Adjust ADA in a ROB (positive to add, negative to remove) | `address`: bech32 address; `robTxHash`: ROB UTxO tx hash; `robOutputIndex`: output index; `lovelacesAdjustAmount`: adjustment; `newMaxPrice?`: optional new max price | -| `claim_rob` | Claim received iAssets from an ROB position | `address`: bech32 address; `robTxHash`: ROB UTxO tx hash; `robOutputIndex`: output index | -| `redeem_rob` | Redeem iAssets against one or more ROB positions | `address`: bech32 address; `redemptionRobs`: array of `{txHash, outputIndex, iAssetAmount}`; `priceOracleTxHash`; `priceOracleOutputIndex`; `iassetTxHash`; `iassetOutputIndex` | +| Tool | Description | Parameters | +| ------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `open_rob` | Open a new ROB position with ADA and a max price limit | `address`: bech32 address; `asset`: iAsset; `lovelacesAmount`: lovelace to deposit; `maxPrice`: on-chain integer string | +| `cancel_rob` | Cancel an existing ROB position | `address`: bech32 address; `robTxHash`: ROB UTxO tx hash; `robOutputIndex`: output index | +| `adjust_rob` | Adjust ADA in a ROB (positive to add, negative to remove) | `address`: bech32 address; `robTxHash`: ROB UTxO tx hash; `robOutputIndex`: output index; `lovelacesAdjustAmount`: adjustment; `newMaxPrice?`: optional new max price | +| `claim_rob` | Claim received iAssets from an ROB position | `address`: bech32 address; `robTxHash`: ROB UTxO tx hash; `robOutputIndex`: output index | +| `redeem_rob` | Redeem iAssets against one or more ROB positions | `address`: bech32 address; `redemptionRobs`: array of `{txHash, outputIndex, iAssetAmount}`; `priceOracleTxHash`; `priceOracleOutputIndex`; `iassetTxHash`; `iassetOutputIndex` | ### DEX Proxy Tools -| Tool | Description | Parameters | -|------|-------------|------------| -| `get_steelswap_tokens` | Get all tokens available on Steelswap DEX | None | -| `get_steelswap_estimate` | Get a swap estimate from Steelswap | `tokenIn`: input token; `tokenOut`: output token; `amountIn`: amount | -| `get_iris_liquidity_pools` | Get liquidity pools from Iris | `tokenA?`: first token; `tokenB?`: second token; `dex?`: DEX filter | -| `get_blockfrost_balances` | Get token balances for a Cardano address | `address`: Cardano bech32 address | +| Tool | Description | Parameters | +| -------------------------- | ----------------------------------------- | -------------------------------------------------------------------- | +| `get_steelswap_tokens` | Get all tokens available on Steelswap DEX | None | +| `get_steelswap_estimate` | Get a swap estimate from Steelswap | `tokenIn`: input token; `tokenOut`: output token; `amountIn`: amount | +| `get_iris_liquidity_pools` | Get liquidity pools from Iris | `tokenA?`: first token; `tokenB?`: second token; `dex?`: DEX filter | +| `get_blockfrost_balances` | Get token balances for a Cardano address | `address`: Cardano bech32 address | ### Collector & IPFS Tools -| Tool | Description | Parameters | -|------|-------------|------------| +| Tool | Description | Parameters | +| --------------------- | ---------------------------------------- | ------------------------------ | | `get_collector_utxos` | Get collector UTXOs for fee distribution | `length?`: max UTXOs to return | -| `store_on_ipfs` | Store text content on IPFS | `text`: content to store | -| `retrieve_from_ipfs` | Retrieve content from IPFS by CID | `cid`: IPFS content identifier | +| `store_on_ipfs` | Store text content on IPFS | `text`: content to store | +| `retrieve_from_ipfs` | Retrieve content from IPFS by CID | `cid`: IPFS content identifier | ## Environment Variables -| Variable | Required | Default | Description | -|----------|----------|---------|-------------| -| `INDEXER_URL` | No | `https://analytics.indigoprotocol.io/api/v1` | Indigo analytics API base URL | -| `BLOCKFROST_API_KEY` | For write ops | — | Blockfrost project ID for transaction building | -| `CARDANO_NETWORK` | No | `mainnet` | Cardano network: `mainnet`, `preprod`, or `preview` | -| `MCP_TRANSPORT` | No | `stdio` | Transport mode: `stdio` or `http` | -| `PORT` | No | `3000` | HTTP server port (only used when `MCP_TRANSPORT=http`) | +| Variable | Required | Default | Description | +| ---------------------- | ------------- | -------------------------------------------- | ----------------------------------------------------------- | +| `INDEXER_URL` | No | `https://analytics.indigoprotocol.io/api/v1` | Indigo analytics API base URL | +| `BLOCKFROST_API_KEY` | For write ops | — | Blockfrost project ID for transaction building | +| `CARDANO_NETWORK` | No | `mainnet` | Cardano network: `mainnet`, `preprod`, or `preview` | +| `MCP_TRANSPORT` | No | `stdio` | Transport mode: `stdio` or `http` | +| `PORT` | No | `3000` | HTTP server port (only used when `MCP_TRANSPORT=http`) | +| `X402_EVM_ADDRESS` | No | — | EVM wallet to receive USDC on Base (enables payment gating) | +| `X402_CARDANO_ADDRESS` | No | — | Cardano address for future USDM support | +| `X402_TESTNET` | No | `false` | Use Base Sepolia for payments | +| `X402_FACILITATOR_URL` | No | `https://x402.org/facilitator` | x402 facilitator URL | +| `PAYMENT_SERVER` | No | — | Alias for `X402_FACILITATOR_URL` (takes precedence) | +| `X402_PRIVATE_KEY` | No | — | EVM private key for client-side auto-payment | +| `X402_LOG_FILE` | No | `/tmp/indigo-mcp-x402.log` | Payment event log file | ## Example Queries @@ -507,6 +517,8 @@ npm run test:watch # run tests in watch mode ``` src/ ├── index.ts # Server entry point (stdio transport) +├── payment.ts # x402 configuration: chain addresses + tool price tiers +├── payment-client.ts # withAutoPayment: client-side auto-pay on 402 responses ├── types/ │ └── tx-types.ts # UnsignedTxResult, TxSummary types ├── tools/ @@ -567,12 +579,43 @@ Indigo MCP optionally gates tools behind per-call micropayments using the [x402 ### Environment variables -| Variable | Required | Default | Description | -|---|---|---|---| -| `X402_EVM_ADDRESS` | to enable | — | EVM wallet to receive USDC on Base | -| `X402_CARDANO_ADDRESS` | optional | — | Cardano address to receive USDM on Cardano | -| `X402_TESTNET` | optional | `false` | Use Base Sepolia / Cardano preprod | -| `X402_FACILITATOR_URL` | optional | `https://x402.org/facilitator` | Override facilitator | +| Variable | Required | Default | Description | +| ---------------------- | --------- | ------------------------------ | ------------------------------------------------------------------- | +| `X402_EVM_ADDRESS` | to enable | — | EVM wallet to receive USDC on Base | +| `X402_CARDANO_ADDRESS` | optional | — | Cardano address to receive USDM on Cardano | +| `X402_TESTNET` | optional | `false` | Use Base Sepolia / Cardano preprod | +| `X402_FACILITATOR_URL` | optional | `https://x402.org/facilitator` | Facilitator URL for payment verification | +| `PAYMENT_SERVER` | optional | — | Alias for `X402_FACILITATOR_URL` (takes precedence if both are set) | +| `X402_PRIVATE_KEY` | optional | — | EVM private key (`0x…`) for client-side auto-payment (see below) | +| `X402_LOG_FILE` | optional | `/tmp/indigo-mcp-x402.log` | File path for payment event logs | + +### Auto-payment (client-side / facilitator settlement) + +When `X402_PRIVATE_KEY` is set, the server acts as both gatekeeper **and** payer. Every outbound tool call that returns a `402 Payment Required` response is automatically: + +1. Parsed for payment requirements (`payTo`, `price`, `chainId`) +2. Signed with the configured private key +3. Retried with the `paymentSignature` attached +4. Logged to stderr and `X402_LOG_FILE` + +This is useful when indigo-mcp is running behind a facilitator (e.g. `PAYMENT_SERVER=https://your-facilitator`) that handles settlement, and you want the server to pay on behalf of its callers transparently. + +```bash +# Run with auto-payment enabled +X402_EVM_ADDRESS=0xYourReceiver \ +X402_PRIVATE_KEY=0xYourPayerPrivateKey \ +PAYMENT_SERVER=https://your-facilitator \ +npx @indigoprotocol/indigo-mcp +``` + +If `X402_PRIVATE_KEY` is not set, auto-payment is disabled and 402 responses are returned as-is to the MCP client. + +All payment events (signing, success, verification failure) are written to both stderr and the log file: + +``` +2026-03-31T19:21:00.000Z [x402] paying $0.001 USDC → 0xReceiver (chain 8453) from 0xPayer +2026-03-31T19:21:00.123Z [x402] payment accepted — tool executed successfully +``` ### Local development From 21565a622134c6507ff829664360a9454ad8e048 Mon Sep 17 00:00:00 2001 From: "a.dacapo21" Date: Wed, 1 Apr 2026 13:45:13 +0300 Subject: [PATCH 2/4] feat(x402): route payments through mcp.openmm.io proxy via split execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace withX402 + withAutoPayment interceptor with wrapWithSplitPayment from @qbtlabs/x402/split — payment verification and settlement now route through https://mcp.openmm.io by default instead of x402.org/facilitator - payment.ts: rewrite as applyPaymentGate(server) — no configure/setToolPrices, just wrapWithSplitPayment with privateKey, workerUrl, testnet, freeTools - server.ts: replace manual server.tool interceptor with applyPaymentGate(server) called before registerTools; must be called before tool registration - Delete payment-client.ts — split gate handles signing + retry internally - .env.example: replace X402_EVM_ADDRESS with X402_PRIVATE_KEY; PAYMENT_SERVER now defaults to https://mcp.openmm.io - README: update x402 section to document split flow, new env vars, and correct MCP client config examples Split execution flow (mirrors openmm-mcp architecture): 1. Tool called — no payment header required from caller 2. Gate POSTs to workerUrl/verify-payment 3. Worker returns 402 with EIP-3009 requirements 4. Gate signs locally with X402_PRIVATE_KEY (key never leaves process) 5. Gate retries with X-PAYMENT header → worker issues JWT 6. Gate verifies JWT locally, executes handler, injects tx hash in response --- .env.example | 25 ++++--- README.md | 82 ++++++++++----------- src/payment-client.ts | 91 ------------------------ src/payment.ts | 161 ++++++++++++++---------------------------- src/server.ts | 23 ++---- 5 files changed, 114 insertions(+), 268 deletions(-) delete mode 100644 src/payment-client.ts diff --git a/.env.example b/.env.example index 1de85e2..3a539e9 100644 --- a/.env.example +++ b/.env.example @@ -4,17 +4,24 @@ INDEXER_URL=https://analytics.indigoprotocol.io/api/v1 BLOCKFROST_API_KEY= # ── x402 payment gating (optional — omit or leave blank to disable) ────────── -# EVM wallet address to receive USDC on Base (required to enable EVM payments) -X402_EVM_ADDRESS= +# +# Payment uses the split execution flow: signing happens locally, verification +# and settlement route through the openmm.io proxy (or your own PAYMENT_SERVER). +# Private keys never leave this process. +# +# To enable: set X402_PRIVATE_KEY to your EVM wallet private key. +# The wallet pays for tool calls; funds must be available in USDC on Base. -# Cardano address (optional) — NOTE: @qbtlabs/x402 v0.5.0 does not yet emit a -# Cardano entry in 402 accepts[] because USDC_CONTRACTS has no cardano: entry. -# Setting this enables Cardano payment *verification* but not advertisement. -# Leave blank until a future x402 release adds native Cardano token support. -X402_CARDANO_ADDRESS= +# EVM private key of the payer wallet (0x-prefixed). Enables auto-payment. +X402_PRIVATE_KEY= -# Set to "true" to use testnets (Base Sepolia = chainId 84532, Cardano preprod) +# Proxy / settlement worker URL. Defaults to https://mcp.openmm.io +# Override for self-hosted deployments. +PAYMENT_SERVER= + +# Set to "true" to use Base Sepolia testnet X402_TESTNET=false -# Override default facilitator URL (defaults to https://x402.org/facilitator) +# Legacy: direct facilitator URL fallback (used only when PAYMENT_SERVER is unset +# and you want to bypass mcp.openmm.io). Defaults to https://mcp.openmm.io. X402_FACILITATOR_URL= diff --git a/README.md b/README.md index 79b21dc..136d39c 100644 --- a/README.md +++ b/README.md @@ -452,13 +452,10 @@ For any client that supports MCP over stdio, point it to the `npx @indigoprotoco | `CARDANO_NETWORK` | No | `mainnet` | Cardano network: `mainnet`, `preprod`, or `preview` | | `MCP_TRANSPORT` | No | `stdio` | Transport mode: `stdio` or `http` | | `PORT` | No | `3000` | HTTP server port (only used when `MCP_TRANSPORT=http`) | -| `X402_EVM_ADDRESS` | No | — | EVM wallet to receive USDC on Base (enables payment gating) | -| `X402_CARDANO_ADDRESS` | No | — | Cardano address for future USDM support | -| `X402_TESTNET` | No | `false` | Use Base Sepolia for payments | -| `X402_FACILITATOR_URL` | No | `https://x402.org/facilitator` | x402 facilitator URL | -| `PAYMENT_SERVER` | No | — | Alias for `X402_FACILITATOR_URL` (takes precedence) | -| `X402_PRIVATE_KEY` | No | — | EVM private key for client-side auto-payment | -| `X402_LOG_FILE` | No | `/tmp/indigo-mcp-x402.log` | Payment event log file | +| `X402_PRIVATE_KEY` | No | — | EVM private key (`0x…`) of the payer wallet — enables auto-payment via split flow | +| `PAYMENT_SERVER` | No | `https://mcp.openmm.io` | Settlement worker / proxy URL | +| `X402_TESTNET` | No | `false` | Use Base Sepolia testnet | +| `X402_FACILITATOR_URL` | No | — | Fallback facilitator (used only when `PAYMENT_SERVER` unset) | ## Example Queries @@ -572,67 +569,66 @@ Indigo MCP optionally gates tools behind per-call micropayments using the [x402 ### How it works +Payment uses the **split execution** model — the same architecture as openMM-MCP: + +1. A tool is called (no payment header needed from the caller) +2. The gate intercepts and contacts the **settlement worker** (`mcp.openmm.io` by default) +3. Worker responds `402` with EIP-3009 requirements (amount, recipient, chain) +4. Gate **signs locally** using `X402_PRIVATE_KEY` — the key never leaves this process +5. Gate retries with the signed payment → worker verifies on-chain, issues a JWT +6. Gate verifies JWT locally, executes the original tool handler +7. Settlement tx hash is injected into the tool response + +This keeps process isolation clean: indigo-mcp never holds the recipient wallet — only the payer key. Verification and settlement are handled by the openmm.io proxy. + - Read tools (`get_tvl`, `get_asset_price`, …) cost **$0.001 USDC** per call - Analysis tools (`analyze_cdp_health`) cost **$0.005 USDC** per call - Write tools (`open_cdp`, `mint_cdp`, …) cost **$0.01 USDC** per call -- Tools called without a valid payment return a `402 Payment Required` JSON response with an `accepts[]` array listing supported chains and amounts ### Environment variables -| Variable | Required | Default | Description | -| ---------------------- | --------- | ------------------------------ | ------------------------------------------------------------------- | -| `X402_EVM_ADDRESS` | to enable | — | EVM wallet to receive USDC on Base | -| `X402_CARDANO_ADDRESS` | optional | — | Cardano address to receive USDM on Cardano | -| `X402_TESTNET` | optional | `false` | Use Base Sepolia / Cardano preprod | -| `X402_FACILITATOR_URL` | optional | `https://x402.org/facilitator` | Facilitator URL for payment verification | -| `PAYMENT_SERVER` | optional | — | Alias for `X402_FACILITATOR_URL` (takes precedence if both are set) | -| `X402_PRIVATE_KEY` | optional | — | EVM private key (`0x…`) for client-side auto-payment (see below) | -| `X402_LOG_FILE` | optional | `/tmp/indigo-mcp-x402.log` | File path for payment event logs | +| Variable | Required | Default | Description | +| ---------------------- | --------- | ----------------------- | ------------------------------------------------------------------------------ | +| `X402_PRIVATE_KEY` | to enable | — | EVM private key (`0x…`) of the payer wallet — enables split payment | +| `PAYMENT_SERVER` | optional | `https://mcp.openmm.io` | Settlement worker / proxy URL | +| `X402_TESTNET` | optional | `false` | Use Base Sepolia testnet | +| `X402_FACILITATOR_URL` | optional | — | Fallback facilitator URL (used only when `PAYMENT_SERVER` is not set) | -### Auto-payment (client-side / facilitator settlement) +### Split execution flow -When `X402_PRIVATE_KEY` is set, the server acts as both gatekeeper **and** payer. Every outbound tool call that returns a `402 Payment Required` response is automatically: +When `X402_PRIVATE_KEY` is set, every paid tool call is handled transparently: -1. Parsed for payment requirements (`payTo`, `price`, `chainId`) -2. Signed with the configured private key -3. Retried with the `paymentSignature` attached -4. Logged to stderr and `X402_LOG_FILE` +1. Gate contacts `PAYMENT_SERVER` (`https://mcp.openmm.io` by default) +2. Signs EIP-3009 locally — the private key never leaves this process +3. Proxy verifies on-chain, issues a short-lived JWT +4. Gate verifies JWT, executes tool, injects settlement tx hash into response -This is useful when indigo-mcp is running behind a facilitator (e.g. `PAYMENT_SERVER=https://your-facilitator`) that handles settlement, and you want the server to pay on behalf of its callers transparently. +If `X402_PRIVATE_KEY` is not set the gate is disabled and all tools execute without payment. ```bash -# Run with auto-payment enabled -X402_EVM_ADDRESS=0xYourReceiver \ +# Minimal: just set the payer key (proxy defaults to mcp.openmm.io) +X402_PRIVATE_KEY=0xYourPayerPrivateKey npx @indigoprotocol/indigo-mcp + +# Self-hosted proxy X402_PRIVATE_KEY=0xYourPayerPrivateKey \ -PAYMENT_SERVER=https://your-facilitator \ +PAYMENT_SERVER=https://your-own-proxy \ npx @indigoprotocol/indigo-mcp ``` -If `X402_PRIVATE_KEY` is not set, auto-payment is disabled and 402 responses are returned as-is to the MCP client. - -All payment events (signing, success, verification failure) are written to both stderr and the log file: - -``` -2026-03-31T19:21:00.000Z [x402] paying $0.001 USDC → 0xReceiver (chain 8453) from 0xPayer -2026-03-31T19:21:00.123Z [x402] payment accepted — tool executed successfully -``` - ### Local development ```bash # 1. Copy example env cp .env.example .env -# Edit .env and fill in X402_EVM_ADDRESS (and optionally X402_CARDANO_ADDRESS) +# Edit .env and set X402_PRIVATE_KEY to a funded Base Sepolia wallet # 2. Start the HTTP server MCP_TRANSPORT=http PORT=3000 npm run dev # 3. Run the payment e2e tests -X402_EVM_ADDRESS=0x... X402_TESTNET=true npm test -- x402-payment +X402_TESTNET=true npm test -- x402-payment ``` -The e2e tests work without a real wallet address — the "real env" test case is the only one that requires `X402_EVM_ADDRESS` to be set. - ### MCP client config with x402 Add the `env` block to whichever MCP config file your client uses: @@ -648,7 +644,7 @@ Add the `env` block to whichever MCP config file your client uses: "env": { "INDEXER_URL": "https://analytics.indigoprotocol.io/api/v1", "BLOCKFROST_API_KEY": "your-blockfrost-project-id", - "X402_EVM_ADDRESS": "0xYourEVMWalletAddress", + "X402_PRIVATE_KEY": "0xYourPayerPrivateKey", "X402_TESTNET": "true" } } @@ -667,7 +663,7 @@ Add the `env` block to whichever MCP config file your client uses: "env": { "INDEXER_URL": "https://analytics.indigoprotocol.io/api/v1", "BLOCKFROST_API_KEY": "your-blockfrost-project-id", - "X402_EVM_ADDRESS": "0xYourEVMWalletAddress", + "X402_PRIVATE_KEY": "0xYourPayerPrivateKey", "X402_TESTNET": "true" } } @@ -677,7 +673,7 @@ Add the `env` block to whichever MCP config file your client uses: **Cursor / Windsurf** — same `env` block applies to `~/.cursor/mcp.json` or `~/.codeium/windsurf/mcp_config.json`. -> Set `X402_TESTNET` to `false` (or omit it) for Base mainnet / Cardano mainnet. +> Set `X402_TESTNET` to `false` (or omit it) for Base mainnet. ## License diff --git a/src/payment-client.ts b/src/payment-client.ts deleted file mode 100644 index c55daeb..0000000 --- a/src/payment-client.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * x402 client-side auto-payment wrapper - * - * When X402_PRIVATE_KEY is set, intercepts 402 responses from withX402-gated - * tool handlers, signs a payment, and retries — so callers (e.g. Claude Code) - * receive the actual tool result rather than a payment-required error. - * - * If X402_PRIVATE_KEY is not set, this is a no-op passthrough. - * - * PAYMENT_SERVER overrides the facilitator URL at startup (before configure() - * is called in payment.ts); at runtime it has no effect here because the - * signing is purely local — the server-side withX402 middleware calls the - * facilitator for verification. - */ - -import { appendFileSync } from 'node:fs'; -import { signPayment, buildPaymentPayload, parsePaymentRequired } from '@qbtlabs/x402'; - -type ToolResult = { content: Array<{ type: string; text: string }> }; -type AnyHandler = (params: Record) => Promise; - -const LOG_FILE = process.env.X402_LOG_FILE ?? '/tmp/indigo-mcp-x402.log'; - -function log(msg: string): void { - const line = `${new Date().toISOString()} ${msg}\n`; - process.stderr.write(line); - try { - appendFileSync(LOG_FILE, line); - } catch { - /* ignore */ - } -} - -/** - * Wraps a tool handler so that 402 responses are automatically paid using - * the configured X402_PRIVATE_KEY. - */ -export function withAutoPayment(handler: AnyHandler): AnyHandler { - const privateKey = process.env.X402_PRIVATE_KEY as `0x${string}` | undefined; - if (!privateKey) return handler; - - return async (params: Record) => { - const result = await handler(params); - - const text = result?.content?.[0]?.text; - if (!text) return result; - - let parsed: Record; - try { - parsed = JSON.parse(text); - } catch { - return result; - } - - if (parsed['code'] !== 402) return result; - - const requirements = parsePaymentRequired(parsed as Parameters[0]); - if (!requirements) return result; - - const signed = await signPayment({ - privateKey, - to: requirements.payTo as `0x${string}`, - amount: requirements.price, - chainId: requirements.chainId, - }); - - const paymentSignature = buildPaymentPayload(signed); - - log( - `[x402] paying $${requirements.price} USDC → ${requirements.payTo} (chain ${requirements.chainId}) from ${signed.from}` - ); - - const finalResult = await handler({ ...params, paymentSignature }); - - const finalText = finalResult?.content?.[0]?.text ?? ''; - let finalParsed: Record = {}; - try { - finalParsed = JSON.parse(finalText); - } catch { - /* not JSON */ - } - - if (finalParsed['code'] === 402) { - log(`[x402] payment verification failed: ${finalParsed['reason'] ?? finalText}`); - } else { - log(`[x402] payment accepted — tool executed successfully`); - } - - return finalResult; - }; -} diff --git a/src/payment.ts b/src/payment.ts index 4b7cc07..d14f635 100644 --- a/src/payment.ts +++ b/src/payment.ts @@ -1,115 +1,60 @@ /** - * Payment module — x402 per-tool payment gating + * Payment module — x402 split execution payment gate * - * Configures chain addresses and maps every Indigo MCP tool to a pricing tier. - * Import this module before registering tools so configure() and setToolPrices() - * are in effect when withX402 wrappers evaluate their first call. + * Routes payment verification through the openmm.io proxy (or a custom + * PAYMENT_SERVER) instead of directly to the x402.org facilitator. + * + * Split execution flow: + * 1. Tool is called (no payment header required from the caller) + * 2. Gate intercepts and POSTs to workerUrl/verify-payment + * 3. Worker responds 402 with EIP-3009 requirements + * 4. Gate signs locally with X402_PRIVATE_KEY — key never leaves this process + * 5. Gate retries with X-PAYMENT header → worker verifies on-chain, issues JWT + * 6. Gate verifies JWT locally, then executes the original tool handler + * 7. Payment metadata (tx hash) is injected into the tool response + * + * Process isolation: the openmm.io worker handles all settlement logic. + * indigo-mcp never holds the recipient wallet — only the payer key. */ -import { configure, setToolPrices } from '@qbtlabs/x402'; - -configure({ - evm: { address: process.env.X402_EVM_ADDRESS! }, - cardano: process.env.X402_CARDANO_ADDRESS - ? { address: process.env.X402_CARDANO_ADDRESS } - : undefined, - // PAYMENT_SERVER is an alias for the facilitator URL (mirrors openmm-mcp convention) - facilitatorUrl: - process.env.PAYMENT_SERVER ?? - process.env.X402_FACILITATOR_URL ?? - 'https://x402.org/facilitator', - testnet: process.env.X402_TESTNET === 'true', -}); - -setToolPrices({ - // ── Read-only tools: protocol state, prices, positions ────────────────── - // Analytics - get_tvl: 'read', - get_apr_rewards: 'read', - get_apr_by_key: 'read', - get_dex_yields: 'read', - get_protocol_stats: 'read', - - // Asset / price feeds - get_assets: 'read', - get_asset: 'read', - get_asset_price: 'read', - get_ada_price: 'read', - get_indy_price: 'read', - - // CDP read - get_all_cdps: 'read', - get_cdps_by_owner: 'read', - get_cdps_by_address: 'read', - analyze_cdp_health: 'analysis', - - // Collector / IPFS read - get_collector_utxos: 'read', - retrieve_from_ipfs: 'read', - - // DEX - get_steelswap_tokens: 'read', - get_steelswap_estimate: 'read', - get_iris_liquidity_pools: 'read', - get_blockfrost_balances: 'read', - - // Governance - get_protocol_params: 'read', - get_temperature_checks: 'read', - get_polls: 'read', - - // Redemption - get_order_book: 'read', - get_redemption_orders: 'read', - get_redemption_queue: 'read', - - // Stability pool read - get_stability_pools: 'read', - get_stability_pool_accounts: 'read', - get_sp_account_by_owner: 'read', +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { wrapWithSplitPayment } from '@qbtlabs/x402/split'; - // Staking read - get_staking_info: 'read', - get_staking_positions: 'read', - get_staking_positions_by_owner: 'read', - get_staking_position_by_address: 'read', +/** Default proxy / settlement worker. Override with PAYMENT_SERVER env var. */ +export const DEFAULT_WORKER_URL = 'https://mcp.openmm.io'; - // ── Write tools: transaction builders / on-chain mutations ────────────── - // CDP write - open_cdp: 'write', - deposit_cdp: 'write', - withdraw_cdp: 'write', - close_cdp: 'write', - mint_cdp: 'write', - burn_cdp: 'write', - leverage_cdp: 'write', - - // CDP liquidation - liquidate_cdp: 'write', - redeem_cdp: 'write', - freeze_cdp: 'write', - merge_cdps: 'write', - - // ROB write - open_rob: 'write', - cancel_rob: 'write', - adjust_rob: 'write', - claim_rob: 'write', - redeem_rob: 'write', - - // Stability pool write - process_sp_request: 'write', - annul_sp_request: 'write', - create_sp_account: 'write', - adjust_sp_account: 'write', - close_sp_account: 'write', - - // Staking write - open_staking_position: 'write', - adjust_staking_position: 'write', - close_staking_position: 'write', - distribute_staking_rewards: 'write', +/** + * Tool names that bypass payment entirely. + * Empty by default — all 40 Indigo tools require payment when enabled. + * Add tool names here to make them always free. + */ +export const FREE_TOOLS: string[] = []; - // IPFS write - store_on_ipfs: 'write', -}); +/** + * Apply the split payment gate to an MCP server. + * Must be called BEFORE registerTools(). + * + * Payment is enabled when X402_PRIVATE_KEY is set (the payer wallet). + * When disabled this is a no-op — all tools execute without payment. + * + * @example + * ```bash + * X402_PRIVATE_KEY=0x... # EVM payer wallet — auto-signs tool payments + * PAYMENT_SERVER=https://mcp.openmm.io # proxy (default) + * X402_TESTNET=true # Base Sepolia + * ``` + */ +export function applyPaymentGate(server: McpServer): void { + const privateKey = process.env.X402_PRIVATE_KEY as `0x${string}` | undefined; + if (!privateKey) return; + + const workerUrl = + process.env.PAYMENT_SERVER ?? process.env.X402_FACILITATOR_URL ?? DEFAULT_WORKER_URL; + + wrapWithSplitPayment(server as any, { + privateKey, + workerUrl, + testnet: process.env.X402_TESTNET === 'true', + freeTools: FREE_TOOLS, + }); +} diff --git a/src/server.ts b/src/server.ts index 1ed3917..1a5cbc9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,11 +3,9 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { createServer as createHttpServer } from 'node:http'; import { randomUUID } from 'node:crypto'; -import { withX402 } from '@qbtlabs/x402'; import { registerTools } from './tools/index.js'; import { registerResources } from './resources/index.js'; -import { withAutoPayment } from './payment-client.js'; -import './payment.js'; +import { applyPaymentGate } from './payment.js'; const SERVER_NAME = 'indigo-mcp'; const SERVER_VERSION = '0.2.0'; @@ -18,20 +16,11 @@ export function createServer(): McpServer { version: SERVER_VERSION, }); - // Intercept server.tool to auto-apply withX402 around every handler. - // This avoids modifying each of the 19 tool files individually. - // withX402 is a no-op when X402_EVM_ADDRESS (or any chain address) is not set. - const originalTool = server.tool.bind(server); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (server as any).tool = function (name: string, ...rest: unknown[]): unknown { - const lastIdx = rest.length - 1; - if (typeof rest[lastIdx] === 'function') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - rest[lastIdx] = withAutoPayment(withX402(name, rest[lastIdx] as any)); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (originalTool as any).apply(server, [name, ...rest]); - }; + // Apply split payment gate before registering tools. + // wrapWithSplitPayment patches server.tool internally so every handler + // goes through the mcp.openmm.io proxy for payment verification. + // No-op when X402_PRIVATE_KEY is not set. + applyPaymentGate(server); registerTools(server); registerResources(server); From fd383dc7df29096b8d30ea30d1459828304c9fcf Mon Sep 17 00:00:00 2001 From: "a.dacapo21" Date: Wed, 1 Apr 2026 20:31:44 +0300 Subject: [PATCH 3/4] style: remove inline comments from payment module --- src/payment.ts | 39 --------------------------------------- src/server.ts | 4 ---- 2 files changed, 43 deletions(-) diff --git a/src/payment.ts b/src/payment.ts index d14f635..d79b5e0 100644 --- a/src/payment.ts +++ b/src/payment.ts @@ -1,49 +1,10 @@ -/** - * Payment module — x402 split execution payment gate - * - * Routes payment verification through the openmm.io proxy (or a custom - * PAYMENT_SERVER) instead of directly to the x402.org facilitator. - * - * Split execution flow: - * 1. Tool is called (no payment header required from the caller) - * 2. Gate intercepts and POSTs to workerUrl/verify-payment - * 3. Worker responds 402 with EIP-3009 requirements - * 4. Gate signs locally with X402_PRIVATE_KEY — key never leaves this process - * 5. Gate retries with X-PAYMENT header → worker verifies on-chain, issues JWT - * 6. Gate verifies JWT locally, then executes the original tool handler - * 7. Payment metadata (tx hash) is injected into the tool response - * - * Process isolation: the openmm.io worker handles all settlement logic. - * indigo-mcp never holds the recipient wallet — only the payer key. - */ - import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { wrapWithSplitPayment } from '@qbtlabs/x402/split'; -/** Default proxy / settlement worker. Override with PAYMENT_SERVER env var. */ export const DEFAULT_WORKER_URL = 'https://mcp.openmm.io'; -/** - * Tool names that bypass payment entirely. - * Empty by default — all 40 Indigo tools require payment when enabled. - * Add tool names here to make them always free. - */ export const FREE_TOOLS: string[] = []; -/** - * Apply the split payment gate to an MCP server. - * Must be called BEFORE registerTools(). - * - * Payment is enabled when X402_PRIVATE_KEY is set (the payer wallet). - * When disabled this is a no-op — all tools execute without payment. - * - * @example - * ```bash - * X402_PRIVATE_KEY=0x... # EVM payer wallet — auto-signs tool payments - * PAYMENT_SERVER=https://mcp.openmm.io # proxy (default) - * X402_TESTNET=true # Base Sepolia - * ``` - */ export function applyPaymentGate(server: McpServer): void { const privateKey = process.env.X402_PRIVATE_KEY as `0x${string}` | undefined; if (!privateKey) return; diff --git a/src/server.ts b/src/server.ts index 1a5cbc9..d70a9c2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,10 +16,6 @@ export function createServer(): McpServer { version: SERVER_VERSION, }); - // Apply split payment gate before registering tools. - // wrapWithSplitPayment patches server.tool internally so every handler - // goes through the mcp.openmm.io proxy for payment verification. - // No-op when X402_PRIVATE_KEY is not set. applyPaymentGate(server); registerTools(server); From 65d5729a82d6fbaf6872962e95a35ce660fa9766 Mon Sep 17 00:00:00 2001 From: "a.dacapo21" Date: Wed, 1 Apr 2026 20:40:23 +0300 Subject: [PATCH 4/4] refactor: declare PAYMENT_SERVER default in .env.example instead of source --- .env.example | 5 ++--- src/payment.ts | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 3a539e9..8efa43d 100644 --- a/.env.example +++ b/.env.example @@ -15,9 +15,8 @@ BLOCKFROST_API_KEY= # EVM private key of the payer wallet (0x-prefixed). Enables auto-payment. X402_PRIVATE_KEY= -# Proxy / settlement worker URL. Defaults to https://mcp.openmm.io -# Override for self-hosted deployments. -PAYMENT_SERVER= +# Proxy / settlement worker URL. Override for self-hosted deployments. +PAYMENT_SERVER=https://mcp.openmm.io # Set to "true" to use Base Sepolia testnet X402_TESTNET=false diff --git a/src/payment.ts b/src/payment.ts index d79b5e0..81cd911 100644 --- a/src/payment.ts +++ b/src/payment.ts @@ -1,8 +1,6 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { wrapWithSplitPayment } from '@qbtlabs/x402/split'; -export const DEFAULT_WORKER_URL = 'https://mcp.openmm.io'; - export const FREE_TOOLS: string[] = []; export function applyPaymentGate(server: McpServer): void { @@ -10,7 +8,7 @@ export function applyPaymentGate(server: McpServer): void { if (!privateKey) return; const workerUrl = - process.env.PAYMENT_SERVER ?? process.env.X402_FACILITATOR_URL ?? DEFAULT_WORKER_URL; + process.env.PAYMENT_SERVER ?? process.env.X402_FACILITATOR_URL ?? 'https://mcp.openmm.io'; wrapWithSplitPayment(server as any, { privateKey,