Blogger MCP Server
by niyonabil
Verified
import { config } from './config';
import { BloggerService } from './bloggerService';
import { initMCPServer } from './server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { Server as HttpServer } from 'http';
import { ServerMode, ServerStatus, ClientConnection, ServerStats } from './types';
import { WebUIManager } from './ui-manager';
/**
* Point d'entrée principal du serveur MCP pour Blogger
*/
async function main() {
try {
console.log('Démarrage du serveur MCP pour Blogger...');
// Initialiser le service Blogger
const bloggerService = new BloggerService();
// Convertir la configuration au format attendu par le serveur
const serverMode: ServerMode = config.mode === 'http'
? { type: 'http' as const, host: config.http.host, port: config.http.port }
: { type: 'stdio' as const };
const serverConfig = {
mode: serverMode,
blogger: config.blogger,
logging: config.logging
};
// Initialiser le serveur MCP avec tous les outils
const server = initMCPServer(bloggerService, serverConfig);
// Initialiser le gestionnaire d'interface utilisateur
const uiManager = new WebUIManager();
// Démarrer l'interface utilisateur sur le port 3001 (ou un autre port configuré)
const uiPort = process.env.UI_PORT ? parseInt(process.env.UI_PORT) : 3001;
await uiManager.start(uiPort);
// Initialiser les statistiques et l'état du serveur
const serverTools = [
'list_blogs', 'get_blog', 'create_blog', 'list_posts',
'search_posts', 'get_post', 'create_post', 'update_post',
'delete_post', 'list_labels', 'get_label'
];
const serverStatus: ServerStatus = {
running: true,
mode: serverMode.type,
startTime: new Date(),
connections: 0,
tools: serverTools
};
const serverStats: ServerStats = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
averageResponseTime: 0,
toolUsage: serverTools.reduce((acc, tool) => {
acc[tool] = 0;
return acc;
}, {} as Record<string, number>)
};
uiManager.updateStatus(serverStatus);
uiManager.updateStats(serverStats);
// Configurer le transport approprié selon le mode
if (serverMode.type === 'http') {
// Pour le mode HTTP, nous utilisons directement le serveur HTTP de Node.js
// car le SDK MCP officiel n'a pas d'équivalent direct à HttpServerTransport
const httpMode = serverMode;
const httpServer = new HttpServer((req, res) => {
if (req.method === 'OPTIONS') {
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
});
res.end();
return;
}
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Méthode non autorisée' }));
return;
}
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', async () => {
try {
const request = JSON.parse(body);
const { tool, params } = request;
// Mettre à jour les statistiques
updateStats(tool);
// Ajouter une connexion client
const clientIp = req.socket.remoteAddress || 'unknown';
updateConnections(req.socket.remotePort?.toString() || 'client', clientIp);
// Appeler l'outil approprié via le SDK MCP
try {
const startTime = Date.now();
// Utiliser la méthode appropriée du SDK MCP pour appeler l'outil
// Note: Le SDK MCP n'a pas de méthode callTool, nous devons donc
// implémenter notre propre logique pour router les appels d'outils
let result;
// Trouver l'outil correspondant dans la liste des outils enregistrés
if (serverTools.includes(tool)) {
// Simuler l'appel d'outil en utilisant directement le service Blogger
switch (tool) {
case 'list_blogs':
const blogs = await bloggerService.listBlogs();
result = { blogs };
break;
case 'get_blog':
const blog = await bloggerService.getBlog(params.blogId);
result = { blog };
break;
case 'list_posts':
const posts = await bloggerService.listPosts(params.blogId, params.maxResults);
result = { posts };
break;
case 'search_posts':
const searchResults = await bloggerService.searchPosts(params.blogId, params.query, params.maxResults);
result = { posts: searchResults };
break;
case 'get_post':
const post = await bloggerService.getPost(params.blogId, params.postId);
result = { post };
break;
case 'create_post':
const newPost = await bloggerService.createPost(params.blogId, {
title: params.title,
content: params.content,
labels: params.labels
});
result = { post: newPost };
break;
case 'update_post':
const updatedPost = await bloggerService.updatePost(params.blogId, params.postId, {
title: params.title,
content: params.content,
labels: params.labels
});
result = { post: updatedPost };
break;
case 'delete_post':
await bloggerService.deletePost(params.blogId, params.postId);
result = { success: true };
break;
case 'list_labels':
const labels = await bloggerService.listLabels(params.blogId);
result = { labels };
break;
case 'get_label':
const label = await bloggerService.getLabel(params.blogId, params.labelName);
result = { label };
break;
case 'create_blog':
result = {
error: 'La création de blogs n\'est pas supportée par l\'API Blogger. Veuillez créer un blog via l\'interface web de Blogger.'
};
break;
default:
throw new Error(`Outil inconnu: ${tool}`);
}
} else {
throw new Error(`Outil inconnu: ${tool}`);
}
const duration = Date.now() - startTime;
// Mettre à jour les statistiques de réussite
updateStats(tool, true, duration);
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
res.end(JSON.stringify(result));
} catch (error) {
// Mettre à jour les statistiques d'échec
updateStats(tool, false);
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: `Erreur lors de l'exécution de l'outil: ${error}`
}));
}
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: `Erreur de parsing: ${error}` }));
}
});
});
httpServer.listen(httpMode.port, httpMode.host, () => {
console.log(`Serveur MCP pour Blogger démarré en mode HTTP`);
console.log(`Écoute sur ${httpMode.host}:${httpMode.port}`);
console.log(`Interface utilisateur disponible sur http://localhost:${uiPort}`);
});
} else {
// Pour le mode stdio, nous utilisons le transport du SDK MCP officiel
const transport = new StdioServerTransport();
await server.connect(transport);
console.log(`Serveur MCP pour Blogger démarré en mode stdio`);
console.log(`Interface utilisateur disponible sur http://localhost:${uiPort}`);
}
// Fonctions pour mettre à jour les statistiques et les connexions
const connections: Record<string, ClientConnection> = {};
let stats = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
totalResponseTime: 0,
toolUsage: serverTools.reduce((acc, tool) => {
acc[tool] = 0;
return acc;
}, {} as Record<string, number>)
};
function updateStats(tool: string, success = true, duration = 0) {
stats.totalRequests++;
if (success) {
stats.successfulRequests++;
stats.totalResponseTime += duration;
}
if (stats.toolUsage[tool] !== undefined) {
stats.toolUsage[tool]++;
}
const updatedStats: ServerStats = {
totalRequests: stats.totalRequests,
successfulRequests: stats.successfulRequests,
failedRequests: stats.totalRequests - stats.successfulRequests,
averageResponseTime: stats.successfulRequests > 0
? Math.round(stats.totalResponseTime / stats.successfulRequests)
: 0,
toolUsage: stats.toolUsage
};
uiManager.updateStats(updatedStats);
}
function updateConnections(clientId: string, clientIp?: string) {
const now = new Date();
if (!connections[clientId]) {
connections[clientId] = {
id: clientId,
ip: clientIp,
connectedAt: now,
lastActivity: now,
requestCount: 1
};
} else {
connections[clientId].lastActivity = now;
connections[clientId].requestCount++;
}
// Nettoyer les connexions inactives (plus de 5 minutes)
const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);
Object.keys(connections).forEach(id => {
if (connections[id].lastActivity < fiveMinutesAgo) {
delete connections[id];
}
});
uiManager.updateConnections(Object.values(connections));
// Mettre à jour le statut avec le nombre de connexions
const updatedStatus: ServerStatus = {
...serverStatus,
connections: Object.keys(connections).length
};
uiManager.updateStatus(updatedStatus);
}
} catch (error) {
console.error('Erreur lors du démarrage du serveur MCP pour Blogger:', error);
process.exit(1);
}
}
// Exécuter la fonction principale
main();