UNPKG

@grebyn/toolflow-mcp-server

Version:

MCP server for managing other MCP servers - discover, install, organize into bundles, and automate with workflows. Uses StreamableHTTP transport with dual OAuth/API key authentication.

207 lines 8.35 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { listTools, getTool } from './tools/index.js'; import { getServerConfig } from './config/server-config.js'; import { withLogging } from './utils/logger.js'; // MCP server with stdio transport and API key authentication // This is the legacy mode - kept for backward compatibility async function validateApiKey(apiKey) { if (!apiKey) { return { isValid: false, error: 'No API key provided' }; } // Extract potential key hash for validation // API keys format: tfl_{org_prefix}_{random_part} if (!apiKey.startsWith('tfl_')) { return { isValid: false, error: 'Invalid API key format' }; } const serverConfig = getServerConfig(); try { // Validate against our API endpoint - send raw API key, let the endpoint handle hashing const response = await fetch(`${serverConfig.apiEndpoint}/proxy`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` // Use API key as bearer token }, body: JSON.stringify({ operation: 'validateApiKey', params: {} }) }); if (!response.ok) { const errorText = await response.text(); return { isValid: false, error: 'API validation failed' }; } const result = await response.json(); if (result.is_valid) { return { isValid: true, userId: result.user_id, organizationId: result.organization_id }; } else { return { isValid: false, error: 'Invalid or expired API key' }; } } catch (error) { return { isValid: false, error: `API key validation error: ${error.message}` }; } } // Create MCP server with API key authentication function createMcpServer(userContext) { const server = new Server({ name: 'toolflow-mcp', version: '1.0.20', }, { capabilities: { tools: {}, }, }); // Register handlers for MCP protocol server.setRequestHandler(InitializeRequestSchema, async (request) => { console.error(`MCP: Handling Initialize request from ${request.params.clientInfo?.name || 'unknown'}`); console.error(`MCP: Authenticated as user ${userContext.userId} in organization ${userContext.organizationId}`); return { protocolVersion: request.params.protocolVersion, capabilities: { tools: {}, logging: {}, }, serverInfo: { name: 'toolflow-mcp', version: '1.0.20', } }; }); server.setRequestHandler(ListToolsRequestSchema, async () => { console.error('MCP: Handling ListTools request'); return { tools: listTools() }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; console.error(`MCP: Handling CallTool request for tool: ${toolName}`); const tool = getTool(toolName); if (!tool) { throw new Error(`Tool not found: ${toolName}`); } // Create user context for the tool with API key const toolUserContext = { userId: userContext.userId, organizationId: userContext.organizationId, apiKey: userContext.apiKey, // Pass API key instead of OAuth token sessionId: undefined, // No session ID in stdio mode tokenJti: undefined // No JWT ID in API key mode }; console.error(`Tool '${toolName}' called by user: ${userContext.userId}`); try { // Execute tool with logging const result = await withLogging(toolUserContext, toolName, request.params.arguments || {}, 'stdio', { clientIdentifier: 'stdio-client' // Generic identifier for stdio mode }, () => tool.execute(request.params.arguments || {}, toolUserContext)); return result; } catch (error) { throw new Error(`Tool execution failed: ${error.message}`); } }); return server; } async function main() { console.error('ToolFlow MCP Server starting in stdio mode with API key authentication...'); const serverConfig = getServerConfig(); console.error('SERVER CONFIG:', JSON.stringify({ ...serverConfig, // Don't log potentially sensitive config apiEndpoint: serverConfig.apiEndpoint }, null, 2)); console.error('TRANSPORT: stdio'); // Check for required API key const apiKey = process.env.TOOLFLOW_API_KEY; if (!apiKey) { console.error(''); console.error('=== API Key Required for Stdio Mode ==='); console.error('The TOOLFLOW_API_KEY environment variable is not set.'); console.error(''); console.error('To use stdio mode with API key authentication:'); console.error(''); console.error('1. Get your API key from: https://toolflow-six.vercel.app/dashboard/api-keys'); console.error('2. Add this configuration to your MCP client:'); console.error(''); console.error(JSON.stringify({ "mcpServers": { "toolflow": { "command": "npx", "args": ["@grebyn/toolflow-mcp-server", "--mode=stdio"], "env": { "TOOLFLOW_API_KEY": "tfl_YOUR_API_KEY_HERE" } } } }, null, 2)); console.error(''); console.error('Note: For OAuth authentication, use the default mode (no --mode flag needed)'); console.error('========================================'); process.exit(1); } // Validate API key console.error('Validating API key...'); const validation = await validateApiKey(apiKey); if (!validation.isValid) { console.error(`ERROR: API key validation failed: ${validation.error}`); console.error(''); console.error('Please check your API key:'); console.error('1. Ensure the key is correct and hasn\'t been deleted'); console.error('2. Verify the key hasn\'t expired'); console.error('3. Check your internet connection'); console.error(''); console.error('Generate a new API key at: https://toolflow-six.vercel.app/dashboard/api-keys'); process.exit(1); } console.error(`API key validated successfully for user ${validation.userId}`); // Create user context const userContext = { userId: validation.userId, organizationId: validation.organizationId, apiKey: apiKey }; // Create MCP server const server = createMcpServer(userContext); // Create stdio transport const transport = new StdioServerTransport(); // Connect server to transport console.error('Connecting MCP server to stdio transport...'); await server.connect(transport); console.error('ToolFlow MCP Server ready! You can now use ToolFlow tools in your MCP client.'); // Handle graceful shutdown process.on('SIGINT', async () => { console.error('Shutting down ToolFlow MCP server...'); try { await transport.close(); console.error('Server shutdown complete'); } catch (error) { console.error('Error during shutdown:', error); } process.exit(0); }); process.on('SIGTERM', async () => { console.error('Shutting down ToolFlow MCP server...'); try { await transport.close(); console.error('Server shutdown complete'); } catch (error) { console.error('Error during shutdown:', error); } process.exit(0); }); } // Run the server main().catch(error => { console.error("ToolFlow MCP Server error:", error); process.exit(1); }); //# sourceMappingURL=index-stdio.js.map