> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nevermined.app/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP

> Learn how to protect your MCP servers with Nevermined Payments Library.

<Note>
  **Are you building with an LLM?**

  If you are using an AI assistant for development, this page provides comprehensive context to streamline the integration process. Simply share this documentation URL with your LLM or copy the page content as a knowledge base.
</Note>

Nevermined Payments Library provides robust tools to add a paywall to your [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/) 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 payment 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 payment token 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:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Payments } from "@nevermined-io/payments"
    import { z } from "zod"

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

    // 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,
      planId: process.env.NVM_PLAN_ID!,    // required
      agentId: process.env.NVM_AGENT_ID,   // optional — informational only
      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)
    })
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    from mcp.server.fastmcp import FastMCP
    from payments_py import Payments, PaymentOptions

    # 1. Initialize Payments
    payments = Payments.get_instance(
        PaymentOptions(
            nvm_api_key=os.environ["NVM_API_KEY"],
            environment=os.environ.get("NVM_ENVIRONMENT", "sandbox")
        )
    )

    # 2. Create MCP server with tool
    fastmcp = FastMCP(name="weather-mcp", json_response=True)

    @fastmcp.tool(name="weather.today", title="Today's Weather")
    async def weather_today(city: str) -> str:
        return f"Weather for {city}: Sunny, 25C."

    # 3. Start with Nevermined paywall
    payments.mcp.start(
        fastmcp=fastmcp,
        plan_id=os.environ["NVM_PLAN_ID"],      # required
        agent_id=os.environ.get("NVM_AGENT_ID"),  # optional — informational only
        port=3000
    )
    ```

    <Note>
      Requires `payments-py[mcp]`: `pip install payments-py[mcp] python-dotenv`
    </Note>
  </Tab>
</Tabs>

**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:

* **GitHub**: [weather-mcp/src/server/main.ts](https://github.com/nevermined-io/tutorials/tree/main/mcp-examples/weather-mcp)

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:

```typescript theme={null}
// 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:

```typescript theme={null}
// As a subscriber
// Create a delegation once, then reuse its id to mint tokens.
const { delegationId } = await paymentsClient.delegation.createDelegation({
  provider: 'erc4337',       // 'stripe' | 'braintree' | 'visa' for fiat plans
  spendingLimitCents: 10000, // $100 budget
  durationSecs: 604800,      // 7 days
  currency: 'usdc',          // 'usd' for fiat plans
})
const { accessToken } = await paymentsClient.x402.getX402AccessToken(
  planId,    // The plan they subscribed to
  undefined, // agentId is optional — informational only
  // scheme defaults to 'nvm:erc4337'; pass 'nvm:card-delegation' for fiat plans
  { scheme: 'nvm:erc4337', delegationConfig: { delegationId } }
)
```

**Step 2: Connect with MCP Client**

Payment is carried **in band** in the tool-call `_meta["x402/payment"]` field, per the [x402 v2 MCP transport spec](https://github.com/coinbase/x402/blob/main/specs/transports-v2/mcp.md). Decode the access token into its `PaymentPayload` object and attach it to each paid call:

```typescript theme={null}
import { Client } from "@modelcontextprotocol/sdk/client"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp"
import { decodeAccessToken } from "@nevermined-io/payments"

const transport = new StreamableHTTPClientTransport(
  new URL("http://localhost:3000/mcp"),
  // The MCP session is an OAuth-protected resource: authenticate it with the
  // access token (without it, `initialize` returns 401). The per-call payment
  // is sent in band via `_meta` below.
  { requestInit: { headers: { Authorization: `Bearer ${accessToken}` } } }
)

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

// Carry the payment in band on each paid tool call
const result = await client.callTool({
  name: "weather.today",
  arguments: { city: "Madrid" },
  _meta: { "x402/payment": decodeAccessToken(accessToken) }
})
```

<Note>
  The `Authorization: Bearer <accessToken>` header authenticates the MCP **session** (it's an OAuth-protected resource — `initialize` returns `401` without it), which is why it's set on the transport above. The **payment** is sent separately, in band, via `_meta["x402/payment"]`. For backwards compatibility the server also reads the payment from the header alone (no `_meta`) as a **deprecated fallback** for one release; new integrations should use the in-band `_meta` field.
</Note>

**Step 3: Use in Claude Desktop**

Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json theme={null}
{
  "mcpServers": {
    "weather": {
      "url": "http://localhost:3000/mcp",
      "auth": {
        "type": "bearer",
        "token": "YOUR_ACCESS_TOKEN_HERE"
      }
    }
  }
}
```

***

## Advanced Usage: Low-Level APIs

<Note>
  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()`.
</Note>

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

<Tabs>
  <Tab title="TypeScript">
    * Node.js >= 18
    * `@nevermined-io/payments` (includes everything needed)
    * `zod` for schema validation

    ```bash theme={null}
    npm install @nevermined-io/payments zod dotenv
    npm install -D typescript @types/node ts-node
    ```
  </Tab>

  <Tab title="Python">
    * Python >= 3.10
    * `payments-py[mcp]` (includes MCP SDK, Uvicorn, and Nevermined SDK)

    ```bash theme={null}
    pip install payments-py[mcp] python-dotenv
    ```
  </Tab>
</Tabs>

### 1) Create a Minimal MCP Server

First, let's create a basic MCP server without any paywall to ensure the core setup is working.

<Tabs>
  <Tab title="TypeScript">
    This server uses the official MCP SDK and exposes a single tool, `weather.today`.

    ```typescript theme={null}
    // 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;
    }
    ```
  </Tab>

  <Tab title="Python">
    This server uses `FastMCP` from the `mcp` Python library to expose the `weather.today` tool.

    ```python theme={null}
    # app_fastmcp.py
    from mcp.server.fastmcp import FastMCP

    fastmcp = FastMCP(name="weather-mcp", json_response=True)

    @fastmcp.tool(name="weather.today", title="Today's Weather")
    async def weather_today(city: str) -> str:
        return f"Weather for {city}: Sunny, 25C."
    ```
  </Tab>
</Tabs>

### 2) Initialize Nevermined Payments

Initialize the Nevermined Payments SDK with your builder/agent owner API key and environment.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // 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) || "sandbox",
    });
    ```

    <Note>
      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.
    </Note>
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # payments_setup.py
    from payments_py import Payments, PaymentOptions

    payments = Payments.get_instance(
        PaymentOptions(nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENV", "sandbox"))
    )
    ```
  </Tab>
</Tabs>

### 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:

```typescript theme={null}
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

<Tabs>
  <Tab title="Based on Response Size">
    Charge credits proportional to the response length:

    ```typescript theme={null}
    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 }
    )
    ```
  </Tab>

  <Tab title="Based on Input Arguments">
    Charge different rates based on input complexity:

    ```typescript theme={null}
    const dynamicCredits = (ctx: CreditsContext): bigint => {
      // Access input arguments
      const args = ctx.args as { city: string; forecast_days?: number }
      
      // Base cost: 5 credits
      let credits = 5n
      
      // Add 2 credits per additional forecast day
      if (args.forecast_days && args.forecast_days > 1) {
        credits += BigInt((args.forecast_days - 1) * 2)
      }
      
      return credits
    }

    payments.mcp.registerTool(
      "weather.forecast",
      config,
      handler,
      { credits: dynamicCredits }
    )
    ```
  </Tab>

  <Tab title="Conditional Pricing">
    Apply different pricing models based on result type:

    ```typescript theme={null}
    const dynamicCredits = (ctx: CreditsContext): bigint => {
      const result = ctx.result as { 
        structuredContent?: any
        rawData?: any 
      }
      
      // Structured responses cost more
      if (result.structuredContent) {
        return 10n
      }
      
      // Raw data costs less
      if (result.rawData) {
        return 3n
      }
      
      // Default
      return 5n
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    def dynamic_credits(ctx: Dict[str, Any]) -> int:
        # Access arguments
        args = ctx.get("args", {})
        city = args.get("city", "")
        
        # Access result
        result = ctx.get("result", {})
        content = result.get("content", [])
        text = content[0].get("text", "") if content else ""
        
        # Calculate based on response length
        return max(1, len(text) // 100)

    payments.mcp.registerTool(
        "weather.today",
        config,
        handler,
        {"credits": dynamic_credits}
    )
    ```
  </Tab>
</Tabs>

**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`

```json theme={null}
{
  "mcpServers": {
    "weather": {
      "url": "http://localhost:3000/mcp",
      "type": "http"
    }
  }
}
```

#### Cursor IDE

Add to your Cursor MCP config file:

* **Location**: `~/.cursor/mcp.json`

```json theme={null}
{
  "mcpServers": {
    "weather": {
      "url": "http://localhost:3000/mcp",
      "type": "http"
    }
  }
}
```

#### Custom MCP Client (TypeScript/JavaScript)

```typescript theme={null}
import { Client } from "@modelcontextprotocol/sdk/client"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp"
import { decodeAccessToken } from "@nevermined-io/payments"

// The MCP session is an OAuth-protected resource: authenticate it with the
// access token (without it, `initialize` returns 401). See "Client Usage" above
// for how to obtain `accessToken`.
const transport = new StreamableHTTPClientTransport(
  new URL("http://localhost:3000/mcp"),
  { requestInit: { headers: { Authorization: `Bearer ${accessToken}` } } }
)

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

// For PAID tools, carry the payment in band on each call.
const result = await client.callTool({
  name: "weather.today",
  arguments: { city: "Madrid" },
  _meta: { "x402/payment": decodeAccessToken(accessToken) }
})

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!

<Note>
  **For production servers**, replace `http://localhost:3000` with your public domain (e.g., `https://weather-mcp.yourdomain.com`).
</Note>

***

## Advanced Usage

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

### Using `withPaywall` for Custom Servers

If you're building a custom MCP server and want granular control, use `withPaywall`. Call `payments.mcp.configure({ planId })` first — `withPaywall` (and `attach`) require a `planId` and throw at registration time if none is set:

```typescript theme={null}
const protectedHandler = payments.mcp.withPaywall(
  myHandler,
  { 
    kind: "tool",
    name: "my.tool",
    credits: 5n
  }
)
```

### Using `attach` for Declarative Registration

```typescript theme={null}
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

<Warning>
  **Only for advanced use cases.** Most users should use `payments.mcp.start()`.
</Warning>

For custom Express servers, you can use `payments.mcp.createApp()`:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // custom-server.ts
    import { Payments } from "@nevermined-io/payments"

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

    payments.mcp.configure({
      planId: process.env.NVM_PLAN_ID!,    // required
      serverName: "custom-server",
      agentId: process.env.NVM_AGENT_ID,   // optional — informational only
    })

    const app = payments.mcp.createApp({
      planId: process.env.NVM_PLAN_ID!,    // required
      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")
    })
    ```
  </Tab>

  <Tab title="Python">
    For complete custom control with ASGI:

    ```python theme={null}
    # lowlevel_app.py
    from payments_py.mcp import build_extra_from_http_headers

    async def app(scope, receive, send):
        # Parse request
        headers = {k.decode(): v.decode() for k, v in scope.get("headers", [])}
        extra = build_extra_from_http_headers(headers)
        
        # Read body...
        body = await receive()
        req = json.loads(body.get("body", b"{}"))
        
        # Route and call handler
        if req.get("method") == "tools/call":
            handler = get_tool_handler(req["params"]["name"])
            result = await handler(req["params"]["arguments"], extra)
            # Send response...
    ```
  </Tab>
</Tabs>

***

## Error Handling

Payment-required is signaled **in band**, not as an HTTP `402`. For **tools**, a missing/invalid token (or unmet subscription) returns an **error tool result** — `isError: true` with the `PaymentRequired` object in `structuredContent` and its JSON string in `content[0].text` — rather than throwing. **Resources and prompts** have no tool-result error channel, so they raise a JSON-RPC error with the codes below (`-32003` is also exposed on the error object for tools).

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

***

## Full Code Examples

### Production-Ready Example

The **Weather MCP Server** is a complete, production-ready example that demonstrates all features:

* **Repository**: [tutorials/mcp-examples/weather-mcp](https://github.com/nevermined-io/tutorials/tree/main/mcp-examples/weather-mcp)
* **Main Server**: [weather-mcp/src/server/main.ts](https://github.com/nevermined-io/tutorials/blob/main/mcp-examples/weather-mcp/src/server/main.ts)

**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:

* **TypeScript (Low-level)**: [nevermined-io/weather-mcp-demo](https://github.com/nevermined-io/weather-mcp-demo) - Examples using `withPaywall` and `attach` for custom servers
* **Python**: [nevermined-io/weather-mcp-demo-py](https://github.com/nevermined-io/weather-mcp-demo-py) - Python implementation with FastMCP
