# Monetise your Model Context Protocol (MCP) server

Charge AI agents for one-time payments using MPP.

This guide shows how to charge AI agents for one-time purchases, such as service bookings, donations, or digital goods, by using a Model Context Protocol (MCP) server, Stripe, and the [Machine Payments Protocol (MPP)](https://docs.stripe.com/payments/machine/mpp.md).

Listing your MCP server in the [Stripe Directory](https://docs.stripe.com/directory.md) makes it discoverable to AI agents, so they can find and pay you without requiring a human to search first. This approach keeps you at the centre of the transaction. Payments settle through your existing Stripe or Connect setup, so you keep your receipts, fees, and customer relationship. The same link also falls back to your existing checkout or donation form for human users, so you don’t need to change how you currently accept payments to support agents.

The approach in this guide is experimental and works with AI agents that support MCP and can make HTTP requests that handle MPP. The payment flow runs over standard HTTP, so it isn’t limited to MCP. Any agent that can make HTTP requests and handle MPP can use it. Many agents use MCP to discover tools, which is why we use it in this integration.

This flow uses [Shared Payment Tokens](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens.md), which are credentials provisioned by a user’s Link agent wallet or through an API. Shared payment tokens are one way for a customer to pay. The same MPP-based approach also works with other payment methods, such as stablecoins, as long as the customer has a compatible wallet. Learn more about the [MPP payment lifecycle](https://docs.stripe.com/payments/machine/mpp.md).

> Non-profits and fundraising platforms can use this flow to accept agent-initiated donations. The MPP receipt confirms the payment, but it doesn’t replace any donation acknowledgement or tax receipt that the non-profit or platform must provide.

## Understand how payment works

When an AI agent calls one of your MCP tools, you can collect payment before you return a result.

When MPP defines the HTTP payment handshake:

1. The client requests a resource without payment.
2. Your server returns an HTTP `402` response with a payment challenge.
3. The client obtains an SPT credential and retries the request.
4. Your server verifies payment and returns the resource with a receipt.

In this pattern, your MCP tool validates the request, then returns a payment link. That link points to a separate HTTP endpoint that handles MPP. The agent posts to the URL, and MPP handles the challenge and credential flow at the HTTP layer.

The same URL can also work for human users. If a browser opens it, the endpoint recognises the `Accept: text/html` header and redirects to a checkout page that you control and prefills the relevant parameters.

## Review the flow

The following sequence diagrams show how the same integration supports both the agent flow and browser fallback.

#### Agent flow

An agent calls an MCP tool, receives a payment link, and uses an SPT to pay for and complete the purchase. (See full diagram at https://docs.stripe.com/agentic-commerce/monetize-mcp)

```text
[Agent] -- Call the MCP tool --> [MCP tool]
[MCP tool] -- Validate the request --> [MCP tool]
[MCP tool] -- Return paymentLink --> [Agent]
[Agent] -- POST without a credential --> [Endpoint]
[Endpoint] -- Return a 402 payment challenge --> [Agent]
[Agent] -- Request an SPT --> [Agent wallet]
[Agent wallet] -- Return the SPT --> [Agent]
[Agent] -- Retry with the SPT --> [Endpoint]
[Endpoint] -- Verify payment and complete the purchase --> [Endpoint]
[Endpoint] -- Return confirmation and an MPP receipt --> [Agent]
```

#### Browser fallback

A customer opens the payment link in a browser and is redirected to a prefilled checkout page that the seller controls. (See full diagram at https://docs.stripe.com/agentic-commerce/monetize-mcp)

```text
[Customer] -- Open the payment link --> [Browser]
[Browser] -- Request with Accept: text/html --> [Endpoint]
[Endpoint] -- Redirect to a prefilled checkout page that you control --> [Browser]
```

## Before you begin

Make sure you have the following set up before you start:

- Access to [Shared Payment Tokens](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens.md) and a valid Stripe network ID.
- A fulfillment flow that validates requests and completes purchases, such as checking availability, confirming inventory, or recording a donation.
- These environment variables:
  - `BASE_URL`: The base URL for your application
  - `STRIPE_SECRET_KEY`: Your Stripe secret key
  - `STRIPE_NETWORK_ID`: The ID representing your [Stripe profile](https://dashboard.stripe.com/profiles)

If you use [Connect](https://stripe.com/connect) to route funds to connected accounts, make sure you have the connected account ID available.

## Install dependencies

Install the required dependencies:

```bash
npm install @hono/node-server @modelcontextprotocol/sdk hono mppx stripe zod
```

## Define your catalog

This example shows a minimal catalog for testing. In production, replace it with your inventory and fulfillment logic.

```javascript
const items = [
  { id: 'coffee', title: 'Coffee', priceCents: 500 },
]

export function getItem(itemId) {
  return items.find((item) => item.id === itemId)
}

export function validatePurchase(purchase) {
  if (purchase.quantity < 1) throw new Error('Quantity must be positive')
}

export function completeOrder(purchase) {
  return { id: `${purchase.item.id}-${purchase.quantity}`, status: 'complete' }
}
```

## Define your MCP tool

The following example shows the MCP tool definition. It validates the purchase request and returns a payment link without handling payment directly.

```javascript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import { getItem, validatePurchase } from './catalog.js'

const server = new McpServer({
  name: 'your-mcp-server',
  version: '1.0.0',
})

server.registerTool(
  'create_purchase_link',
  {
    description:
      'Returns a payment link for a one-time purchase. ' +
      'POST to the link with an MPP credential to pay automatically, ' +
      'or open it in a browser to pay with a card.',
    inputSchema: {
      itemId: z.string(),
      quantity: z.number().int().positive(),
      customerName: z.string(),
      customerEmail: z.string(),
    },
  },
  async ({ itemId, quantity, customerName, customerEmail }) => {
    const params = new URLSearchParams({
      itemId,
      quantity: String(quantity),
      customerName,
      customerEmail,
    })
    const paymentLink = `${process.env.BASE_URL}/api/purchase?${params}`

    // Do your normal business logic
    const item = getItem(itemId)
    if (!item) throw new Error('Item not found')
    validatePurchase({ item, quantity, customerName, customerEmail })

    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          // This link must be MPP enabled
          paymentLink,
          instructions: {
            agent: `POST to paymentLink. Server returns 402 on first call — obtain an SPT for networkId "${process.env.STRIPE_NETWORK_ID}" and retry.`,
            browser: 'Open paymentLink in a browser to pay with a card.',
          },
          item: {
            title: item.title,
            quantity,
            price: `${(item.priceCents / 100).toFixed(2)} USD`,
          },
        }, null, 2),
      }],
    }
  },
)

await server.connect(new StdioServerTransport())
```

## Create your payment endpoint

The following example shows the HTTP endpoint that handles MPP payments for agents and redirects browsers to a checkout flow that you control.

```javascript
import crypto from 'crypto'
import StripeClient from 'stripe'
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { Mppx, stripe } from 'mppx/server'
import { completeOrder, getItem, validatePurchase } from './catalog.js'

// Secret used to secure payment challenges
// https://mpp.dev/protocol/challenges#challenge-binding
const mppSecretKey = crypto
  .createHmac('sha256', process.env.STRIPE_SECRET_KEY)
  .update('mpp-challenge-signing')
  .digest('base64')

const stripeClient = new StripeClient(process.env.STRIPE_SECRET_KEY)

const stripeMachinePayments = stripe.create({
  client: stripeClient,
  networkId: process.env.STRIPE_NETWORK_ID,
  livemode: !process.env.STRIPE_SECRET_KEY.includes('_test_'),
  // Connect parameters — use whichever charge type matches your existing
  // Connect integration; don't mix the two.
  connect: {
    applicationFeeAmount: 250,

    // Direct charge — the PaymentIntent is created directly on the connected
    // account. Pass the connected account ID as `stripeAccount` instead of
    // `transferData`/`onBehalfOf`.
    stripeAccount: connectedAccountId,

    // Destination charge — the PaymentIntent is created on your platform
    // account and funds move to the connected account after the charge.
    // `onBehalfOf` is optional; set it if you want the connected account's
    // statement descriptor, MCC, and so on to apply to the charge.
    // transferData: { destination: connectedAccountId },
    // onBehalfOf: connectedAccountId,
  },
})

const mppx = Mppx.create({
  methods: [stripeMachinePayments.spt.charge()],
  secretKey: mppSecretKey,
})

// GET or POST /api/purchase?itemId=...&quantity=...
async function handler(request) {
  const url = new URL(request.url)
  const params = url.searchParams

  // Browser redirect — send humans to a checkout page you control
  const accept = request.headers.get('Accept') ?? ''
  if (accept.includes('text/html')) {
    return Response.redirect(new URL(
      `/checkout/${params.get('itemId')}?${params}`,
      process.env.BASE_URL,
    ))
  }

  // Agent/programmatic path — MPP 402-challenge flow
  const item = getItem(params.get('itemId'))
  if (!item) return Response.json({ error: 'Not found' }, { status: 404 })
  const quantity = Number(params.get('quantity'))
  const customerName = params.get('customerName')
  const customerEmail = params.get('customerEmail')
  if (!Number.isInteger(quantity) || quantity < 1 || !customerName || !customerEmail) {
    return Response.json({ error: 'Invalid purchase request' }, { status: 400 })
  }
  validatePurchase({ item, quantity, customerName, customerEmail })

  const result = await mppx.compose([
    'stripe/charge',
    {
      amount: ((item.priceCents * quantity) / 100).toFixed(2),
      currency: 'usd',
      decimals: 2,
      description: item.title,
    },
  ])(request)

  if (result.status === 402) return result.challenge

  // Payment confirmed — run your business logic
  const order = completeOrder({ item, quantity, customerName, customerEmail })
  return result.withReceipt(Response.json({ success: true, orderId: order.id }))
}

const app = new Hono()

app.on(['GET', 'POST'], '/api/purchase', (context) =>
  handler(context.req.raw),
)

serve({
  fetch: app.fetch,
  port: Number(process.env.PORT ?? 3000),
})
```

## Test your integration

You can test the full agent payment flow end to end by connecting your MCP server and the Link agent wallet to an agent harness that you control, such as [Claude Code](https://docs.claude.com/en/docs/claude-code), then asking the agent to complete a payment. When everything is configured correctly, the agent can use your MCP server and the Link agent wallet independently to discover your tool, call the payment endpoint, obtain a credential, and complete the purchase.

### Start your payment endpoint

Start the payment endpoint by running:

```bash
node --env-file=/absolute/path/to/.env /absolute/path/to/server.js
```

### Add your MCP server

Add your MCP server to an agent harness that you control.

#### Claude Code

Run the following command:

```bash
claude mcp add paid-catalog -- \
  node --env-file=/absolute/path/to/.env /absolute/path/to/mcp.js
```

#### Codex

Run the following command:

```bash
codex mcp add paid-catalog -- \
  node --env-file=/absolute/path/to/.env /absolute/path/to/mcp.js
```

#### Other

Add this local server configuration to your MCP client:

```json
{
  "mcpServers": {
    "paid-catalog": {
      "command": "node",
      "args": [
        "--env-file=/absolute/path/to/.env",
        "/absolute/path/to/mcp.js"
      ]
    }
  }
}
```

Consult your client’s documentation for the configuration file location.

### Add the Link agent wallet skill

Add the [Link agent wallet skill](https://github.com/stripe/link-cli) so the agent can provision a shared payment token to pay:

```bash
npx skills add stripe/link-cli
```

### Configure Test mode

Add an instruction to your agent’s context, such as in `CLAUDE.md` or the system prompt, that identifies the flow as a test. This signals the Link agent wallet to create shared payment tokens in a [sandbox](https://docs.stripe.com/sandboxes.md), which return test credentials and don’t charge the underlying payment method.

With that configuration in place, make sure that your agent passes the `--test` flag when it creates a spend request so the entire flow runs against test credentials end to end.

### Ask the agent to complete a payment

Prompt the agent to use your MCP server to make a purchase.

```bash
Use the `create_purchase_link` tool from the paid-catalog MCP to purchase one item with the ID `coffee` for a customer named `Alice` with the email `test@example.com`.

This flow uses test mode, so it doesn't move real funds. Make sure you use the test mode flag when you create the Link spend request.
```

When your integration is configured correctly, the agent calls your MCP tool, obtains a sandbox credential from the Link agent wallet, and completes the payment.

### Verify expected behaviour

Verify these behaviours during testing:

- The order is completed in your system.
- The payment succeeds.
- The link shows a payment interface when you open it in a browser.
- If you use [Connect](https://docs.stripe.com/connect.md), your integration routes funds to the expected connected account.

You can also test the payment endpoint directly, open it in a browser, or use the [Link CLI](https://github.com/stripe/link-cli) to make an MPP payment request manually.

## List your MCP server on the Stripe Directory

When you’re ready to share your MCP server externally, contact your Stripe representative or [Stripe Support](https://support.stripe.com/) with the server URL and relevant integration details to ask about listing it in the Stripe Directory.

## See also

- [Link agent wallet](https://github.com/stripe/link-cli)
- [Machine Payments Protocol (MPP)](https://docs.stripe.com/payments/machine/mpp.md)
- [Shared Payment Tokens (SPT)](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens.md)
