Building MCP Applications: Pet Store
Let us show you how to build a complete MCP server and client application using a pet store scenario. You'll learn to create an MCP server that manages pet inventory and a client application that interacts with it through the Model Context Protocol.
Overview
In this section, we'll build:
- Pet Store MCP Server: A server that provides tools for managing pets, inventory, and customer orders
- AI Client Application: A client that uses the MCP server to help customers find and purchase pets
- Secure Integration: Implementation of authentication and authorization controls
- Testing and Debugging: Using MCP Inspector to validate the implementation
Prerequisites and Setup
Required Tools
- Node.js: Version 18 or later
- TypeScript: For type safety and better development experience
- MCP SDK: Official TypeScript SDK for MCP development
- Database: SQLite for simple data persistence
Project Structure
pet-store-mcp/
├── server/
│ ├── src/
│ │ ├── index.ts
│ │ ├── tools/
│ │ │ ├── petManagement.ts
│ │ │ ├── inventory.ts
│ │ │ └── orders.ts
│ │ ├── resources/
│ │ │ └── petDatabase.ts
│ │ └── database/
│ │ └── schema.sql
│ ├── package.json
│ └── tsconfig.json
├── client/
│ ├── src/
│ │ ├── index.ts
│ │ └── petStoreClient.ts
│ ├── package.json
│ └── tsconfig.json
└── README.md
Step 1: Setting Up the Development Environment
Initialize the Project
# Create project directory
mkdir pet-store-mcp
cd pet-store-mcp
# Create server directory
mkdir server
cd server
npm init -y
npm install @modelcontextprotocol/sdk sqlite3 @types/sqlite3
npm install -D typescript @types/node tsx
# Create client directory
cd ../
mkdir client
cd client
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node tsx
TypeScript Configuration
# server/tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Step 2: Creating the Pet Store MCP Server
Database Schema
-- server/src/database/schema.sql
CREATE TABLE IF NOT EXISTS pets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
species TEXT NOT NULL,
breed TEXT,
age INTEGER,
price DECIMAL(10,2),
description TEXT,
available BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_name TEXT NOT NULL,
customer_email TEXT NOT NULL,
pet_id INTEGER,
status TEXT DEFAULT 'pending',
total_amount DECIMAL(10,2),
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (pet_id) REFERENCES pets (id)
);
-- Sample data
INSERT INTO pets (name, species, breed, age, price, description) VALUES
('Buddy', 'Dog', 'Golden Retriever', 2, 1200.00, 'Friendly and energetic golden retriever puppy'),
('Whiskers', 'Cat', 'Persian', 1, 800.00, 'Beautiful long-haired Persian cat'),
('Charlie', 'Dog', 'Labrador', 3, 1000.00, 'Well-trained family dog, great with kids'),
('Luna', 'Cat', 'Siamese', 2, 600.00, 'Elegant Siamese cat with striking blue eyes'),
('Max', 'Dog', 'German Shepherd', 4, 1500.00, 'Intelligent and loyal working dog');
Pet Management Tools
// server/src/tools/petManagement.ts
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import Database from 'sqlite3';
export class PetManagementTools {
private db: Database.Database;
constructor(db: Database.Database) {
this.db = db;
}
// Tool: List available pets
getListPetsTool(): Tool {
return {
name: 'list_pets',
description: 'List all available pets in the store with filtering options',
inputSchema: {
type: 'object',
properties: {
species: {
type: 'string',
description: 'Filter by species (dog, cat, bird, etc.)',
enum: ['dog', 'cat', 'bird', 'fish', 'rabbit']
},
maxPrice: {
type: 'number',
description: 'Maximum price filter'
},
availableOnly: {
type: 'boolean',
description: 'Show only available pets',
default: true
}
}
}
};
}
async listPets(args: any): Promise {
return new Promise((resolve, reject) => {
let query = 'SELECT * FROM pets WHERE 1=1';
const params: any[] = [];
if (args.species) {
query += ' AND LOWER(species) = LOWER(?)';
params.push(args.species);
}
if (args.maxPrice) {
query += ' AND price <= ?';
params.push(args.maxPrice);
}
if (args.availableOnly !== false) {
query += ' AND available = 1';
}
query += ' ORDER BY name';
this.db.all(query, params, (err, rows) => {
if (err) {
reject(err);
} else {
resolve({
success: true,
pets: rows,
count: rows.length
});
}
});
});
}
// Tool: Get detailed pet information
getPetDetailsTool(): Tool {
return {
name: 'get_pet_details',
description: 'Get detailed information about a specific pet',
inputSchema: {
type: 'object',
properties: {
petId: {
type: 'integer',
description: 'The ID of the pet to retrieve details for'
}
},
required: ['petId']
}
};
}
async getPetDetails(args: any): Promise {
return new Promise((resolve, reject) => {
this.db.get(
'SELECT * FROM pets WHERE id = ?',
[args.petId],
(err, row) => {
if (err) {
reject(err);
} else if (!row) {
resolve({
success: false,
error: 'Pet not found'
});
} else {
resolve({
success: true,
pet: row
});
}
}
);
});
}
// Tool: Add new pet to inventory
getAddPetTool(): Tool {
return {
name: 'add_pet',
description: 'Add a new pet to the store inventory',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Pet name' },
species: { type: 'string', description: 'Species (dog, cat, etc.)' },
breed: { type: 'string', description: 'Breed of the pet' },
age: { type: 'integer', description: 'Age in years' },
price: { type: 'number', description: 'Price in dollars' },
description: { type: 'string', description: 'Description of the pet' }
},
required: ['name', 'species', 'price']
}
};
}
async addPet(args: any): Promise {
return new Promise((resolve, reject) => {
this.db.run(
`INSERT INTO pets (name, species, breed, age, price, description)
VALUES (?, ?, ?, ?, ?, ?)`,
[args.name, args.species, args.breed || null, args.age || null, args.price, args.description || null],
function(err) {
if (err) {
reject(err);
} else {
resolve({
success: true,
petId: this.lastID,
message: `Successfully added ${args.name} to inventory`
});
}
}
);
});
}
}
Order Management Tools
// server/src/tools/orders.ts
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import Database from 'sqlite3';
export class OrderManagementTools {
private db: Database.Database;
constructor(db: Database.Database) {
this.db = db;
}
getCreateOrderTool(): Tool {
return {
name: 'create_order',
description: 'Create a new order for a pet purchase',
inputSchema: {
type: 'object',
properties: {
customerName: { type: 'string', description: 'Customer full name' },
customerEmail: { type: 'string', description: 'Customer email address' },
petId: { type: 'integer', description: 'ID of the pet to purchase' }
},
required: ['customerName', 'customerEmail', 'petId']
}
};
}
async createOrder(args: any): Promise {
return new Promise((resolve, reject) => {
// First check if pet is available
this.db.get(
'SELECT * FROM pets WHERE id = ? AND available = 1',
[args.petId],
(err, pet: any) => {
if (err) {
reject(err);
return;
}
if (!pet) {
resolve({
success: false,
error: 'Pet not available or not found'
});
return;
}
// Create the order
this.db.run(
`INSERT INTO orders (customer_name, customer_email, pet_id, total_amount)
VALUES (?, ?, ?, ?)`,
[args.customerName, args.customerEmail, args.petId, pet.price],
function(err) {
if (err) {
reject(err);
} else {
// Mark pet as unavailable
this.db.run(
'UPDATE pets SET available = 0 WHERE id = ?',
[args.petId],
(updateErr) => {
if (updateErr) {
reject(updateErr);
} else {
resolve({
success: true,
orderId: this.lastID,
petName: pet.name,
totalAmount: pet.price,
message: `Order created successfully for ${pet.name}`
});
}
}
);
}
}
);
}
);
});
}
getOrderStatusTool(): Tool {
return {
name: 'get_order_status',
description: 'Get the status of an existing order',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'integer', description: 'Order ID to check status for' }
},
required: ['orderId']
}
};
}
async getOrderStatus(args: any): Promise {
return new Promise((resolve, reject) => {
this.db.get(
`SELECT o.*, p.name as pet_name, p.species, p.breed
FROM orders o
JOIN pets p ON o.pet_id = p.id
WHERE o.id = ?`,
[args.orderId],
(err, row) => {
if (err) {
reject(err);
} else if (!row) {
resolve({
success: false,
error: 'Order not found'
});
} else {
resolve({
success: true,
order: row
});
}
}
);
});
}
}
Main Server Implementation
// server/src/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import Database from 'sqlite3';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { PetManagementTools } from './tools/petManagement.js';
import { OrderManagementTools } from './tools/orders.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
class PetStoreMCPServer {
private server: Server;
private db: Database.Database;
private petTools: PetManagementTools;
private orderTools: OrderManagementTools;
constructor() {
this.server = new Server(
{
name: 'pet-store-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
this.setupDatabase();
this.setupTools();
this.setupHandlers();
}
private setupDatabase() {
this.db = new Database.Database(':memory:');
// Initialize database schema
const schema = readFileSync(join(__dirname, 'database/schema.sql'), 'utf8');
this.db.exec(schema, (err) => {
if (err) {
console.error('Database initialization error:', err);
} else {
console.log('Database initialized successfully');
}
});
this.petTools = new PetManagementTools(this.db);
this.orderTools = new OrderManagementTools(this.db);
}
private setupTools() {
// Register pet management tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
this.petTools.getListPetsTool(),
this.petTools.getPetDetailsTool(),
this.petTools.getAddPetTool(),
this.orderTools.getCreateOrderTool(),
this.orderTools.getOrderStatusTool(),
],
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
switch (request.params.name) {
case 'list_pets':
const petsResult = await this.petTools.listPets(request.params.arguments);
return {
content: [
{
type: 'text',
text: JSON.stringify(petsResult, null, 2),
},
],
};
case 'get_pet_details':
const petResult = await this.petTools.getPetDetails(request.params.arguments);
return {
content: [
{
type: 'text',
text: JSON.stringify(petResult, null, 2),
},
],
};
case 'add_pet':
const addResult = await this.petTools.addPet(request.params.arguments);
return {
content: [
{
type: 'text',
text: JSON.stringify(addResult, null, 2),
},
],
};
case 'create_order':
const orderResult = await this.orderTools.createOrder(request.params.arguments);
return {
content: [
{
type: 'text',
text: JSON.stringify(orderResult, null, 2),
},
],
};
case 'get_order_status':
const statusResult = await this.orderTools.getOrderStatus(request.params.arguments);
return {
content: [
{
type: 'text',
text: JSON.stringify(statusResult, null, 2),
},
],
};
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
}
private setupHandlers() {
// Resources handler (for serving pet catalog data)
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'petstore://catalog',
name: 'Pet Store Catalog',
description: 'Complete catalog of available pets',
mimeType: 'application/json',
},
{
uri: 'petstore://inventory-stats',
name: 'Inventory Statistics',
description: 'Statistical overview of pet inventory',
mimeType: 'application/json',
},
],
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
if (uri === 'petstore://catalog') {
const pets = await this.petTools.listPets({ availableOnly: false });
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(pets, null, 2),
},
],
};
} else if (uri === 'petstore://inventory-stats') {
return new Promise((resolve, reject) => {
this.db.all(
`SELECT
species,
COUNT(*) as total,
SUM(CASE WHEN available = 1 THEN 1 ELSE 0 END) as available,
AVG(price) as avg_price
FROM pets
GROUP BY species`,
[],
(err, rows) => {
if (err) {
reject(err);
} else {
resolve({
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify({
inventoryStats: rows,
lastUpdated: new Date().toISOString()
}, null, 2),
},
],
});
}
}
);
});
} else {
throw new Error(`Unknown resource: ${uri}`);
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Pet Store MCP Server running on stdio');
}
}
// Start the server
const server = new PetStoreMCPServer();
server.run().catch(console.error);
Step 3: Creating the Client Application
MCP Client Implementation
// client/src/petStoreClient.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { spawn } from 'child_process';
export class PetStoreClient {
private client: Client;
private transport: StdioClientTransport;
constructor() {
this.client = new Client(
{
name: 'pet-store-client',
version: '1.0.0',
},
{
capabilities: {},
}
);
}
async connect() {
// Spawn the MCP server process
const serverProcess = spawn('node', ['../server/dist/index.js'], {
stdio: ['pipe', 'pipe', 'pipe'],
});
this.transport = new StdioClientTransport({
process: serverProcess,
});
await this.client.connect(this.transport);
console.log('Connected to Pet Store MCP Server');
}
async disconnect() {
await this.client.close();
}
async listAvailablePets(filters?: { species?: string; maxPrice?: number }) {
try {
const result = await this.client.request(
{
method: 'tools/call',
params: {
name: 'list_pets',
arguments: {
...filters,
availableOnly: true
},
},
},
{
timeout: 5000,
}
);
return JSON.parse(result.content[0].text);
} catch (error) {
console.error('Error listing pets:', error);
throw error;
}
}
async getPetDetails(petId: number) {
try {
const result = await this.client.request(
{
method: 'tools/call',
params: {
name: 'get_pet_details',
arguments: { petId },
},
},
{
timeout: 5000,
}
);
return JSON.parse(result.content[0].text);
} catch (error) {
console.error('Error getting pet details:', error);
throw error;
}
}
async createOrder(customerName: string, customerEmail: string, petId: number) {
try {
const result = await this.client.request(
{
method: 'tools/call',
params: {
name: 'create_order',
arguments: {
customerName,
customerEmail,
petId,
},
},
},
{
timeout: 5000,
}
);
return JSON.parse(result.content[0].text);
} catch (error) {
console.error('Error creating order:', error);
throw error;
}
}
async getInventoryStats() {
try {
const result = await this.client.request(
{
method: 'resources/read',
params: {
uri: 'petstore://inventory-stats',
},
},
{
timeout: 5000,
}
);
return JSON.parse(result.contents[0].text);
} catch (error) {
console.error('Error getting inventory stats:', error);
throw error;
}
}
}
Demo Application
// client/src/index.ts
import { PetStoreClient } from './petStoreClient.js';
import { createInterface } from 'readline';
class PetStoreDemo {
private client: PetStoreClient;
private rl: any;
constructor() {
this.client = new PetStoreClient();
this.rl = createInterface({
input: process.stdin,
output: process.stdout,
});
}
private async prompt(question: string): Promise {
return new Promise((resolve) => {
this.rl.question(question, resolve);
});
}
async run() {
try {
console.log('🐾 Welcome to Pet Store MCP Demo!');
console.log('Connecting to pet store server...\n');
await this.client.connect();
while (true) {
console.log('\n--- Pet Store Menu ---');
console.log('1. Browse available pets');
console.log('2. Search pets by species');
console.log('3. Get pet details');
console.log('4. Purchase a pet');
console.log('5. View inventory statistics');
console.log('6. Exit');
const choice = await this.prompt('\nEnter your choice (1-6): ');
switch (choice) {
case '1':
await this.browsePets();
break;
case '2':
await this.searchPetsBySpecies();
break;
case '3':
await this.getPetDetails();
break;
case '4':
await this.purchasePet();
break;
case '5':
await this.viewInventoryStats();
break;
case '6':
console.log('Thank you for visiting our pet store! 🐾');
await this.client.disconnect();
this.rl.close();
return;
default:
console.log('Invalid choice. Please try again.');
}
}
} catch (error) {
console.error('Demo error:', error);
}
}
private async browsePets() {
console.log('\n📋 Available Pets:');
try {
const result = await this.client.listAvailablePets();
if (result.success && result.pets.length > 0) {
result.pets.forEach((pet: any) => {
console.log(`ID: ${pet.id} | ${pet.name} (${pet.species}) - $${pet.price}`);
console.log(` Breed: ${pet.breed || 'Mixed'} | Age: ${pet.age || 'Unknown'}`);
console.log(` ${pet.description}\n`);
});
} else {
console.log('No pets available at the moment.');
}
} catch (error) {
console.error('Failed to browse pets:', error.message);
}
}
private async searchPetsBySpecies() {
const species = await this.prompt('Enter species to search for (dog, cat, bird, fish, rabbit): ');
console.log(`\n🔍 Searching for ${species}s...`);
try {
const result = await this.client.listAvailablePets({ species: species.toLowerCase() });
if (result.success && result.pets.length > 0) {
console.log(`Found ${result.count} ${species}(s):`);
result.pets.forEach((pet: any) => {
console.log(`ID: ${pet.id} | ${pet.name} - $${pet.price}`);
console.log(` ${pet.description}\n`);
});
} else {
console.log(`No ${species}s available.`);
}
} catch (error) {
console.error('Search failed:', error.message);
}
}
private async getPetDetails() {
const petIdStr = await this.prompt('Enter pet ID for details: ');
const petId = parseInt(petIdStr);
if (isNaN(petId)) {
console.log('Invalid pet ID.');
return;
}
try {
const result = await this.client.getPetDetails(petId);
if (result.success) {
const pet = result.pet;
console.log(`\n🐕 Pet Details:`);
console.log(`Name: ${pet.name}`);
console.log(`Species: ${pet.species}`);
console.log(`Breed: ${pet.breed || 'Mixed'}`);
console.log(`Age: ${pet.age || 'Unknown'} years`);
console.log(`Price: $${pet.price}`);
console.log(`Available: ${pet.available ? 'Yes' : 'No'}`);
console.log(`Description: ${pet.description}`);
} else {
console.log('Pet not found.');
}
} catch (error) {
console.error('Failed to get pet details:', error.message);
}
}
private async purchasePet() {
const petIdStr = await this.prompt('Enter pet ID to purchase: ');
const petId = parseInt(petIdStr);
if (isNaN(petId)) {
console.log('Invalid pet ID.');
return;
}
const customerName = await this.prompt('Enter your full name: ');
const customerEmail = await this.prompt('Enter your email: ');
console.log('\n💳 Processing your order...');
try {
const result = await this.client.createOrder(customerName, customerEmail, petId);
if (result.success) {
console.log(`\n✅ Order successful!`);
console.log(`Order ID: ${result.orderId}`);
console.log(`Pet: ${result.petName}`);
console.log(`Total: $${result.totalAmount}`);
console.log(`\nThank you for your purchase! 🎉`);
} else {
console.log(`❌ Order failed: ${result.error}`);
}
} catch (error) {
console.error('Purchase failed:', error.message);
}
}
private async viewInventoryStats() {
console.log('\n📊 Inventory Statistics:');
try {
const stats = await this.client.getInventoryStats();
console.log(`Last Updated: ${new Date(stats.lastUpdated).toLocaleString()}\n`);
stats.inventoryStats.forEach((stat: any) => {
console.log(`${stat.species.toUpperCase()}:`);
console.log(` Total: ${stat.total}`);
console.log(` Available: ${stat.available}`);
console.log(` Average Price: $${stat.avg_price.toFixed(2)}\n`);
});
} catch (error) {
console.error('Failed to get statistics:', error.message);
}
}
}
// Run the demo
const demo = new PetStoreDemo();
demo.run().catch(console.error);
Step 4: Testing with MCP Inspector
Building and Testing the Server
# Build the server
cd server
npm run build
# Test with MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js
Inspector Testing Checklist
- Connection Test: Verify the server connects successfully
- Tools Discovery: Check that all 5 tools are listed correctly
- Tool Execution: Test each tool with various parameters
- Resource Access: Verify catalog and stats resources are accessible
- Error Handling: Test with invalid inputs to ensure proper error responses
- Performance: Monitor response times and resource usage
Step 5: Running the Complete Application
Package.json Scripts
// server/package.json
{
"name": "pet-store-mcp-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts",
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
"test": "npm run build && npm run inspect"
}
}
// client/package.json
{
"name": "pet-store-mcp-client",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
}
}
Running the Demo Application
# Terminal 1: Build and prepare server
cd server
npm run build
# Terminal 2: Build and run client
cd client
npm run build
npm start
Application Monitoring and Logging
For production readiness, add comprehensive logging to track application behavior:
// server/src/utils/logger.ts
export class Logger {
static info(message: string, data?: any) {
console.log(`[INFO] ${new Date().toISOString()} - ${message}`, data || '');
}
static error(message: string, error?: any) {
console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, error || '');
}
static audit(action: string, user: string, details?: any) {
console.log(`[AUDIT] ${new Date().toISOString()} - ${action} by ${user}`, details || '');
}
}
Performance Monitoring
Monitor key performance metrics during operation:
- Response Times: Track tool execution times and database query performance
- Error Rates: Monitor failed requests and their causes
- Connection Health: Ensure MCP client-server connections remain stable
- Resource Usage: Monitor memory and CPU usage for optimization opportunities
Common Startup Issues and Solutions
| Issue |
Symptoms |
Solution |
| Port Already in Use |
Server fails to start with port binding error |
Kill existing processes or change port configuration |
| Database Connection Failed |
Tools return database errors |
Verify SQLite file permissions and schema initialization |
| Client Connection Timeout |
Client cannot connect to server |
Ensure server is running and check transport configuration |
| Tool Execution Errors |
Tools return unexpected errors |
Check parameter validation and database constraints |
Step 6A: Production Readiness Basics
Before implementing advanced security features, ensure your application meets basic production standards:
Environment Configuration
// server/.env
NODE_ENV=production
DATABASE_URL=postgresql://user:password@localhost:5432/petstore
LOG_LEVEL=info
PORT=3000
// Load environment variables in server
import dotenv from 'dotenv';
dotenv.config();
const config = {
nodeEnv: process.env.NODE_ENV || 'development',
databaseUrl: process.env.DATABASE_URL || ':memory:',
logLevel: process.env.LOG_LEVEL || 'debug',
port: parseInt(process.env.PORT || '3000')
};
Error Handling Enhancement
// server/src/middleware/errorHandler.ts
export class ErrorHandler {
static handleToolError(error: any, toolName: string) {
Logger.error(`Tool ${toolName} failed`, error);
if (error.code === 'SQLITE_CONSTRAINT') {
return {
success: false,
error: 'Data validation failed',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
};
}
return {
success: false,
error: 'Internal server error',
requestId: Math.random().toString(36).substr(2, 9)
};
}
}
Basic Health Checks
// Add to main server implementation
private setupHealthChecks() {
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
// Add health check resource
return {
resources: [
{
uri: 'petstore://health',
name: 'Health Check',
description: 'Server health status',
mimeType: 'application/json',
},
// ... existing resources
],
};
});
}
// Health check implementation
async getHealthStatus(): Promise {
try {
// Test database connection
const dbTest = await this.testDatabaseConnection();
return {
status: 'healthy',
timestamp: new Date().toISOString(),
database: dbTest ? 'connected' : 'disconnected',
uptime: process.uptime()
};
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
timestamp: new Date().toISOString()
};
}
}
Step 6B: Advanced Security and Enterprise Features
Now that your application has basic production readiness, implement enterprise-grade security features:
Authentication Enhancement
Note: For production implementations, follow the official MCP security best practices documented at MCP Security Best Practices.
// server/src/middleware/auth.ts
export class AuthMiddleware {
private validApiKeys: Set;
constructor() {
this.validApiKeys = new Set([
process.env.API_KEY_1,
process.env.API_KEY_2,
].filter(Boolean));
}
validateRequest(headers: any): boolean {
const apiKey = headers['x-api-key'];
return this.validApiKeys.has(apiKey);
}
authorize(operation: string, userRole: string): boolean {
const permissions = {
'customer': ['list_pets', 'get_pet_details', 'create_order', 'get_order_status'],
'staff': ['list_pets', 'get_pet_details', 'add_pet', 'create_order', 'get_order_status'],
'admin': ['*'] // All operations
};
return permissions[userRole]?.includes(operation) || permissions[userRole]?.includes('*');
}
// Prevent token passthrough - only accept tokens issued for this MCP server
validateTokenAudience(token: any): boolean {
return token.aud === process.env.MCP_SERVER_ID;
}
// Generate secure session IDs bound to user information
generateSecureSessionId(userId: string): string {
const sessionId = crypto.randomUUID();
return `${userId}:${sessionId}`;
}
}
Input Validation
// server/src/validation/schemas.ts
import Joi from 'joi';
export const petSchema = Joi.object({
name: Joi.string().min(1).max(50).required(),
species: Joi.string().valid('dog', 'cat', 'bird', 'fish', 'rabbit').required(),
breed: Joi.string().max(50).optional(),
age: Joi.number().integer().min(0).max(30).optional(),
price: Joi.number().positive().max(10000).required(),
description: Joi.string().max(500).optional()
});
export const orderSchema = Joi.object({
customerName: Joi.string().min(2).max(100).required(),
customerEmail: Joi.string().email().required(),
petId: Joi.number().integer().positive().required()
});
export function validateInput(schema: Joi.ObjectSchema, data: any) {
const { error, value } = schema.validate(data);
if (error) {
throw new Error(`Validation error: ${error.details[0].message}`);
}
return value;
}
Rate Limiting and Security Headers
// server/src/middleware/security.ts
export class SecurityMiddleware {
private rateLimiter = new Map();
checkRateLimit(clientId: string, limit: number = 100, windowMs: number = 60000): boolean {
const now = Date.now();
const clientData = this.rateLimiter.get(clientId);
if (!clientData || now > clientData.resetTime) {
this.rateLimiter.set(clientId, { count: 1, resetTime: now + windowMs });
return true;
}
if (clientData.count >= limit) {
return false;
}
clientData.count++;
return true;
}
setSecurityHeaders(response: any) {
response.headers = {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
};
}
}
Troubleshooting Guide
Development Issues
| Problem |
Check This |
Quick Fix |
| TypeScript Build Errors |
Import paths, type definitions |
Run npm run build to see specific errors |
| MCP Inspector Not Connecting |
Server process status, port conflicts |
Restart server and check console output |
| Database Errors |
Schema initialization, file permissions |
Delete SQLite file and restart to reinitialize |
| Tool Parameter Errors |
JSON schema validation, required fields |
Use MCP Inspector to test with valid parameters |
Production Issues
- Memory Leaks: Monitor database connection pooling and close unused connections
- Performance Degradation: Check database query performance and add appropriate indexes
- Security Alerts: Review authentication logs and implement proper session management
- Scaling Issues: Consider horizontal scaling and load balancing for high traffic
Testing and Validation Checklist
Functional Testing
- ✅ All tools execute successfully with valid parameters
- ✅ Error handling works correctly with invalid inputs
- ✅ Database operations maintain data integrity
- ✅ Client-server communication is stable
- ✅ Resources are accessible and return correct data
Security Testing
- ✅ Input validation prevents malicious data
- ✅ Authentication mechanisms work correctly
- ✅ Rate limiting prevents abuse
- ✅ Error messages don't leak sensitive information
- ✅ Security headers are properly set
- ✅ Token passthrough is prevented (only accept tokens issued for the MCP server)
- ✅ Session hijacking protection is implemented
- ✅ Confused deputy attacks are mitigated
- ✅ Secure session ID generation with user binding
- ✅ Audit logging captures all security-relevant events
Performance Testing
- ✅ Response times are acceptable under normal load
- ✅ Memory usage remains stable over time
- ✅ Database queries are optimized
- ✅ Connection pooling works effectively
- ✅ Error recovery mechanisms function properly
Key Aspects Covered
MCP Architecture Benefits
- Standardization: The same client can work with different MCP servers without modification
- Discoverability: Tools and resources are self-describing through the protocol
- Type Safety: JSON schemas ensure proper input/output validation
- Flexibility: Easy to add new tools and extend functionality
Best Practices Demonstrated
- Modular Design: Separate tool classes for different functional areas
- Error Handling: Proper error responses and status codes
- Data Validation: Input validation and sanitization
- Resource Management: Proper database connection handling
- Documentation: Clear tool descriptions and parameter schemas
- Production Readiness: Logging, monitoring, and security considerations
Production Considerations
- Database: Use persistent storage (PostgreSQL, MySQL) instead of in-memory SQLite
- Authentication: Implement proper OAuth 2.1 authentication flow
- Rate Limiting: Add rate limiting to prevent abuse
- Monitoring: Add comprehensive logging and monitoring
- Deployment: Containerize with Docker for easy deployment
- Testing: Add unit tests and integration tests
- Security: Implement enterprise-grade security controls
- Performance: Optimize for scalability and reliability
Next Steps and Extensions
This section provides a solid foundation for building MCP applications. Consider extending it with:
- Web Interface: Build a React frontend that uses the MCP client
- Multi-tenant Support: Add support for multiple pet store locations
- Payment Integration: Add payment processing tools
- Inventory Management: Add supplier and restocking tools
- Analytics: Add reporting and analytics capabilities
- Mobile App: Create a mobile client using the same MCP server
- Microservices: Split into multiple specialized MCP servers
- AI Enhancement: Add machine learning features for recommendations