@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
JavaScript
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