This repository is archived. MCP functionality has been merged into the main x402-go library:
Please use the main library for all future development. This repository is no longer maintained.
x402 payment protocol support for MCP-Go clients and servers.
This library provides:
- Client Transport: Automatic x402 payment handling for MCP clients
- Server Wrapper: Payment collection support for MCP servers
- 🔌 Drop-in replacement: Fully compatible with mcp-go transport interface
- 💰 Automatic payments: Handles 402 responses transparently
- 🔐 Multiple signers: Support for private keys, mnemonics, keystores
- 🎯 Payment control: Optional callback for payment approval
- 🧪 Testing support: Mock signers and payment recorders for easy testing
- 💳 Payment collection: Require payments for specific MCP tools
- 🔒 Payment verification: Automatic verification via x402 facilitator
- ⛓️ On-chain settlement: Automatic settlement of verified payments
- 🎛️ Flexible pricing: Set different prices for different tools
- 🔄 Mixed mode: Support both free and paid tools on same server
go get github.com/mark3labs/mcp-go-x402package main
import (
"context"
"log"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
x402 "github.com/mark3labs/mcp-go-x402"
)
func main() {
signer, err := x402.NewPrivateKeySigner(
"YOUR_PRIVATE_KEY_HEX",
x402.AcceptUSDCBase(),
)
if err != nil {
log.Fatal(err)
}
transport, err := x402.New(x402.Config{
ServerURL: "https://paid-mcp-server.example.com",
Signers: []x402.PaymentSigner{signer},
})
if err != nil {
log.Fatal(err)
}
mcpClient := client.NewClient(transport)
ctx := context.Background()
if err := mcpClient.Start(ctx); err != nil {
log.Fatal(err)
}
defer mcpClient.Close()
_, err = mcpClient.Initialize(ctx, mcp.InitializeRequest{
Params: mcp.InitializeParams{
ProtocolVersion: "1.0.0",
ClientInfo: mcp.Implementation{
Name: "x402-client",
Version: "1.0.0",
},
},
})
if err != nil {
log.Fatal(err)
}
tools, _ := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
log.Printf("Found %d tools", len(tools.Tools))
}package main
import (
"context"
"log"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
x402server "github.com/mark3labs/mcp-go-x402/server"
)
func main() {
// Configure x402 server
config := &x402server.Config{
FacilitatorURL: "https://facilitator.x402.rs",
VerifyOnly: false, // Set to true for testing without settlement
}
// Create x402 server
srv := x402server.NewX402Server("my-server", "1.0.0", config)
// Add a free tool
srv.AddTool(
mcp.NewTool("free-tool",
mcp.WithDescription("This tool is free")),
freeToolHandler,
)
// Add a paid tool with multiple payment options
srv.AddPayableTool(
mcp.NewTool("premium-tool",
mcp.WithDescription("Premium feature"),
mcp.WithString("input", mcp.Required())),
premiumToolHandler,
x402server.RequireUSDCBase("0xYourWallet", "10000", "Premium feature"),
x402server.RequireUSDCBaseSepolia("0xYourWallet", "5000", "Premium feature (testnet)"),
)
// Start server
log.Println("Starting x402 MCP server on :8080")
if err := srv.Start(":8080"); err != nil {
log.Fatal(err)
}
}
func freeToolHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return &mcp.CallToolResult{
Content: []mcp.Content{mcp.NewTextContent("Free response")},
}, nil
}
func premiumToolHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
input := req.GetString("input", "")
// Process premium request
return &mcp.CallToolResult{
Content: []mcp.Content{mcp.NewTextContent("Premium response for: " + input)},
}, nil
}The library supports Solana payments using SPL tokens in addition to EVM-based payments.
// Create Solana signer with base58 private key
signer, err := x402.NewSolanaPrivateKeySigner(
privateKeyBase58,
x402.AcceptUSDCSolanaDevnet(),
)
// Or load from Solana CLI keypair file
signer, err := x402.NewSolanaPrivateKeySignerFromFile(
"~/.config/solana/id.json",
x402.AcceptUSDCSolana().WithPriority(2),
)
// Use with transport
transport, err := x402.New(x402.Config{
ServerURL: "http://localhost:8080",
Signers: []x402.PaymentSigner{signer},
})srv.AddPayableTool(
mcp.NewTool("premium-search",
mcp.WithDescription("Premium search on Solana")),
searchHandler,
x402server.RequireUSDCSolanaDevnet(
recipientAddress,
"1000000",
"Premium search access",
),
)evmSigner, _ := x402.NewPrivateKeySigner(
evmPrivateKey,
x402.AcceptUSDCBaseSepolia().WithPriority(1),
)
solSigner, _ := x402.NewSolanaPrivateKeySigner(
solPrivateKey,
x402.AcceptUSDCSolanaDevnet().WithPriority(2),
)
transport, _ := x402.New(x402.Config{
ServerURL: serverURL,
Signers: []x402.PaymentSigner{evmSigner, solSigner},
})See examples/svm-client and examples/svm-server for complete working examples.
signer, err := x402.NewPrivateKeySigner(
privateKey,
x402.AcceptUSDCBase(),
x402.AcceptUSDCBaseSepolia(),
)
config := x402.Config{
ServerURL: "https://server.example.com",
Signers: []x402.PaymentSigner{signer},
}config := x402.Config{
ServerURL: "https://server.example.com",
Signers: []x402.PaymentSigner{signer},
PaymentCallback: func(amount *big.Int, resource string) bool {
if amount.Cmp(big.NewInt(100000)) > 0 {
fmt.Printf("Approve payment of %s for %s? ", amount, resource)
return getUserApproval()
}
return true
},
}config := x402.Config{
ServerURL: "https://server.example.com",
Signers: []x402.PaymentSigner{signer},
OnPaymentAttempt: func(event x402.PaymentEvent) {
log.Printf("Attempting payment: %s to %s", event.Amount, event.Recipient)
},
OnPaymentSuccess: func(event x402.PaymentEvent) {
log.Printf("Payment successful: tx %s", event.Transaction)
},
OnPaymentFailure: func(event x402.PaymentEvent, err error) {
log.Printf("Payment failed: %v", err)
},
}Configure multiple signers with different payment options and priorities. The client will try signers in priority order until one succeeds:
// Create personal wallet for small payments
personalSigner, _ := x402.NewPrivateKeySigner(
personalKey,
x402.AcceptUSDCBase().WithMaxAmount("50000"), // Max 0.05 USDC
)
personalSigner.WithPriority(1) // Try first
// Create business wallet for larger payments
businessSigner, _ := x402.NewPrivateKeySigner(
businessKey,
x402.AcceptUSDCBase(), // No limit
)
businessSigner.WithPriority(2) // Fallback
config := x402.Config{
ServerURL: "https://server.example.com",
Signers: []x402.PaymentSigner{personalSigner, businessSigner},
}// Mainnet signer
mainnetSigner, _ := x402.NewPrivateKeySigner(
mainnetKey,
x402.AcceptUSDCBase(),
)
// Testnet signer for development
testnetSigner, _ := x402.NewPrivateKeySigner(
testnetKey,
x402.AcceptUSDCBaseSepolia(),
)
config := x402.Config{
ServerURL: "https://server.example.com",
Signers: []x402.PaymentSigner{mainnetSigner, testnetSigner},
}config := &x402server.Config{
FacilitatorURL: "https://facilitator.x402.rs",
VerifyOnly: false, // Set to true for testing without settlement
}Servers can now offer multiple payment options per tool, allowing clients to choose their preferred network or take advantage of discounts:
srv.AddPayableTool(
mcp.NewTool("analytics",
mcp.WithDescription("Advanced analytics"),
mcp.WithString("query", mcp.Required())),
analyticsHandler,
// Base mainnet - cheapest option
x402server.RequireUSDCBase("0xYourWallet", "50000", "Analytics via Base - 0.05 USDC"),
// Polygon mainnet - moderate fees
x402server.RequireUSDCPolygon("0xYourWallet", "75000", "Analytics via Polygon - 0.075 USDC"),
// Avalanche mainnet
x402server.RequireUSDCAvalanche("0xYourWallet", "100000", "Analytics via Avalanche - 0.1 USDC"),
// Testnet options for development
x402server.RequireUSDCBaseSepolia("0xYourWallet", "10000", "Analytics via Base Sepolia (testnet) - 0.01 USDC"),
x402server.RequireUSDCPolygonAmoy("0xYourWallet", "10000", "Analytics via Polygon Amoy (testnet) - 0.01 USDC"),
x402server.RequireUSDCAvalancheFuji("0xYourWallet", "10000", "Analytics via Avalanche Fuji (testnet) - 0.01 USDC"),
)Mainnet:
RequireUSDCBase(payTo, amount, description)- Base mainnetRequireUSDCPolygon(payTo, amount, description)- Polygon mainnetRequireUSDCAvalanche(payTo, amount, description)- Avalanche C-Chain mainnetRequireUSDCSolana(payTo, amount, description)- Solana mainnet
Testnet:
RequireUSDCBaseSepolia(payTo, amount, description)- Base Sepolia testnetRequireUSDCPolygonAmoy(payTo, amount, description)- Polygon Amoy testnetRequireUSDCAvalancheFuji(payTo, amount, description)- Avalanche Fuji testnetRequireUSDCSolanaDevnet(payTo, amount, description)- Solana devnet
When a client requests a paid tool without payment, they receive all available payment options and can choose the one that works best for them based on:
- Network preference (gas fees, speed)
- Available balance on different chains
- Price differences (discounts for certain networks)
// If you already have an MCP server, wrap it with x402
mcpServer := server.NewMCPServer("existing", "1.0")
httpServer := server.NewStreamableHTTPServer(mcpServer)
// Wrap with x402 handler
x402Handler := x402server.NewX402Handler(httpServer, config)
// Use as http.Handler
http.Handle("/", x402Handler)
http.ListenAndServe(":8080", nil)signer, err := x402.NewPrivateKeySigner(
"0xYourPrivateKeyHex",
x402.AcceptUSDCBase(), // Must specify at least one payment option
)signer, err := x402.NewMnemonicSigner(
"your twelve word mnemonic phrase here ...",
"m/44'/60'/0'/0/0", // Optional: derivation path
x402.AcceptUSDCBase(),
)keystoreJSON, _ := os.ReadFile("keystore.json")
signer, err := x402.NewKeystoreSigner(
keystoreJSON,
"password",
x402.AcceptUSDCBase(),
)signer, err := x402.NewSolanaPrivateKeySigner(
"YourBase58PrivateKey",
x402.AcceptUSDCSolana(), // Mainnet
// x402.AcceptUSDCSolanaDevnet(), // Or devnet for testing
)// Load from Solana CLI keypair file
signer, err := x402.NewSolanaPrivateKeySignerFromFile(
"~/.config/solana/id.json",
x402.AcceptUSDCSolanaDevnet(),
)signer, err := x402.NewPrivateKeySigner(
privateKey,
// Priority 1: Prefer Base (cheap & fast)
x402.AcceptUSDCBase().WithPriority(1),
// Priority 2: Polygon (moderate fees)
x402.AcceptUSDCPolygon().WithPriority(2),
// Priority 3: Avalanche
x402.AcceptUSDCAvalanche().WithPriority(3),
// Priority 4: Fallback to testnets
x402.AcceptUSDCBaseSepolia().WithPriority(4),
x402.AcceptUSDCPolygonAmoy().WithPriority(5),
x402.AcceptUSDCAvalancheFuji().WithPriority(6),
)- Base:
x402.AcceptUSDCBase()- Chain ID: 8453 - Polygon:
x402.AcceptUSDCPolygon()- Chain ID: 137 - Avalanche C-Chain:
x402.AcceptUSDCAvalanche()- Chain ID: 43114
- Base Sepolia:
x402.AcceptUSDCBaseSepolia()- Chain ID: 84532 - Polygon Amoy:
x402.AcceptUSDCPolygonAmoy()- Chain ID: 80002 - Avalanche Fuji:
x402.AcceptUSDCAvalancheFuji()- Chain ID: 43113
- Solana Mainnet:
x402.AcceptUSDCSolana() - Solana Devnet:
x402.AcceptUSDCSolanaDevnet()
signer, err := x402.NewPrivateKeySigner(
privateKey,
x402.AcceptUSDCBase()
.WithMaxAmount("100000") // Max 0.1 USDC per payment
.WithMinBalance("1000000"), // Keep 1 USDC reserve
)type MyCustomSigner struct {
paymentOptions []x402.ClientPaymentOption
}
func NewMyCustomSigner() *MyCustomSigner {
return &MyCustomSigner{
paymentOptions: []x402.ClientPaymentOption{
x402.AcceptUSDCBase(),
x402.AcceptUSDCBaseSepolia(),
},
}
}
func (s *MyCustomSigner) SignPayment(ctx context.Context, req x402.PaymentRequirement) (*x402.PaymentPayload, error) {
// Your custom signing logic
// Could use hardware wallet, remote signer, etc.
}
func (s *MyCustomSigner) GetAddress() string {
return "0xYourAddress"
}
func (s *MyCustomSigner) SupportsNetwork(network string) bool {
for _, opt := range s.paymentOptions {
if opt.Network == network {
return true
}
}
return false
}
func (s *MyCustomSigner) HasAsset(asset, network string) bool {
for _, opt := range s.paymentOptions {
if opt.Network == network && opt.Asset == asset {
return true
}
}
return false
}
func (s *MyCustomSigner) GetPaymentOption(network, asset string) *x402.ClientPaymentOption {
for _, opt := range s.paymentOptions {
if opt.Network == network && opt.Asset == asset {
optCopy := opt
return &optCopy
}
}
return nil
}func TestMyMCPClient(t *testing.T) {
// No real wallet needed for tests
signer := x402.NewMockSigner(
"0xTestWallet",
x402.AcceptUSDCBaseSepolia(), // Mock signer for testing
)
transport, _ := x402.New(x402.Config{
ServerURL: "https://test-server.example.com",
Signers: []x402.PaymentSigner{signer},
})
// Test your MCP client
client := client.NewClient(transport)
// ...
}func TestSolanaClient(t *testing.T) {
signer := x402.NewMockSolanaSigner(
"DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
x402.AcceptUSDCSolanaDevnet(),
)
transport, _ := x402.New(x402.Config{
ServerURL: "https://test-server.example.com",
Signers: []x402.PaymentSigner{signer},
})
// ...
}func TestPaymentFlow(t *testing.T) {
// Create mock signer and recorder
signer := x402.NewMockSigner(
"0xTestWallet",
x402.AcceptUSDCBaseSepolia(),
)
recorder := x402.NewPaymentRecorder()
transport, _ := x402.New(x402.Config{
ServerURL: testServer.URL,
Signers: []x402.PaymentSigner{signer},
})
// Attach the recorder using the helper function
x402.WithPaymentRecorder(recorder)(transport)
// Make requests...
// Verify payments
assert.Equal(t, 2, recorder.PaymentCount()) // Attempt + Success events
lastPayment := recorder.LastPayment()
assert.Equal(t, x402.PaymentEventSuccess, lastPayment.Type)
assert.Equal(t, "20000", recorder.TotalAmount())
}base- Base Mainnet (viaAcceptUSDCBase(),RequireUSDCBase())base-sepolia- Base Sepolia Testnet (viaAcceptUSDCBaseSepolia(),RequireUSDCBaseSepolia())
solana- Solana Mainnet (viaAcceptUSDCSolana(),RequireUSDCSolana())solana-devnet- Solana Devnet (viaAcceptUSDCSolanaDevnet(),RequireUSDCSolanaDevnet())
Additional networks can be supported by manually configuring ClientPaymentOption or PaymentRequirement objects with the appropriate network, asset, and scheme parameters.
- Private keys: Never hardcode private keys. Use environment variables or secure key management.
- Payment approval: Use
PaymentCallbackto control payment approval based on amount or resource. - Network verification: The library verifies network and asset compatibility before signing.
- Per-option limits: Use
.WithMaxAmount()on payment options to set per-network spending limits.
- Chain ID verification: Signers verify the chain ID matches the payment option configuration.
- EIP-712 signing: All EVM signatures use typed structured data per EIP-712.
- Blockhash expiration: Solana transactions expire after ~60-90 seconds. The facilitator must submit quickly.
- Fee payer trust: The facilitator acts as the fee payer and adds their signature before submission.
- Transaction inspection: Clients should validate the transaction matches the payment requirement before signing.
See the examples directory for more detailed examples:
- Client - Simple client that can pay for tool use with EVM chains
- Server - Server that collects payments on EVM chains
- SVM Client - Client that uses Solana for payments
- SVM Server - Server that collects payments on Solana
- Client makes MCP request through x402 transport
- If server returns 402 Payment Required, transport extracts payment requirements
- Transport uses configured signer to create payment authorization
- Transport retries request with X-PAYMENT header
- Server verifies and settles payment, then returns response
- Server receives MCP request
- Checks if requested tool requires payment
- If no payment provided, returns 402 with payment requirements
- If payment provided, verifies with facilitator service
- Settles payment on-chain (unless in verify-only mode)
- Executes tool and returns response with payment confirmation
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details.
- MCP-Go for the excellent MCP implementation
- x402 Protocol (GitHub) for the payment specification
- go-ethereum for EVM crypto utilities
- solana-go for Solana transaction construction and signing