UNPKG

emotions-mcp-server

Version:

MCP Server for accessing PostgreSQL emotions database

427 lines (402 loc) 13.3 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { EmotionsDbService } from './database'; import { z } from 'zod'; export class EmotionsMcpServer { private server: McpServer; private db: EmotionsDbService; private transport: StdioServerTransport | StreamableHTTPServerTransport | null = null; constructor(connectionString: string) { this.db = new EmotionsDbService(connectionString); this.server = new McpServer({ name: 'EmotionsMcpServer', version: '1.0.0', }); this.registerMethods(); } private registerMethods() { // Method to check if the emotions table exists this.server.registerTool( 'tableExists', { description: 'Check if the emotions table exists for a given user context', inputSchema: {} }, async () => { try { const exists = await this.db.tableExists(); return { content: [{ type: 'text', text: JSON.stringify({ exists }) }] }; } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); // Method to create the emotions table this.server.registerTool( 'createTable', { description: 'Create the emotions table in the database', inputSchema: {} }, async () => { try { await this.db.createTable(); return { content: [{ type: 'text', text: 'Table created successfully' }] }; } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); // Method to insert a new emotion this.server.registerTool( 'insertEmotion', { description: 'Insert a new emotion record into the database', inputSchema: { userContext: z.string().describe('User context for the emotion'), emotion: z.object({ emotion: z.string(), datum: z.string().pipe(z.coerce.date()), alter: z.number().optional(), quellenart: z.enum(['Eigene Emotion', 'Übernommene Emotion', 'Geerbte Emotion']), quelle: z.string().optional(), koerperteil: z.string().optional(), auswirkungen: z.string().optional(), bemerkungen: z.string().optional() }).describe('Emotion data') } }, async ({ userContext, emotion }) => { try { if (!userContext) { return { content: [{ type: 'text', text: 'User context is required' }], status: 'error' }; } if (!emotion) { return { content: [{ type: 'text', text: 'Emotion data is required' }], status: 'error' }; } const result = await this.db.insertEmotion(userContext, emotion); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); // Method to update an emotion this.server.registerTool( 'updateEmotion', { description: 'Update an existing emotion by ID', inputSchema: { userContext: z.string().describe('User context for the emotion'), emotionNumber: z.number().describe('Number of the emotion to update'), emotion: z.object({ emotion: z.string(), datum: z.string().pipe(z.coerce.date()), alter: z.number().optional(), quellenart: z.enum(['Eigene Emotion', 'Übernommene Emotion', 'Geerbte Emotion']), quelle: z.string().optional(), koerperteil: z.string().optional(), auswirkungen: z.string().optional(), bemerkungen: z.string().optional() }).describe('Updated emotion data') } }, async ({ userContext, emotionNumber, emotion }) => { try { if (!userContext) { return { content: [{ type: 'text', text: 'User context is required' }], status: 'error' }; } if (!emotionNumber) { return { content: [{ type: 'text', text: 'Emotion ID is required' }], status: 'error' }; } if (!emotion) { return { content: [{ type: 'text', text: 'Updated emotion data is required' }], status: 'error' }; } const updatedEmotion = await this.db.updateEmotion(userContext, emotionNumber, emotion); if (updatedEmotion) { return { content: [{ type: 'text', text: JSON.stringify(updatedEmotion) }] }; } else { return { content: [{ type: 'text', text: 'Emotion not found or update failed' }], status: 'error' }; } } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); // Method to delete an emotion this.server.registerTool( 'deleteEmotion', { description: 'Delete an emotion by ID', inputSchema: { userContext: z.string().describe('User context for the emotion'), emotionNumber: z.number().describe('Number of the emotion to delete') } }, async ({ userContext, emotionNumber }) => { try { if (!userContext) { return { content: [{ type: 'text', text: 'User context is required' }], status: 'error' }; } if (!emotionNumber) { return { content: [{ type: 'text', text: 'Emotion Number is required' }], status: 'error' }; } const result = await this.db.deleteEmotion(userContext, emotionNumber); if (result) { return { content: [{ type: 'text', text: 'Emotion deleted successfully' }] }; } else { return { content: [{ type: 'text', text: 'Emotion not found' }], status: 'error' }; } } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); // Method to get emotions with filters this.server.registerTool( 'getEmotions', { description: 'Get emotions based on filters', inputSchema: { userContext: z.string().describe('User context for the emotions'), filter: z.object({ nummer: z.number().optional(), emotion: z.string().optional(), datumVon: z.string().pipe(z.coerce.date()).optional(), datumBis: z.string().pipe(z.coerce.date()).optional(), alterVon: z.number().optional(), alterBis: z.number().optional(), quellenart: z.enum(['Eigene Emotion', 'Übernommene Emotion', 'Geerbte Emotion']).optional(), quelle: z.string().optional(), koerperteil: z.string().optional(), auswirkungen: z.string().optional(), bemerkungen: z.string().optional() }).optional().describe('Filter criteria') } }, async ({ userContext, filter = {} }) => { try { if (!userContext) { return { content: [{ type: 'text', text: 'User context is required' }], status: 'error' }; } const emotions = await this.db.getEmotions(userContext, filter); return { content: [{ type: 'text', text: JSON.stringify(emotions) }] }; } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); // Method to get a specific emotion by ID this.server.registerTool( 'getEmotionByNumber', { description: 'Get a specific emotion by Number', inputSchema: { userContext: z.string().describe('User context for the emotion'), emotionNumber: z.number().describe('Number of the emotion to retrieve') } }, async ({ userContext, emotionNumber }) => { try { if (!userContext) { return { content: [{ type: 'text', text: 'User context is required' }], status: 'error' }; } if (!emotionNumber) { return { content: [{ type: 'text', text: 'Emotion Number is required' }], status: 'error' }; } const emotion = await this.db.getEmotionByNumber(userContext, emotionNumber); if (emotion) { return { content: [{ type: 'text', text: JSON.stringify(emotion) }] }; } else { return { content: [{ type: 'text', text: 'Emotion not found' }], status: 'error' }; } } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); // Method to get recent emotions this.server.registerTool( 'getRecentEmotions', { description: 'Get the most recent emotions for a user', inputSchema: { userContext: z.string().describe('User context for the emotions'), limit: z.number().min(1).default(10).describe('Maximum number of emotions to retrieve') } }, async ({ userContext, limit = 10 }) => { try { if (!userContext) { return { content: [{ type: 'text', text: 'User context is required' }], status: 'error' }; } const emotions = await this.db.getRecentEmotions(userContext, limit); return { content: [{ type: 'text', text: JSON.stringify(emotions) }] }; } catch (error: any) { if (error.message) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], status: 'error' }; } return { content: [{ type: 'text', text: `Error: ${JSON.stringify(error)}` }], status: 'error' }; } } ); } sendLoggingMessage(message: string) : void { // Logging is not supported in the MCP SDK //this.server.server.sendLoggingMessage({ // level: "info", // data: message, //}); } async start(): Promise<void> { try { this.transport = new StdioServerTransport(); await this.server.connect(this.transport); this.sendLoggingMessage('MCP Server started'); } catch (error) { console.error('Failed to start MCP Server:', error); throw error; } } async stop(): Promise<void> { try { this.sendLoggingMessage('Stopping MCP Server'); if (this.transport) { await this.server.close(); } await this.db.closeConnection(); } catch (error) { console.error('Error stopping MCP Server:', error); throw error; } } }