Back to Blog
Technical
MCP
Integration
Tutorial
Technical
API

MCP Integration Guide: Connecting AI Models to External Data

Step-by-step tutorial on implementing Model Context Protocol (MCP) integrations. Learn how to enhance AI capabilities with real-time data access.

Technical TeamJanuary 8, 202515 min read

MCP Integration Guide: Connecting AI Models to External Data

The Model Context Protocol (MCP) is revolutionizing how AI models interact with external data sources. This comprehensive guide will walk you through everything you need to know to implement MCP integrations, from basic concepts to production-ready implementations.

What is MCP?

Model Context Protocol (MCP) is an open protocol that enables AI models to securely connect to external data sources, APIs, and tools. Think of it as a standardized way for AI to access real-time information beyond its training data.

Key Benefits

  • Real-time data access - Connect to live databases, APIs, and services
  • Enhanced context - Provide models with up-to-date information
  • Tool integration - Enable AI to use external tools and services
  • Security - Controlled, audited access to sensitive systems
  • Standardization - Works across different AI models and platforms

MCP Architecture Overview

┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│  AI Model   │ ←──────→ │ MCP Server   │ ←──────→ │  Data       │
│  (Claude,   │   MCP    │  (Your API)  │   API    │  Source     │
│   GPT, etc) │ Protocol │              │  Calls   │  (DB, APIs) │
└─────────────┘         └──────────────┘         └─────────────┘

Getting Started

Prerequisites

  • Basic understanding of REST APIs
  • Node.js 18+ or Python 3.9+
  • API access to an AI model (Claude, ChatGPT, etc.)
  • A data source to connect (database, API, or file system)

Installation

Node.js:

npm install @anthropic-ai/sdk @modelcontextprotocol/sdk
# or
yarn add @anthropic-ai/sdk @modelcontextprotocol/sdk

Python:

pip install anthropic mcp-sdk

Your First MCP Integration

Let's build a simple MCP server that provides AI models with access to a product database.

Step 1: Define Your Data Source

// database.ts
interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
  category: string;
}

// Simulated database
const products: Product[] = [
  { id: "1", name: "Laptop", price: 999, stock: 15, category: "Electronics" },
  { id: "2", name: "Mouse", price: 29, stock: 50, category: "Electronics" },
  { id: "3", name: "Desk", price: 299, stock: 8, category: "Furniture" }
];

export async function getProducts(category?: string): Promise<Product[]> {
  if (category) {
    return products.filter(p => p.category === category);
  }
  return products;
}

export async function getProduct(id: string): Promise<Product | null> {
  return products.find(p => p.id === id) || null;
}

export async function updateStock(id: string, quantity: number): Promise<boolean> {
  const product = products.find(p => p.id === id);
  if (product && product.stock >= quantity) {
    product.stock -= quantity;
    return true;
  }
  return false;
}

Step 2: Create MCP Server

// mcp-server.ts
import { MCPServer } from '@modelcontextprotocol/sdk';
import { getProducts, getProduct, updateStock } from './database';

const server = new MCPServer({
  name: 'Product Database MCP',
  version: '1.0.0',
  description: 'Access to product inventory database'
});

// Define available tools
server.addTool({
  name: 'list_products',
  description: 'List all products, optionally filtered by category',
  inputSchema: {
    type: 'object',
    properties: {
      category: {
        type: 'string',
        description: 'Filter by product category (optional)',
        enum: ['Electronics', 'Furniture', 'Clothing']
      }
    }
  },
  handler: async (input) => {
    const products = await getProducts(input.category);
    return {
      products,
      count: products.length
    };
  }
});

server.addTool({
  name: 'get_product_details',
  description: 'Get detailed information about a specific product',
  inputSchema: {
    type: 'object',
    properties: {
      productId: {
        type: 'string',
        description: 'The unique product ID'
      }
    },
    required: ['productId']
  },
  handler: async (input) => {
    const product = await getProduct(input.productId);
    if (!product) {
      throw new Error(`Product with ID ${input.productId} not found`);
    }
    return product;
  }
});

server.addTool({
  name: 'check_availability',
  description: 'Check if a product is available in requested quantity',
  inputSchema: {
    type: 'object',
    properties: {
      productId: { type: 'string' },
      quantity: { type: 'number', minimum: 1 }
    },
    required: ['productId', 'quantity']
  },
  handler: async (input) => {
    const product = await getProduct(input.productId);
    if (!product) {
      return { available: false, reason: 'Product not found' };
    }
    return {
      available: product.stock >= input.quantity,
      currentStock: product.stock,
      requested: input.quantity
    };
  }
});

// Start server
server.listen(3000);
console.log('MCP Server running on port 3000');

Step 3: Connect AI Model to MCP

// ai-client.ts
import Anthropic from '@anthropic-ai/sdk';
import { MCPClient } from '@modelcontextprotocol/sdk';

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY
});

const mcpClient = new MCPClient({
  serverUrl: 'http://localhost:3000'
});

async function chatWithMCP(userMessage: string) {
  // Get available tools from MCP server
  const tools = await mcpClient.listTools();

  // Create message with Claude
  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 1024,
    tools: tools,
    messages: [{
      role: 'user',
      content: userMessage
    }]
  });

  // Handle tool calls
  if (response.stop_reason === 'tool_use') {
    const toolUse = response.content.find(block => block.type === 'tool_use');
    if (toolUse) {
      // Execute tool via MCP
      const toolResult = await mcpClient.executeTool(
        toolUse.name,
        toolUse.input
      );

      // Continue conversation with tool result
      const finalResponse = await anthropic.messages.create({
        model: 'claude-3-5-sonnet-20241022',
        max_tokens: 1024,
        messages: [
          { role: 'user', content: userMessage },
          { role: 'assistant', content: response.content },
          {
            role: 'user',
            content: [{
              type: 'tool_result',
              tool_use_id: toolUse.id,
              content: JSON.stringify(toolResult)
            }]
          }
        ]
      });

      return finalResponse.content[0].text;
    }
  }

  return response.content[0].text;
}

// Example usage
async function main() {
  const response = await chatWithMCP(
    "What electronics do you have in stock under $50?"
  );
  console.log(response);
}

main();

Advanced MCP Patterns

1. Authentication and Security

server.addMiddleware({
  name: 'auth',
  handler: async (context, next) => {
    const apiKey = context.headers['x-api-key'];

    if (!apiKey || !await validateApiKey(apiKey)) {
      throw new Error('Unauthorized');
    }

    // Add user context
    context.user = await getUserFromApiKey(apiKey);

    return next();
  }
});

2. Rate Limiting

import { RateLimiter } from 'limiter';

const limiter = new RateLimiter({
  tokensPerInterval: 100,
  interval: 'minute'
});

server.addMiddleware({
  name: 'rateLimit',
  handler: async (context, next) => {
    const remaining = await limiter.removeTokens(1);

    if (remaining < 0) {
      throw new Error('Rate limit exceeded');
    }

    return next();
  }
});

3. Caching

import NodeCache from 'node-cache';

const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes

server.addTool({
  name: 'expensive_query',
  handler: async (input) => {
    const cacheKey = JSON.stringify(input);

    // Check cache first
    const cached = cache.get(cacheKey);
    if (cached) {
      return { ...cached, cached: true };
    }

    // Execute expensive operation
    const result = await expensiveOperation(input);

    // Cache result
    cache.set(cacheKey, result);

    return result;
  }
});

4. Error Handling

server.addErrorHandler({
  handler: async (error, context) => {
    // Log error
    console.error('MCP Error:', error);

    // Send to monitoring service
    await monitoringService.logError({
      error: error.message,
      stack: error.stack,
      context: {
        tool: context.toolName,
        input: context.input,
        user: context.user
      }
    });

    // Return user-friendly error
    return {
      error: true,
      message: 'An error occurred processing your request',
      code: error.code || 'INTERNAL_ERROR'
    };
  }
});

Production Best Practices

1. Use TypeScript for Type Safety

// types.ts
export interface MCPToolInput {
  [key: string]: string | number | boolean | object;
}

export interface MCPToolOutput {
  [key: string]: any;
}

export interface MCPTool {
  name: string;
  description: string;
  inputSchema: JSONSchema;
  handler: (input: MCPToolInput) => Promise<MCPToolOutput>;
}

2. Implement Health Checks

server.addHealthCheck({
  name: 'database',
  handler: async () => {
    try {
      await database.ping();
      return { healthy: true };
    } catch (error) {
      return { healthy: false, error: error.message };
    }
  }
});

3. Add Monitoring and Logging

import { Logger } from 'winston';

const logger = new Logger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'mcp-error.log', level: 'error' }),
    new winston.transports.File({ filename: 'mcp-combined.log' })
  ]
});

server.addMiddleware({
  name: 'logging',
  handler: async (context, next) => {
    const start = Date.now();

    logger.info('MCP Request', {
      tool: context.toolName,
      input: context.input,
      user: context.user?.id
    });

    try {
      const result = await next();
      const duration = Date.now() - start;

      logger.info('MCP Response', {
        tool: context.toolName,
        duration,
        success: true
      });

      return result;
    } catch (error) {
      const duration = Date.now() - start;

      logger.error('MCP Error', {
        tool: context.toolName,
        duration,
        error: error.message,
        stack: error.stack
      });

      throw error;
    }
  }
});

4. Version Your Tools

server.addTool({
  name: 'get_product_v2',
  version: '2.0.0',
  description: 'Get product details with enhanced metadata',
  handler: async (input) => {
    // New implementation
  }
});

// Keep old version for backwards compatibility
server.addTool({
  name: 'get_product',
  version: '1.0.0',
  deprecated: true,
  deprecationMessage: 'Use get_product_v2 instead',
  handler: async (input) => {
    // Old implementation
  }
});

Common Use Cases

1. Database Integration

  • Customer data lookup
  • Order management
  • Inventory queries
  • Analytics and reporting

2. API Integration

  • Weather data
  • Stock prices
  • News feeds
  • Social media data

3. Internal Tools

  • Ticket systems (Jira, Linear)
  • Documentation (Confluence, Notion)
  • Communication (Slack, Teams)
  • Code repositories (GitHub, GitLab)

4. Custom Business Logic

  • Pricing calculations
  • Recommendation engines
  • Compliance checks
  • Data validation

Troubleshooting

Common Issues

1. Tool Not Found

// Ensure tool is properly registered
console.log(await mcpClient.listTools());

2. Authentication Failures

// Verify API keys and permissions
console.log('Auth headers:', context.headers);

3. Timeout Errors

// Increase timeout for slow operations
server.setTimeout(30000); // 30 seconds

4. Schema Validation Errors

// Use JSON Schema validator
import Ajv from 'ajv';
const ajv = new Ajv();
const validate = ajv.compile(inputSchema);
if (!validate(input)) {
  console.error('Validation errors:', validate.errors);
}

Testing Your MCP Integration

// mcp-server.test.ts
import { describe, it, expect } from 'vitest';
import { MCPClient } from '@modelcontextprotocol/sdk';

describe('MCP Server', () => {
  const client = new MCPClient({ serverUrl: 'http://localhost:3000' });

  it('should list all products', async () => {
    const result = await client.executeTool('list_products', {});
    expect(result.products).toBeInstanceOf(Array);
    expect(result.count).toBeGreaterThan(0);
  });

  it('should get product details', async () => {
    const result = await client.executeTool('get_product_details', {
      productId: '1'
    });
    expect(result.id).toBe('1');
    expect(result.name).toBeDefined();
  });

  it('should handle invalid product ID', async () => {
    await expect(
      client.executeTool('get_product_details', { productId: 'invalid' })
    ).rejects.toThrow('Product with ID invalid not found');
  });
});

Deployment

Docker

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY . .

EXPOSE 3000

CMD ["node", "dist/mcp-server.js"]

Docker Compose

version: '3.8'

services:
  mcp-server:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/products
      - API_KEY=your_api_key
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=products
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Next Steps

Conclusion

MCP integration opens up endless possibilities for enhancing AI capabilities with real-time data and tools. Start with simple integrations and gradually build more sophisticated systems as you learn.

Ready to build your first MCP integration? Share what you're building in the comments!


Related Articles:

Tags

Enjoyed this article?

Get more AI productivity tips and prompt engineering insights delivered to your inbox weekly.

Related Articles

Discover the most effective ChatGPT prompts specifically designed for software developers. These battle-tested prompts will accelerate your coding workflow and reduce debugging time.

1/15/20258 min read

In-depth analysis of how Claude and ChatGPT handle TypeScript tasks, with specific prompt examples and performance comparisons for different development scenarios.

1/12/202512 min read

Updated guidelines and techniques for writing effective AI prompts. Includes new strategies for multimodal models and advanced prompting methods.

1/5/202510 min read