Understanding MCP Server: Building Powerful AI Integrations
On this page
- Understanding MCP Server: Building Powerful AI Integrations
- What is an MCP Server?
- Key Components of MCP Architecture
- Why MCP Matters
- Building an MCP Server for GitHub Integration
- Setting Up the Project
- Defining the MCP Protocol Types
- Creating the GitHub Service
- Implementing the MCP Server
- Creating the Main Entry Point
- Environment Configuration
- Using the MCP Server with an AI Client
- Advanced Features and Extensions
- Authentication and Authorization
- Rate Limiting
- Caching Responses
- Best Practices for MCP Server Development
- Conclusion
- Resources
Understanding MCP Server: Building Powerful AI Integrations
The Model Context Protocol (MCP) represents a significant advancement in how AI systems interact with external tools and data sources. In this comprehensive guide, we’ll explore what MCP servers are, how they work, and provide a practical example of building an MCP server that connects to GitHub’s API to fetch repository issues.
What is an MCP Server?
The Model Context Protocol (MCP) is a standardized communication protocol that enables AI systems to seamlessly connect with external tools, databases, and services. An MCP server acts as a bridge between AI models and the outside world, providing a structured way for AI to access and manipulate data beyond its training corpus.
Key Components of MCP Architecture
- MCP Server: The intermediary service that implements the MCP protocol and exposes functionality to AI systems
- Tools/Functions: Specific capabilities exposed by the MCP server (e.g., database queries, API calls)
- AI Client: The AI system that consumes the MCP server’s capabilities
- Resources: External data sources or services that the MCP server interacts with
Why MCP Matters
MCP addresses several critical limitations of traditional AI systems:
- Real-time Data Access: Enables AI to work with up-to-date information rather than being limited to training data
- Tool Use: Allows AI to leverage specialized tools and services
- Structured Interaction: Provides a standardized protocol for AI-to-service communication
- Capability Extension: Expands what AI can do without requiring retraining
Building an MCP Server for GitHub Integration
Let’s create a practical example: an MCP server that connects to GitHub’s API to fetch issues from any public repository. This example demonstrates how MCP can bridge AI systems with external services.
Setting Up the Project
First, let’s set up a TypeScript project with the necessary dependencies:
// File: package.json{ "name": "github-mcp-server", "version": "1.0.0", "description": "MCP Server for GitHub integration", "main": "dist/index.js", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "ts-node src/index.ts" }, "dependencies": { "express": "^4.18.2", "axios": "^1.6.2", "dotenv": "^16.3.1", "cors": "^2.8.5" }, "devDependencies": { "@types/express": "^4.17.21", "@types/cors": "^2.8.17", "typescript": "^5.3.2", "ts-node": "^10.9.1" }}Create a TypeScript configuration file:
// File: tsconfig.json{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "outDir": "./dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"]}Defining the MCP Protocol Types
Let’s define the TypeScript types for our MCP server:
export interface MCPRequest { id: string; method: string; params: any;}
export interface MCPResponse { id: string; result?: any; error?: { code: number; message: string; };}
export interface GitHubIssue { id: number; number: number; title: string; state: string; html_url: string; body: string; user: { login: string; avatar_url: string; }; created_at: string; updated_at: string; labels: Array<{ name: string; color: string; }>; comments: number;}
export interface FetchIssuesParams { owner: string; repo: string; state?: 'open' | 'closed' | 'all'; labels?: string; sort?: 'created' | 'updated' | 'comments'; direction?: 'asc' | 'desc'; per_page?: number; page?: number;}Creating the GitHub Service
Now, let’s implement the service that will interact with GitHub’s API:
import axios from 'axios';import { FetchIssuesParams, GitHubIssue } from '../types';
export class GitHubService { private baseUrl = 'https://api.github.com'; private headers: Record<string, string>;
constructor(apiToken?: string) { this.headers = { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'GitHub-MCP-Server' };
if (apiToken) { this.headers['Authorization'] = `token ${apiToken}`; } }
async fetchRepositoryIssues(params: FetchIssuesParams): Promise<GitHubIssue[]> { try { const { owner, repo, state = 'open', labels, sort = 'created', direction = 'desc', per_page = 30, page = 1 } = params;
const url = `${this.baseUrl}/repos/${owner}/${repo}/issues`;
const response = await axios.get(url, { headers: this.headers, params: { state, labels, sort, direction, per_page, page } });
return response.data; } catch (error) { if (axios.isAxiosError(error)) { const statusCode = error.response?.status || 500; const message = error.response?.data?.message || error.message;
throw new Error(`GitHub API Error (${statusCode}): ${message}`); } throw error; } }
async fetchIssueDetails(owner: string, repo: string, issueNumber: number): Promise<GitHubIssue> { try { const url = `${this.baseUrl}/repos/${owner}/${repo}/issues/${issueNumber}`;
const response = await axios.get(url, { headers: this.headers });
return response.data; } catch (error) { if (axios.isAxiosError(error)) { const statusCode = error.response?.status || 500; const message = error.response?.data?.message || error.message;
throw new Error(`GitHub API Error (${statusCode}): ${message}`); } throw error; } }}Implementing the MCP Server
Now, let’s create the MCP server that will expose these GitHub capabilities:
import express from 'express';import cors from 'cors';import dotenv from 'dotenv';import { MCPRequest, MCPResponse, FetchIssuesParams } from './types';import { GitHubService } from './services/github.service';
dotenv.config();
const app = express();app.use(cors());app.use(express.json());
const PORT = process.env.PORT || 3000;const githubService = new GitHubService(process.env.GITHUB_TOKEN);
// MCP endpointapp.post('/mcp', async (req, res) => { const mcpRequest: MCPRequest = req.body; let mcpResponse: MCPResponse = { id: mcpRequest.id };
try { switch (mcpRequest.method) { case 'github.fetchIssues': const params: FetchIssuesParams = mcpRequest.params; const issues = await githubService.fetchRepositoryIssues(params); mcpResponse.result = issues; break;
case 'github.fetchIssueDetails': const { owner, repo, issueNumber } = mcpRequest.params; const issue = await githubService.fetchIssueDetails(owner, repo, issueNumber); mcpResponse.result = issue; break;
default: mcpResponse.error = { code: 404, message: `Method '${mcpRequest.method}' not found` }; } } catch (error) { mcpResponse.error = { code: 500, message: error instanceof Error ? error.message : 'Unknown error' }; }
res.json(mcpResponse);});
// Server info endpointapp.get('/info', (req, res) => { res.json({ name: 'GitHub MCP Server', version: '1.0.0', methods: [ { name: 'github.fetchIssues', description: 'Fetch issues from a GitHub repository', params: { owner: 'string (required)', repo: 'string (required)', state: 'string (optional: open, closed, all)', labels: 'string (optional)', sort: 'string (optional: created, updated, comments)', direction: 'string (optional: asc, desc)', per_page: 'number (optional)', page: 'number (optional)' } }, { name: 'github.fetchIssueDetails', description: 'Fetch details of a specific GitHub issue', params: { owner: 'string (required)', repo: 'string (required)', issueNumber: 'number (required)' } } ] });});
// Main entry pointexport function startServer() { app.listen(PORT, () => { console.log(`MCP Server running on port ${PORT}`); });}Creating the Main Entry Point
import { startServer } from './mcp-server';
startServer();Environment Configuration
Create a .env file for configuration:
PORT=3000GITHUB_TOKEN=your_github_personal_access_tokenUsing the MCP Server with an AI Client
Now that we have our MCP server, let’s see how an AI client would interact with it:
import axios from 'axios';import { MCPRequest, MCPResponse } from '../src/types';
async function callMCPServer(method: string, params: any): Promise<any> { const mcpRequest: MCPRequest = { id: Date.now().toString(), method, params };
try { const response = await axios.post('http://localhost:3000/mcp', mcpRequest); const mcpResponse: MCPResponse = response.data;
if (mcpResponse.error) { throw new Error(`MCP Error (${mcpResponse.error.code}): ${mcpResponse.error.message}`); }
return mcpResponse.result; } catch (error) { console.error('Error calling MCP server:', error); throw error; }}
async function main() { try { // Example: Fetch issues from React repository const issues = await callMCPServer('github.fetchIssues', { owner: 'facebook', repo: 'react', state: 'open', labels: 'bug', per_page: 5 });
console.log('React Bug Issues:'); issues.forEach((issue: any) => { console.log(`#${issue.number}: ${issue.title}`); console.log(` State: ${issue.state}`); console.log(` URL: ${issue.html_url}`); console.log(` Created by: ${issue.user.login}`); console.log(` Created at: ${new Date(issue.created_at).toLocaleString()}`); console.log('---'); });
// Example: Fetch details of a specific issue if (issues.length > 0) { const issueNumber = issues[0].number; const issueDetails = await callMCPServer('github.fetchIssueDetails', { owner: 'facebook', repo: 'react', issueNumber });
console.log('\nDetailed Issue Information:'); console.log(`Title: ${issueDetails.title}`); console.log(`Body: ${issueDetails.body.substring(0, 200)}...`); console.log(`Labels: ${issueDetails.labels.map((l: any) => l.name).join(', ')}`); } } catch (error) { console.error('Error in main function:', error); }}
main();Advanced Features and Extensions
Our basic MCP server can be extended with additional capabilities:
Authentication and Authorization
import { Request, Response, NextFunction } from 'express';
export function authMiddleware(req: Request, res: Response, next: NextFunction) { const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.API_KEY) { return res.status(401).json({ error: { code: 401, message: 'Unauthorized: Invalid API key' } }); }
next();}Rate Limiting
import { Request, Response, NextFunction } from 'express';
const requestCounts: Record<string, { count: number, resetTime: number }> = {};
export function rateLimitMiddleware(req: Request, res: Response, next: NextFunction) { const clientIp = req.ip; const now = Date.now(); const windowMs = 60 * 1000; // 1 minute const maxRequests = 60; // 60 requests per minute
// Initialize or reset if window expired if (!requestCounts[clientIp] || now > requestCounts[clientIp].resetTime) { requestCounts[clientIp] = { count: 0, resetTime: now + windowMs }; }
// Increment request count requestCounts[clientIp].count++;
// Check if over limit if (requestCounts[clientIp].count > maxRequests) { return res.status(429).json({ error: { code: 429, message: 'Too Many Requests: Rate limit exceeded' } }); }
next();}Caching Responses
export class CacheService { private cache: Map<string, { data: any, expiry: number }> = new Map();
set(key: string, data: any, ttlSeconds: number = 300): void { const expiry = Date.now() + (ttlSeconds * 1000); this.cache.set(key, { data, expiry }); }
get(key: string): any | null { const item = this.cache.get(key);
if (!item) { return null; }
// Check if expired if (Date.now() > item.expiry) { this.cache.delete(key); return null; }
return item.data; }
invalidate(key: string): void { this.cache.delete(key); }
clear(): void { this.cache.clear(); }}Best Practices for MCP Server Development
When building MCP servers, consider these best practices:
- Structured Error Handling: Provide clear error messages and appropriate status codes
- Validation: Validate all input parameters before processing
- Documentation: Document all available methods and their parameters
- Security: Implement proper authentication and authorization
- Rate Limiting: Protect your server and downstream APIs from abuse
- Monitoring: Log usage and errors for debugging and optimization
- Versioning: Use semantic versioning for your API
- Caching: Implement caching for frequently accessed data
- Graceful Degradation: Handle service outages gracefully
Conclusion
MCP servers represent a powerful paradigm for extending AI capabilities through standardized integration with external services. By building an MCP server for GitHub integration, we’ve demonstrated how AI systems can access real-time data from external APIs in a structured and controlled manner.
This approach enables AI to work with the most current information available, making it more useful for tasks that require up-to-date data. The MCP protocol provides a clean separation between the AI model and external services, allowing each to evolve independently while maintaining compatibility through the standardized interface.
As AI systems continue to evolve, MCP servers will play an increasingly important role in expanding their capabilities and ensuring they can access the information and tools they need to be truly useful in real-world applications.