tududi/backend/modules/mcp/server.js

146 lines
4.5 KiB
JavaScript

'use strict';
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const {
StdioServerTransport,
} = require('@modelcontextprotocol/sdk/server/stdio.js');
const {
CallToolRequestSchema,
ListToolsRequestSchema,
} = require('@modelcontextprotocol/sdk/types.js');
const { findValidTokenByValue } = require('../users/apiTokenService');
const { User } = require('../../models');
const { registerAllTools } = require('./toolRegistry');
/**
* Start the MCP server
* Validates authentication token and initializes stdio transport
*/
async function startMcpServer() {
try {
// Validate environment
const apiToken = process.env.TUDUDI_API_TOKEN;
if (!apiToken) {
throw new Error(
'TUDUDI_API_TOKEN environment variable is required. ' +
'Generate a token in Profile → API Keys and add it to your Claude Desktop config.'
);
}
// Validate token and get user context
const tokenRecord = await findValidTokenByValue(apiToken);
if (!tokenRecord) {
throw new Error(
'Invalid or expired TUDUDI_API_TOKEN. ' +
'Please generate a new token in Profile → API Keys.'
);
}
const user = await User.findByPk(tokenRecord.user_id);
if (!user) {
throw new Error('User not found for the provided token.');
}
// Create context for all tools
const context = {
userId: user.id,
user: user,
apiToken: tokenRecord,
};
// Initialize MCP server
const server = new Server(
{
name: process.env.MCP_SERVER_NAME || 'tududi',
version: process.env.MCP_SERVER_VERSION || '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Store tools registry for request handlers
const tools = [];
// Register list tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: tools,
};
});
// Register call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const toolName = request.params.name;
const tool = tools.find((t) => t.name === toolName);
if (!tool) {
throw new Error(`Unknown tool: ${toolName}`);
}
try {
// Call the tool handler with params and context
const result = await tool.handler(
request.params.arguments || {},
context
);
return result;
} catch (error) {
console.error(`Error executing tool ${toolName}:`, error);
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
// Register all tools with context
registerAllTools(server, context, tools);
// Start stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Tududi MCP server running on stdio');
console.error(`Authenticated as: ${user.email} (ID: ${user.id})`);
console.error(`Available tools: ${tools.length}`);
// Update token last_used_at (fire and forget)
tokenRecord
.update({ last_used_at: new Date() })
.catch((err) =>
console.error('Failed to update token last_used_at:', err)
);
} catch (error) {
console.error('Fatal MCP server error:', error.message);
process.exit(1);
}
}
// Start server if running directly
if (require.main === module) {
// Load environment variables
require('dotenv').config();
// Initialize database connection
const { sequelize } = require('../../models');
sequelize
.authenticate()
.then(() => {
console.error('Database connection established');
return startMcpServer();
})
.catch((error) => {
console.error('Failed to connect to database:', error);
process.exit(1);
});
}
module.exports = startMcpServer;