Creating Custom MCP Servers
Learn how to build your own MCP servers to extend TeamDay's capabilities with custom integrations and specialized tools.
Getting Started
Prerequisites
- Node.js 18+ or Python 3.8+
- Understanding of REST APIs and WebSockets
- Familiarity with your target integration
- TeamDay development environment access
Basic MCP Server Structure
// Basic Node.js MCP Server
const express = require('express');
const { MCPServer } = require('@teamday/mcp-sdk');
const app = express();
const mcpServer = new MCPServer({
name: 'my-custom-server',
version: '1.0.0',
description: 'Custom integration server'
});
// Register tools
mcpServer.registerTool('my_tool', {
description: 'My custom tool',
parameters: {
input: { type: 'string', required: true }
},
handler: async (params) => {
// Tool implementation
return { result: `Processed: ${params.input}` };
}
});
app.use('/mcp', mcpServer.handler);
app.listen(3000);
Tool Implementation
Simple Tools
// Text processing tool
mcpServer.registerTool('process_text', {
description: 'Process and transform text',
parameters: {
text: { type: 'string', required: true },
operation: { type: 'string', enum: ['uppercase', 'lowercase', 'reverse'] }
},
handler: async ({ text, operation }) => {
switch (operation) {
case 'uppercase': return { result: text.toUpperCase() };
case 'lowercase': return { result: text.toLowerCase() };
case 'reverse': return { result: text.split('').reverse().join('') };
default: throw new Error('Invalid operation');
}
}
});
Database Tools
// Database query tool
mcpServer.registerTool('query_database', {
description: 'Execute database queries',
parameters: {
query: { type: 'string', required: true },
params: { type: 'array', default: [] }
},
handler: async ({ query, params }) => {
const db = await getDatabase();
const result = await db.query(query, params);
return { data: result.rows, count: result.rowCount };
}
});
API Integration Tools
// HTTP request tool
mcpServer.registerTool('http_request', {
description: 'Make HTTP requests to external APIs',
parameters: {
url: { type: 'string', required: true },
method: { type: 'string', default: 'GET' },
headers: { type: 'object', default: {} },
body: { type: 'object' }
},
handler: async ({ url, method, headers, body }) => {
const response = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined
});
return {
status: response.status,
data: await response.json()
};
}
});
Configuration and Deployment
Environment Configuration
// config.js
module.exports = {
port: process.env.PORT || 3000,
database: {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
name: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD
},
apis: {
external_service: {
url: process.env.EXTERNAL_API_URL,
key: process.env.EXTERNAL_API_KEY
}
}
};
Docker Deployment
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Docker Compose
version: '3.8'
services:
mcp-server:
build: .
ports:
- "3000:3000"
environment:
- DB_HOST=database
- DB_PASSWORD=${DB_PASSWORD}
depends_on:
- database
database:
image: postgres:15
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Testing and Validation
Unit Testing
// tests/tools.test.js
const { MCPServer } = require('@teamday/mcp-sdk');
describe('Custom Tools', () => {
let server;
beforeEach(() => {
server = new MCPServer({ name: 'test-server' });
// Register tools...
});
test('process_text tool', async () => {
const result = await server.callTool('process_text', {
text: 'hello',
operation: 'uppercase'
});
expect(result.result).toBe('HELLO');
});
});
Integration Testing
// Test with TeamDay agent
const testIntegration = async () => {
const response = await fetch('http://localhost:3000/mcp/tools/my_tool', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input: 'test data' })
});
const result = await response.json();
console.log('Tool result:', result);
};
Security Best Practices
Authentication
// API key authentication
const authenticateRequest = (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !isValidApiKey(apiKey)) {
return res.status(401).json({ error: 'Invalid API key' });
}
next();
};
app.use('/mcp', authenticateRequest);
Input Validation
const { body, validationResult } = require('express-validator');
app.post('/mcp/tools/:tool',
body('parameters').isObject(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process request...
}
);
Error Handling
Structured Error Responses
class MCPError extends Error {
constructor(code, message, details = null) {
super(message);
this.code = code;
this.details = details;
this.retryable = ['TIMEOUT', 'RATE_LIMIT'].includes(code);
}
}
// Error handler middleware
app.use((error, req, res, next) => {
if (error instanceof MCPError) {
res.status(400).json({
error: {
code: error.code,
message: error.message,
details: error.details,
retryable: error.retryable
}
});
} else {
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'Internal server error',
retryable: false
}
});
}
});
Performance Optimization
Caching
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes
mcpServer.registerTool('cached_api_call', {
handler: async ({ url }) => {
const cacheKey = `api:${url}`;
let result = cache.get(cacheKey);
if (!result) {
result = await fetch(url).then(r => r.json());
cache.set(cacheKey, result);
}
return result;
}
});
Connection Pooling
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20,
idleTimeoutMillis: 30000
});
const getDatabase = () => pool;
Monitoring and Logging
Structured Logging
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'mcp-server.log' }),
new winston.transports.Console()
]
});
// Log tool executions
mcpServer.on('tool_execution', (toolName, params, result, duration) => {
logger.info('Tool executed', {
tool: toolName,
duration,
success: !result.error
});
});
Health Checks
app.get('/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
services: {}
};
try {
// Check database
await pool.query('SELECT 1');
health.services.database = 'healthy';
} catch (error) {
health.services.database = 'unhealthy';
health.status = 'degraded';
}
res.json(health);
});
Publishing Your Server
NPM Package
{
"name": "@yourorg/mcp-custom-server",
"version": "1.0.0",
"description": "Custom MCP server for TeamDay",
"main": "server.js",
"keywords": ["mcp", "teamday", "integration"],
"repository": "https://github.com/yourorg/mcp-custom-server"
}
TeamDay Marketplace
- Test thoroughly in development
- Create comprehensive documentation
- Submit to TeamDay marketplace
- Provide support and maintenance