Créer des serveurs MCP personnalisés

Apprenez à construire vos propres serveurs MCP pour étendre les capacités de TeamDay avec des intégrations personnalisées et des outils spécialisés.

Premiers pas

Prérequis

  • Node.js 18+ ou Python 3.8+
  • Compréhension des APIs REST et WebSockets
  • Familiarité avec votre intégration cible
  • Accès à l’environnement de développement TeamDay

Structure de base d’un serveur MCP

// Serveur MCP Node.js de base
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: 'Serveur d\'intégration personnalisé'
});

// Enregistrer des outils
mcpServer.registerTool('my_tool', {
  description: 'Mon outil personnalisé',
  parameters: {
    input: { type: 'string', required: true }
  },
  handler: async (params) => {
    // Implémentation de l'outil
    return { result: `Traité : ${params.input}` };
  }
});

app.use('/mcp', mcpServer.handler);
app.listen(3000);

Implémentation d’outils

Outils simples

// Outil de traitement de texte
mcpServer.registerTool('process_text', {
  description: 'Traiter et transformer du texte',
  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('Opération invalide');
    }
  }
});

Outils de base de données

// Outil de requête de base de données
mcpServer.registerTool('query_database', {
  description: 'Exécuter des requêtes de base de données',
  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 };
  }
});

Outils d’intégration API

// Outil de requête HTTP
mcpServer.registerTool('http_request', {
  description: 'Effectuer des requêtes HTTP vers des APIs externes',
  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 et déploiement

Configuration d’environnement

// 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
    }
  }
};

Déploiement Docker

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:

Tests et validation

Tests unitaires

// tests/tools.test.js
const { MCPServer } = require('@teamday/mcp-sdk');

describe('Outils personnalisés', () => {
  let server;

  beforeEach(() => {
    server = new MCPServer({ name: 'test-server' });
    // Enregistrer les outils...
  });

  test('outil process_text', async () => {
    const result = await server.callTool('process_text', {
      text: 'hello',
      operation: 'uppercase'
    });
    expect(result.result).toBe('HELLO');
  });
});

Tests d’intégration

// Tester avec l'agent TeamDay
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('Résultat de l\'outil:', result);
};

Bonnes pratiques de sécurité

Authentification

// Authentification par clé API
const authenticateRequest = (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey || !isValidApiKey(apiKey)) {
    return res.status(401).json({ error: 'Clé API invalide' });
  }
  next();
};

app.use('/mcp', authenticateRequest);

Validation des entrées

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() });
    }
    // Traiter la requête...
  }
);

Gestion des erreurs

Réponses d’erreur structurées

class MCPError extends Error {
  constructor(code, message, details = null) {
    super(message);
    this.code = code;
    this.details = details;
    this.retryable = ['TIMEOUT', 'RATE_LIMIT'].includes(code);
  }
}

// Middleware de gestion d'erreur
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: 'Erreur interne du serveur',
        retryable: false
      }
    });
  }
});

Optimisation des performances

Mise en cache

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;
  }
});

Pooling de connexions

const { Pool } = require('pg');
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30000
});

const getDatabase = () => pool;

Surveillance et journalisation

Journalisation structurée

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()
  ]
});

// Journaliser les exécutions d'outils
mcpServer.on('tool_execution', (toolName, params, result, duration) => {
  logger.info('Outil exécuté', {
    tool: toolName,
    duration,
    success: !result.error
  });
});

Vérifications de santé

app.get('/health', async (req, res) => {
  const health = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    services: {}
  };

  try {
    // Vérifier la base de données
    await pool.query('SELECT 1');
    health.services.database = 'healthy';
  } catch (error) {
    health.services.database = 'unhealthy';
    health.status = 'degraded';
  }

  res.json(health);
});

Publier votre serveur

Package NPM

{
  "name": "@yourorg/mcp-custom-server",
  "version": "1.0.0",
  "description": "Serveur MCP personnalisé pour TeamDay",
  "main": "server.js",
  "keywords": ["mcp", "teamday", "integration"],
  "repository": "https://github.com/yourorg/mcp-custom-server"
}

Marketplace TeamDay

  1. Tester minutieusement en développement
  2. Créer une documentation complète
  3. Soumettre au marketplace TeamDay
  4. Fournir support et maintenance

Prochaines étapes