MCP Server

  • src
#!/usr/bin/env node /** * Folderr MCP Server * * This server provides tools to interact with Folderr's API, specifically for managing * and communicating with Folderr Assistants. It supports both token-based authentication * and direct API key usage. * * @module folderr-server */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CONFIG_FILE = path.join(__dirname, '../config.json'); /** * Configuration interface for the Folderr MCP Server */ interface Config { /** Base URL for the Folderr API */ baseUrl: string; /** Authentication token (from login or API key) */ token?: string; } /** * Response interface for authentication requests */ interface AuthResponse { /** JWT token for authentication */ token: string; /** User information */ user: { /** User's unique identifier */ id: string; /** User's email address */ email: string; }; } class FolderrServer { private server: Server; private config: Config; private axiosInstance; constructor() { this.server = new Server( { name: 'folderr-server', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); // Load or initialize config this.config = this.loadConfig(); this.axiosInstance = axios.create({ baseURL: this.config.baseUrl || 'https://api-staging.folderr.com', headers: this.config.token ? { Authorization: `Bearer ${this.config.token}` } : {}, }); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private loadConfig(): Config { try { if (fs.existsSync(CONFIG_FILE)) { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); } } catch (error) { console.error('Error loading config:', error); } return { baseUrl: 'https://api-staging.folderr.com' }; } private saveConfig() { try { fs.writeFileSync(CONFIG_FILE, JSON.stringify(this.config, null, 2)); } catch (error) { console.error('Error saving config:', error); } } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'set_api_token', description: 'Set an API token for authentication (alternative to login)', inputSchema: { type: 'object', properties: { token: { type: 'string', description: 'API token generated from Folderr developers section', }, }, required: ['token'], }, }, { name: 'login', description: 'Login to Folderr with email and password', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'User email', }, password: { type: 'string', description: 'User password', }, }, required: ['email', 'password'], }, }, { name: 'list_assistants', description: 'List all available assistants', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'ask_assistant', description: 'Ask a question to a specific assistant', inputSchema: { type: 'object', properties: { assistant_id: { type: 'string', description: 'ID of the assistant to ask', }, question: { type: 'string', description: 'Question to ask the assistant', }, }, required: ['assistant_id', 'question'], }, }, { name: 'list_workflows', description: 'List all available workflows', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'get_workflow_inputs', description: 'Get the required inputs for a workflow', inputSchema: { type: 'object', properties: { workflow_id: { type: 'string', description: 'ID of the workflow', }, }, required: ['workflow_id'], }, }, { name: 'execute_workflow', description: 'Execute a workflow with the required inputs', inputSchema: { type: 'object', properties: { workflow_id: { type: 'string', description: 'ID of the workflow', }, inputs: { type: 'object', description: 'Input values required by the workflow', additionalProperties: true, }, }, required: ['workflow_id', 'inputs'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'login': return await this.handleLogin(request.params.arguments); case 'set_api_token': return await this.handleSetApiToken(request.params.arguments); case 'list_assistants': return await this.handleListAssistants(); case 'ask_assistant': return await this.handleAskAssistant(request.params.arguments); case 'list_workflows': return await this.handleListWorkflows(); case 'get_workflow_inputs': return await this.handleGetWorkflowInputs(request.params.arguments); case 'execute_workflow': return await this.handleExecuteWorkflow(request.params.arguments); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } /** * Handle login requests using email and password * @param args Login arguments containing email and password */ private async handleLogin(args: any) { try { const response = await this.axiosInstance.post<AuthResponse>('/api/auth/sign-in', { email: args.email, password: args.password, }); // Update config and axios instance with new token this.config.token = response.data.token; this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${this.config.token}`; this.saveConfig(); return { content: [ { type: 'text', text: 'Successfully logged in', }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Login failed: ${error.response?.data?.message || error.message}`, }, ], isError: true, }; } } /** * Handle setting an API token for authentication * @param args Arguments containing the API token */ private async handleSetApiToken(args: any) { try { // Update config and axios instance with new token this.config.token = args.token; this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${this.config.token}`; this.saveConfig(); return { content: [ { type: 'text', text: 'Successfully set API token', }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Failed to set API token: ${error.message}`, }, ], isError: true, }; } } /** * Handle requests to list all available assistants */ private async handleListAssistants() { if (!this.config.token) { throw new McpError(ErrorCode.InvalidRequest, 'Not logged in'); } try { const response = await this.axiosInstance.get('/api/agent'); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Failed to list assistants: ${error.response?.data?.message || error.message}`, }, ], isError: true, }; } } /** * Handle requests to ask questions to specific assistants * @param args Arguments containing assistant_id and question */ private async handleAskAssistant(args: any) { if (!this.config.token) { throw new McpError(ErrorCode.InvalidRequest, 'Not logged in'); } try { const response = await this.axiosInstance.post( `/api/agent/${args.assistant_id}/message`, { message: args.question } ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Failed to ask assistant: ${error.response?.data?.message || error.message}`, }, ], isError: true, }; } } /** * Handle requests to list all available workflows */ private async handleListWorkflows() { if (!this.config.token) { throw new McpError(ErrorCode.InvalidRequest, 'Not logged in'); } try { const response = await this.axiosInstance.get('/api/workflows'); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Failed to list workflows: ${error.response?.data?.message || error.message}`, }, ], isError: true, }; } } /** * Handle requests to get workflow inputs * @param args Arguments containing workflow_id */ private async handleGetWorkflowInputs(args: any) { if (!this.config.token) { throw new McpError(ErrorCode.InvalidRequest, 'Not logged in'); } try { const response = await this.axiosInstance.get(`/api/workflows/api-client/${args.workflow_id}/inputs`); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Failed to get workflow inputs: ${error.response?.data?.message || error.message}`, }, ], isError: true, }; } } /** * Handle requests to execute a workflow * @param args Arguments containing workflow_id and inputs */ private async handleExecuteWorkflow(args: any) { if (!this.config.token) { throw new McpError(ErrorCode.InvalidRequest, 'Not logged in'); } try { const response = await this.axiosInstance.post( `/api/workflows/api-client/${args.workflow_id}`, args.inputs ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Failed to execute workflow: ${error.response?.data?.message || error.message}`, }, ], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Folderr MCP server running on stdio'); } } const server = new FolderrServer(); server.run().catch(console.error);