MCP Server Integration Guide
This guide shows you how to integrate SolvaPay SDK with Model Context Protocol (MCP) servers to protect MCP tools with paywall protection and subscription management.
Table of Contents
Installation
Install the required packages:
npm install @solvapay/server @modelcontextprotocol/sdk
# or
pnpm add @solvapay/server @modelcontextprotocol/sdk
# or
yarn add @solvapay/server @modelcontextprotocol/sdk
Basic Setup
1. Initialize SolvaPay
Create a SolvaPay instance in your MCP server:
import { createSolvaPay } from '@solvapay/server';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// Initialize SolvaPay
const solvaPay = createSolvaPay({
apiKey: process.env.SOLVAPAY_SECRET_KEY,
});
// Create payable handler for your agent
const payable = solvaPay.payable({
agent: 'agt_YOUR_AGENT_ID',
plan: 'pln_YOUR_PLAN_ID', // Optional: can be set per tool
});
2. Create MCP Server
Set up your MCP server:
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
// Create MCP server
const server = new Server(
{
name: 'solvapay-protected-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
3. Define Tools
Define your MCP tools:
const tools: Tool[] = [
{
name: 'create_task',
description: 'Create a new task (requires subscription)',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the task',
},
auth: {
type: 'object',
description: 'Authentication information',
properties: {
customer_ref: { type: 'string' },
},
required: ['customer_ref'],
},
},
required: ['title', 'auth'],
},
},
];
Protecting MCP Tools
Basic Tool Protection
Wrap your tool handlers with payable.mcp():
// Your business logic function
async function createTask(args: { title: string; auth: { customer_ref: string } }) {
const { title } = args;
// Your business logic here
const task = {
id: Date.now().toString(),
title,
createdAt: new Date().toISOString(),
};
return {
success: true,
task,
};
}
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'create_task': {
// Protect the tool with paywall
const handler = payable.mcp(createTask);
return await handler(args);
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
Multiple Tools with Same Plan
If all tools use the same plan, create one payable handler:
const payable = solvaPay.payable({
agent: 'agt_myapi',
plan: 'pln_premium',
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'create_task': {
const handler = payable.mcp(createTask);
return await handler(args);
}
case 'get_task': {
const handler = payable.mcp(getTask);
return await handler(args);
}
case 'list_tasks': {
const handler = payable.mcp(listTasks);
return await handler(args);
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
Different Plans per Tool
Create multiple payable handlers for different plans:
const freeTier = solvaPay.payable({
agent: 'agt_myapi',
plan: 'pln_free',
});
const premiumTier = solvaPay.payable({
agent: 'agt_myapi',
plan: 'pln_premium',
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'list_tasks': {
// Free tier tool
const handler = freeTier.mcp(listTasks);
return await handler(args);
}
case 'create_task': {
// Premium tier tool
const handler = premiumTier.mcp(createTask);
return await handler(args);
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
Authentication
Customer Reference in Tool Arguments
The MCP adapter expects customer reference in the auth object:
const tools: Tool[] = [
{
name: 'create_task',
description: 'Create a new task',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string' },
auth: {
type: 'object',
properties: {
customer_ref: { type: 'string' },
},
required: ['customer_ref'],
},
},
required: ['title', 'auth'],
},
},
];
Extract Customer Reference
The MCP adapter automatically extracts customer_ref from args.auth.customer_ref:
async function createTask(args: {
title: string;
auth: { customer_ref: string }
}) {
// customer_ref is automatically extracted by the adapter
// Your business logic here
return { success: true, task: {} };
}
const handler = payable.mcp(createTask);
Custom Customer Reference Extraction
Override customer reference extraction:
const handler = payable.mcp(createTask, {
getCustomerRef: (args: any) => {
// Custom logic to extract customer reference
return args.auth?.customer_ref || args.userId || null;
},
});
Error Handling
Basic Error Handling
The MCP adapter automatically handles PaywallError and converts it to MCP error format:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'create_task': {
const handler = payable.mcp(createTask);
return await handler(args);
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
// PaywallError is automatically handled by the adapter
// Other errors are re-thrown
throw error;
}
});
Custom Error Handling
Handle errors manually for custom error responses:
import { PaywallError } from '@solvapay/server';
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'create_task': {
const handler = payable.mcp(createTask);
return await handler(args);
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
if (error instanceof PaywallError) {
// Custom paywall error response
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'Payment required',
message: error.message,
checkoutUrl: error.structuredContent.checkoutUrl,
plan: error.structuredContent.plan,
agent: error.structuredContent.agent,
}),
},
],
isError: true,
};
}
// Handle other errors
throw error;
}
});
Complete Example
Here's a complete MCP server with SolvaPay integration:
import 'dotenv/config';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { createSolvaPay, PaywallError } from '@solvapay/server';
// Initialize SolvaPay
const solvaPay = createSolvaPay({
apiKey: process.env.SOLVAPAY_SECRET_KEY,
});
// Create payable handler
const payable = solvaPay.payable({
agent: 'agt_myapi',
plan: 'pln_premium',
});
// Define tools
const tools: Tool[] = [
{
name: 'create_task',
description: 'Create a new task (requires subscription)',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the task',
},
description: {
type: 'string',
description: 'Optional description of the task',
},
auth: {
type: 'object',
description: 'Authentication information',
properties: {
customer_ref: { type: 'string' },
},
required: ['customer_ref'],
},
},
required: ['title', 'auth'],
},
},
{
name: 'get_task',
description: 'Get a task by ID (requires subscription)',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'ID of the task to retrieve',
},
auth: {
type: 'object',
description: 'Authentication information',
properties: {
customer_ref: { type: 'string' },
},
required: ['customer_ref'],
},
},
required: ['id', 'auth'],
},
},
{
name: 'list_tasks',
description: 'List all tasks (requires subscription)',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of tasks to return (default: 10)',
},
offset: {
type: 'number',
description: 'Number of tasks to skip (default: 0)',
},
auth: {
type: 'object',
description: 'Authentication information',
properties: {
customer_ref: { type: 'string' },
},
required: ['customer_ref'],
},
},
},
},
];
// Business logic functions
async function createTask(args: {
title: string;
description?: string;
auth: { customer_ref: string };
}) {
const { title, description } = args;
const task = {
id: Date.now().toString(),
title,
description,
createdAt: new Date().toISOString(),
};
return {
success: true,
message: 'Task created successfully',
task,
};
}
async function getTask(args: {
id: string;
auth: { customer_ref: string };
}) {
const { id } = args;
// Simulate fetching from database
const task = {
id,
title: 'Sample Task',
description: 'Task description',
createdAt: new Date().toISOString(),
};
return {
success: true,
task,
};
}
async function listTasks(args: {
limit?: number;
offset?: number;
auth: { customer_ref: string };
}) {
const { limit = 10, offset = 0 } = args;
// Simulate fetching from database
const tasks = Array.from({ length: limit }, (_, i) => ({
id: (offset + i + 1).toString(),
title: `Task ${offset + i + 1}`,
description: `Description for task ${offset + i + 1}`,
createdAt: new Date().toISOString(),
}));
return {
success: true,
tasks,
total: tasks.length,
limit,
offset,
};
}
// Create MCP server
const server = new Server(
{
name: 'solvapay-protected-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Handle tool listing
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'create_task': {
const handler = payable.mcp(createTask);
const result = await handler(args);
return {
content: [
{
type: 'text',
text: JSON.stringify(result),
},
],
};
}
case 'get_task': {
const handler = payable.mcp(getTask);
const result = await handler(args);
return {
content: [
{
type: 'text',
text: JSON.stringify(result),
},
],
};
}
case 'list_tasks': {
const handler = payable.mcp(listTasks);
const result = await handler(args);
return {
content: [
{
type: 'text',
text: JSON.stringify(result),
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
if (error instanceof PaywallError) {
// Return paywall error in MCP format
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: 'Payment required',
message: error.message,
checkoutUrl: error.structuredContent.checkoutUrl,
plan: error.structuredContent.plan,
agent: error.structuredContent.agent,
}),
},
],
isError: true,
};
}
// Re-throw other errors
throw error;
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('🚀 SolvaPay Protected MCP Server started');
console.error('📝 Available tools: create_task, get_task, list_tasks');
console.error('💰 Paywall protection enabled');
}
main().catch((error) => {
console.error('Failed to start MCP server:', error);
process.exit(1);
});
Testing the Example
- Save the code to
src/index.ts - Set environment variable:
SOLVAPAY_SECRET_KEY=sk_... - Run the server:
node dist/index.js
The server will listen on stdio and respond to MCP protocol messages.
Tool Response Format
The MCP adapter automatically formats responses. Your business logic should return:
- Object: Automatically converted to MCP response format
- Error: Thrown as exception (PaywallError handled automatically)
async function createTask(args: any) {
// Return object - automatically formatted
return { success: true, task: {} };
// Or throw error - automatically handled
if (!args.title) {
throw new Error('Title is required');
}
}
Best Practices
-
Customer Reference: Always require
customer_refin theauthobject for tool arguments. -
Error Handling: Handle
PaywallErrorappropriately to provide clear error messages to MCP clients. -
Tool Documentation: Provide clear descriptions in tool schemas so users understand what each tool does.
-
Type Safety: Use TypeScript for better type safety and developer experience.
-
Environment Variables: Store API keys in environment variables, not in code.
-
Tool Naming: Use clear, descriptive names for your tools.
Next Steps
- Express.js Integration Guide - Learn HTTP framework integration
- Error Handling Strategies - Advanced error handling patterns
- Custom Authentication Adapters - Build custom auth adapters
- API Reference - Full API documentation