Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions examples/mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Payment-protected MCP tools using Server-Sent Events (SSE).

This example demonstrates:
- **Server**: SSE-based MCP server with free and paid tools
- **Client**: Connects to server and handles the payment flow
- **Client**: Connects to server and handles payment automatically via `McpClient`

The server and client run in separate terminals, communicating via SSE.

Expand Down Expand Up @@ -73,15 +73,9 @@ Available tools:
1. Calling free tool (echo)...
Result: Echo: Hello, world!

2. Calling paid tool without credential (premium_echo)...
Got error code: -32042
Challenge ID: abc123...

3. Creating payment credential...
Credential created for challenge: abc123...

4. Retrying with credential...
2. Calling paid tool (premium_echo)...
Result: ✨ Premium Echo ✨: Hello, premium! (paid by 0x..., tx: 0x...)
Receipt: success, ref=0x...
```

## Server Implementations
Expand Down
81 changes: 19 additions & 62 deletions examples/mcp-server/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
"""MCP client demonstrating the payment flow.
"""MCP client demonstrating automatic payment handling.

Connects to an already-running MCP server via SSE and demonstrates:
1. Calling a free tool (echo)
2. Calling a paid tool without credentials (gets -32042 error)
3. Parsing the challenge, creating a credential, and retrying
2. Calling a paid tool (premium_echo) with automatic payment

Uses McpClient to handle the payment flow automatically—no manual
challenge parsing or credential creation needed.

Usage:
# Terminal 1: Start the server
Expand All @@ -27,11 +29,7 @@
from mcp import ClientSession
from mcp.client.sse import sse_client

from mpp.extensions.mcp import (
CODE_PAYMENT_REQUIRED,
MCPChallenge,
MCPCredential,
)
from mpp.extensions.mcp import McpClient
from mpp.methods.tempo import ChargeIntent, TempoAccount, tempo

SERVER_URL = os.environ.get("MCP_SERVER_URL", "http://127.0.0.1:8000/sse")
Expand All @@ -57,69 +55,28 @@ async def run_client() -> None:
async with ClientSession(streams[0], streams[1]) as session:
await session.initialize()

tools = await session.list_tools()
# Wrap the session with automatic payment handling
client = McpClient(session, methods=[method])

tools = await client.list_tools()
print("Available tools:")
for tool in tools.tools:
print(f" - {tool.name}: {tool.description}")
print()

# 1. Free tool — works without payment
print("1. Calling free tool (echo)...")
result = await session.call_tool("echo", {"message": "Hello, world!"})
result = await client.call_tool("echo", {"message": "Hello, world!"})
print(f" Result: {result.content[0].text}")
print()

print("2. Calling paid tool without credential (premium_echo)...")
try:
result = await session.call_tool(
"premium_echo", {"message": "Hello, premium!"}
)
print(f" Result: {result.content[0].text}")
except Exception as e:
error_data = getattr(e, "error", None) or {}
error_code = (
error_data.get("code")
if isinstance(error_data, dict)
else getattr(error_data, "code", None)
)

print(f" Got error code: {error_code}")

if error_code == CODE_PAYMENT_REQUIRED:
data = (
error_data.get("data", {})
if isinstance(error_data, dict)
else getattr(error_data, "data", {})
)
challenges = (
data.get("challenges", []) if isinstance(data, dict) else []
)

if challenges:
challenge_data = challenges[0]
print(f" Challenge ID: {challenge_data.get('id', 'unknown')}")
print()

print("3. Creating payment credential...")
challenge = MCPChallenge.from_dict(challenge_data)
core_credential = await method.create_credential(
challenge.to_core()
)

mcp_credential = MCPCredential.from_core(
core_credential, challenge
)
print(f" Credential created for challenge: {challenge.id}")
print()

print("4. Retrying with credential...")
result = await session.call_tool(
"premium_echo",
{"message": "Hello, premium!"},
meta=mcp_credential.to_meta(),
)
print(f" Result: {result.content[0].text}")
else:
print(f" Unexpected error: {e}")
# 2. Paid tool — McpClient handles payment automatically
print("2. Calling paid tool (premium_echo)...")
result = await client.call_tool("premium_echo", {"message": "Hello, premium!"})
print(f" Result: {result.content[0].text}")
if result.receipt:
print(f" Receipt: {result.receipt.status}, ref={result.receipt.reference}")
print()


def main() -> None:
Expand Down
5 changes: 5 additions & 0 deletions src/mpp/extensions/mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ async def expensive_tool(query: str, *, credential, receipt) -> str:
"""

from mpp.extensions.mcp.capabilities import payment_capabilities
from mpp.extensions.mcp.client import (
McpClient,
McpToolResult,
PaymentOutcomeUnknownError,
)
from mpp.extensions.mcp.constants import (
CODE_MALFORMED_CREDENTIAL,
CODE_PAYMENT_REQUIRED,
Expand Down
Loading
Loading