Skip to main content
Are you building with an LLM?If you are using an AI assistant for development, you can provide it with a comprehensive context file to streamline the integration process.Click here to view the raw context file. Simply copy its contents and paste them into your LLM’s input as a knowledge base.
Nevermined Payments Library provides robust tools to add a paywall to your Model Context Protocol (MCP) server. This allows you to monetize your AI tools, resources, and prompts by restricting access to subscribers with valid payment plans. The integration is designed to be seamless, whether you are using a high-level framework like the official MCP SDKs or a custom low-level JSON-RPC router.
  • Paywall Protection: Wrap your handlers with withPaywall to automatically verify Authorization tokens and check for valid subscriptions.
  • Credit Burning: Automatically burn credits after a successful API call, with support for both fixed and dynamic costs.
  • Declarative Registration: Use the attach method to register and protect your tools in a single, clean step.
  • Framework Agnostic: Works with both high-level servers (like the official TypeScript SDK’s McpServer or Python’s FastMCP) and custom low-level ASGI/Express routers.

What is MCP?

As Large Language Models (LLMs) and AI agents become more sophisticated, their greatest limitation is their isolation. By default, they lack access to real-time information, private data sources, or the ability to perform actions in the outside world. The Model Context Protocol (MCP) was designed to solve this problem by creating a standardized communication layer for AI. Think of MCP as a universal language that allows any AI agent to ask a server, “What can you do?” and “How can I use your capabilities?”. It turns a closed-off model into an agent that can interact with the world through a secure and discoverable interface. An MCP server essentially publishes a “menu” of its services, which can include:
  • Tools: These are concrete actions the agent can request, like sending an email, querying a database, or fetching a weather forecast. The agent provides specific arguments (e.g., city="Paris") and the server executes the action.
  • Resources: These are stable pointers to data, identified by a URI. While a tool call might give a human-readable summary, a resource link (weather://today/Paris) provides the raw, structured data (like a JSON object) that an agent can parse and use for further tasks.
  • Prompts: These are pre-defined templates that help guide an agent’s behavior, ensuring it requests information in the correct format or follows a specific interaction pattern.

Why integrate MCP with Nevermined Payments Library?

While MCP provides a powerful standard for what an AI agent can do, it doesn’t specify who is allowed to do it or how those services are paid for. This is where Nevermined Payments Library comes in. By integrating Nevermined, you can transform your open MCP server into a secure, monetizable platform. The core idea is to place a “paywall” in front of your MCP handlers. This paywall acts as a gatekeeper, intercepting every incoming request to a tool, resource, or prompt. Before executing your logic, it checks the user’s Authorization header to verify they have a valid subscription and sufficient credits through the Nevermined protocol. If they don’t, the request is blocked. If they do, the request proceeds, and after your handler successfully completes, the paywall automatically deducts the configured number of credits. This integration allows you to build a sustainable business model around your AI services. You can offer different subscription tiers (plans), charge dynamically based on usage, and maintain a complete audit trail of every transaction, all without cluttering your core application logic with complex payment code.

OAuth 2.1 Support for Remote MCP Servers

Nevermined Payments Library implements complete OAuth 2.1 authentication for MCP servers, acting as both:
  • OAuth Authorization Server (RFC 8414)
  • OAuth Protected Resource Server (RFC 8414)
  • OpenID Connect Provider (OIDC Discovery 1.0)
All OAuth discovery endpoints (/.well-known/oauth-authorization-server, /.well-known/oauth-protected-resource, /.well-known/openid-configuration) are automatically generated and configured. Your MCP server becomes instantly compatible with any OAuth-enabled MCP client (Claude Desktop, custom agents, etc.) without any manual OAuth setup.

Simplified API: Complete MCP Server in Minutes

The Nevermined Payments Library provides a high-level API that handles everything for you:
  • ✅ MCP Server creation
  • ✅ Express.js setup with CORS
  • ✅ OAuth 2.1 endpoints (auto-generated)
  • ✅ HTTP transport (POST/GET/DELETE /mcp)
  • ✅ Session management for streaming
  • ✅ Paywall protection for all tools/resources/prompts

Quick Example

Here’s how to create a complete, production-ready MCP server with OAuth authentication:
import { Payments } from "@nevermined-io/payments"
import { z } from "zod"

// 1. Initialize Payments
const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY!,
  environment: "staging"
})

// 2. Register tools with built-in paywall
payments.mcp.registerTool(
  "weather.today",
  {
    title: "Today's Weather",
    description: "Get weather for a city",
    inputSchema: z.object({ 
      city: z.string().min(2).max(80).describe("City name") 
    })
  },
  async (args, extra, context) => {
    // Access authentication context for observability
    console.log(`Request ID: ${context?.authResult.requestId}`)
    console.log(`Credits charged: ${context?.credits}`)
    
    // Your business logic here
    const weather = await fetchWeather(args.city)
    
    return {
      content: [{ 
        type: "text", 
        text: `Weather in ${args.city}: ${weather.description}, ${weather.temp}°C` 
      }]
    }
  },
  { credits: 5n }  // Fixed credits (5 per call)
)

// 3. Start everything (MCP Server + Express + OAuth)
const { info, stop } = await payments.mcp.start({
  port: 3000,
  agentId: process.env.NVM_AGENT_ID!,
  serverName: "my-weather-server",
  version: "1.0.0",
  description: "Weather MCP server with OAuth authentication"
})

console.log(`🚀 Server running at ${info.baseUrl}/mcp`)
console.log(`🔐 OAuth endpoints: ${info.baseUrl}/.well-known/`)
console.log(`🛠️  Tools: ${info.tools.join(", ")}`)

// Graceful shutdown
process.on("SIGINT", async () => {
  await stop()
  process.exit(0)
})
That’s it! Your MCP server is now:
  • ✅ Running with OAuth 2.1 authentication
  • ✅ Protected by Nevermined paywall
  • ✅ Compatible with Claude Desktop and any MCP client
  • ✅ Monetizable with automatic credit deduction

What payments.mcp.start() Does for You

This single function call handles:
  1. Express Server Setup: Creates and configures an Express.js application
  2. OAuth Endpoints: Auto-generates RFC-compliant discovery endpoints:
    • /.well-known/oauth-authorization-server
    • /.well-known/oauth-protected-resource
    • /.well-known/openid-configuration
    • /register (Dynamic Client Registration - RFC 7591)
  3. MCP Transport: Sets up HTTP transport endpoints (POST/GET/DELETE /mcp)
  4. Session Management: Handles SSE streaming and session lifecycle
  5. CORS & Middleware: Configures CORS, JSON parsing, and HTTP logging
  6. Graceful Shutdown: Returns a stop() function for clean server termination

Complete Working Example

See the full production-ready example with advanced features: This example includes:
  • Dynamic credit calculation based on response size
  • Resources with URI templates (weather://today/{city})
  • Prompts for LLM guidance
  • OpenAI observability integration

Dynamic Credits

Instead of fixed credits, you can calculate them dynamically based on the handler’s result:
// Dynamic credits based on response complexity
const weatherCreditsCalculator = (ctx: CreditsContext): bigint => {
  const result = ctx.result as { structuredContent?: { forecast?: string } }
  const forecast = result?.structuredContent?.forecast || ""
  
  // Charge 1 credit for short forecasts, 2-19 for longer ones
  return forecast.length <= 100 ? 1n : BigInt(Math.floor(Math.random() * 18) + 2)
}

payments.mcp.registerTool(
  "weather.today",
  config,
  handler,
  { credits: weatherCreditsCalculator }  // Function instead of fixed value
)
Important:
  • Fixed credits: Calculated BEFORE handler execution (available in context.credits)
  • Dynamic credits: Calculated AFTER handler execution (based on ctx.result)

Client Usage

Step 1: Get Access Token Users need to subscribe to your agent’s plan and obtain an access token:
// As a subscriber
const { accessToken } = await paymentsClient.agents.getAgentAccessToken(
  planId,   // The plan they subscribed to
  agentId   // Your MCP server's agent ID
)
Step 2: Connect with MCP Client
import { Client } from "@modelcontextprotocol/sdk/client"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp"

const transport = new StreamableHTTPClientTransport(
  new URL("http://localhost:3000/mcp"),
  {
    requestInit: {
      headers: { Authorization: `Bearer ${accessToken}` }
    }
  }
)

const client = new Client({ name: "my-client" })
await client.connect(transport)

// Call tool
const result = await client.callTool({
  name: "weather.today",
  arguments: { city: "Madrid" }
})
Step 3: Use in Claude Desktop Add to Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
  "mcpServers": {
    "weather": {
      "url": "http://localhost:3000/mcp",
      "auth": {
        "type": "bearer",
        "token": "YOUR_ACCESS_TOKEN_HERE"
      }
    }
  }
}

Advanced Usage: Low-Level APIs

The sections below show advanced, low-level APIs for users who need custom server implementations or fine-grained control.For most use cases, we recommend using the simplified API above with payments.mcp.start().
This advanced tutorial covers low-level APIs for building custom MCP servers. These approaches give you full control over the server implementation, but require more manual setup compared to the simplified API.

0) Requirements & Installation

  • Node.js >= 18
  • @nevermined-io/payments (includes everything needed)
  • zod for schema validation
npm install @nevermined-io/payments zod dotenv
npm install -D typescript @types/node ts-node

1) Create a Minimal MCP Server

First, let’s create a basic MCP server without any paywall to ensure the core setup is working.
This server uses the official MCP SDK and exposes a single tool, weather.today.
// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export function createMcpServer() {
    const server = new McpServer({
        name: "weather-mcp",
        version: "1.0.0",
    });

    server.tool(
        "weather.today",
        { title: "Today's Weather", inputSchema: z.object({ city: z.string() }) },
        async (args) => ({
            content: [{ type: "text", text: `Weather for ${args.city}: Sunny, 25C.` }],
        })
    );
    return server;
}

2) Initialize Nevermined Payments

Initialize the Nevermined Payments SDK with your builder/agent owner API key and environment.
// payments-setup.ts
import { Payments, EnvironmentName } from "@nevermined-io/payments";

const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY!,
  environment: (process.env.NVM_ENVIRONMENT as EnvironmentName) || "staging_sandbox",
});
When using low-level APIs, you may need to call payments.mcp.configure() to set defaults. With the simplified API (payments.mcp.start()), configuration is handled automatically.

3) Dynamic Credits (Optional)

Instead of charging fixed credits, you can calculate them dynamically based on input arguments or response content.

Understanding CreditsContext

When using a function for credits, it receives a CreditsContext object with:
interface CreditsContext {
  args: unknown       // Input arguments passed to the tool/resource/prompt
  result: any         // The response returned by your handler
  request: {
    authHeader: string   // Authorization Bearer token
    logicalUrl: string   // Full MCP logical URL (e.g., "mcp://server/tools/weather")
    toolName: string     // Name of the tool/resource/prompt
  }
}
Key Properties:
  • ctx.args: The input arguments the user sent (e.g., { city: "London" })
  • ctx.result: The complete response your handler returned
  • ctx.request.toolName: The name of the tool being called
  • ctx.request.logicalUrl: The full logical URL for observability
  • ctx.request.authHeader: The auth token (for advanced logging)

Examples

Charge credits proportional to the response length:
import type { CreditsContext } from "@nevermined-io/payments"

const dynamicCredits = (ctx: CreditsContext): bigint => {
  // Access the result returned by your handler
  const result = ctx.result as { content: Array<{ text: string }> }
  const text = result.content[0]?.text || ""
  
  // Charge 1 credit per 100 characters
  const credits = Math.ceil(text.length / 100)
  return BigInt(credits)
}

payments.mcp.registerTool(
  "weather.today",
  config,
  handler,
  { credits: dynamicCredits }
)
Important:
  • Fixed credits (credits: 5n): Calculated BEFORE handler execution, available in context.credits
  • Dynamic credits (function): Calculated AFTER handler execution, based on ctx.args and ctx.result
  • Return type: Must return bigint (TypeScript) or int (Python)
  • ⚠️ Performance: Keep calculations lightweight to avoid delays

4) Connecting MCP Clients

Once your server is running with payments.mcp.start(), connecting clients is straightforward. Nevermined handles OAuth authentication automatically - clients just need to know the server URL.

Claude Desktop

Add to your Claude Desktop config file:
  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "weather": {
      "url": "http://localhost:3000/mcp",
      "type": "http"
    }
  }
}

Cursor IDE

Add to your Cursor MCP config file:
  • Location: ~/.cursor/mcp.json
{
  "mcpServers": {
    "weather": {
      "url": "http://localhost:3000/mcp",
      "type": "http"
    }
  }
}

Custom MCP Client (TypeScript/JavaScript)

import { Client } from "@modelcontextprotocol/sdk/client"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp"

// Simple connection - no auth headers needed
const transport = new StreamableHTTPClientTransport(
  new URL("http://localhost:3000/mcp")
)

const client = new Client({ name: "my-client", version: "1.0.0" })
await client.connect(transport)

// Use tools
const result = await client.callTool({
  name: "weather.today",
  arguments: { city: "Madrid" }
})

console.log(result)

How OAuth Works Behind the Scenes

When a client connects to your MCP server:
  1. Discovery: Client reads /.well-known/oauth-authorization-server to find OAuth endpoints
  2. Registration: Client registers itself via /register (dynamic client registration)
  3. Authorization: Client obtains access token via OAuth 2.1 flow
  4. Authenticated Requests: All subsequent MCP requests include the token automatically
You don’t need to handle any of this manually - payments.mcp.start() configures everything automatically!
For production servers, replace http://localhost:3000 with your public domain (e.g., https://weather-mcp.yourdomain.com).

Advanced Usage

The methods below are for advanced use cases requiring custom server setup. For most applications, use the simplified payments.mcp.start() approach shown above.

Using withPaywall for Custom Servers

If you’re building a custom MCP server and want granular control, use withPaywall:
const protectedHandler = payments.mcp.withPaywall(
  myHandler,
  { 
    kind: "tool",
    name: "my.tool",
    credits: 5n
  }
)

Using attach for Declarative Registration

const server = new McpServer({ name: "my-server", version: "1.0.0" })
const registrar = payments.mcp.attach(server)

registrar.registerTool(
  "weather.today",
  config,
  handler,
  { credits: 1n }
)

Custom Low-Level Server

Only for advanced use cases. Most users should use payments.mcp.start().
For custom Express servers, you can use payments.mcp.createApp():
// custom-server.ts
import { Payments } from "@nevermined-io/payments"

const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY!,
  environment: "staging_sandbox"
})

payments.mcp.configure({
  agentId: process.env.NVM_AGENT_ID!,
  serverName: "custom-server"
})

const app = payments.mcp.createApp({
  baseUrl: "http://localhost:3000",
  serverName: "custom-server",
  tools: ["weather.today"]
})

// Add your custom JSON-RPC routing
app.post("/mcp", async (req, res) => {
  const { method, params } = req.body
  
  // Build extra object from request
  const extra = {
    requestInfo: {
      headers: req.headers,
      method: req.method,
      url: req.url
    }
  }
  
  // Route to handler and pass extra
  if (method === "tools/call") {
    const handler = getToolHandler(params.name)
    const result = await handler(params.arguments, extra)
    res.json({ jsonrpc: "2.0", id: req.body.id, result })
  }
})

app.listen(3000, () => {
  console.log("Custom server running on http://localhost:3000")
})

Error Handling

Error CodeDescription
-32003Payment Required - No token / Invalid token / Insufficient credits
-32002Misconfiguration - Server setup error
-32603Internal Error - Handler execution failed

Full Code Examples

Production-Ready Example

The Weather MCP Server is a complete, production-ready example that demonstrates all features: Features demonstrated:
  • ✅ Complete OAuth 2.1 authentication with payments.mcp.start()
  • ✅ Tools with dynamic credit calculation based on response size
  • ✅ Resources with URI templates (weather://today/{city})
  • ✅ Prompts for LLM guidance
  • ✅ OpenAI integration with Nevermined observability
  • ✅ Graceful shutdown handling
  • ✅ Environment-based configuration

Additional Examples

Advanced and low-level API examples: