UNPKG

veas

Version:

Veas CLI - Command-line interface for Veas platform

212 lines 8.89 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import pc from 'picocolors'; import { AuthManager } from '../auth/auth-manager.js'; import { CacheManager } from '../cache/cache-manager.js'; import { logger } from '../utils/logger.js'; import { getBestAuthToken } from './auth-wrapper.js'; import { executeStandaloneTool, standaloneTools } from './standalone-tools.js'; export class MCPServer { server; cache; authManager; tools; isStandalone = false; constructor(options = {}) { this.server = new Server({ name: 'veas-mcp-server', version: '0.1.0', }, { capabilities: { tools: {}, }, }); this.cache = new CacheManager(options.cacheOptions); this.authManager = AuthManager.getInstance(); this.tools = new Map(); } async initialize() { const pat = process.env.VEAS_PAT; if (!pat) { const isAuthenticated = await this.authManager.isAuthenticated(); if (!isAuthenticated) { throw new Error('Not authenticated. Please run "veas login" first or set VEAS_PAT environment variable.'); } } logger.debug('Loading MCP tools...'); await this.loadTools(); logger.debug('Setting up handlers...'); this.setupHandlers(); logger.debug('MCP server initialized'); } async loadTools() { const authToken = await getBestAuthToken(); const { token, type } = authToken; logger.info(`Using ${type} token for MCP tools`); logger.debug(`Token preview: ${token.substring(0, 20)}...`); logger.debug('Fetching MCP tools from server...'); let mcpTools = []; let useStandalone = false; const forceOffline = process.env.VEAS_OFFLINE_MODE === 'true'; if (forceOffline) { logger.info(pc.yellow('Running in offline mode (VEAS_OFFLINE_MODE=true)')); mcpTools = standaloneTools; useStandalone = true; } else { try { const apiUrl = process.env.VEAS_API_URL || 'https://veas.app'; const response = await fetch(`${apiUrl}/api/mcp-simple`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'X-MCP-Token': token, }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/list', id: '1', }), }); if (response.ok) { const result = await response.json(); if (result.result?.tools) { mcpTools = result.result.tools; logger.debug(`Received ${mcpTools.length} tools via simple MCP endpoint`); useStandalone = false; } else { throw new Error('No tools in response'); } } else { throw new Error(`HTTP ${response.status}: ${await response.text()}`); } } catch (error) { logger.error(`MCP simple endpoint failed: ${error.message}`); logger.error(`API URL: ${process.env.VEAS_API_URL || 'https://veas.app'}/api/mcp-simple`); logger.error(`To use offline mode, set VEAS_OFFLINE_MODE=true`); throw new Error(`Failed to connect to MCP server: ${error.message}`); } } this.isStandalone = useStandalone; for (const tool of mcpTools) { this.tools.set(tool.name, tool); } logger.info(pc.green(`Loaded ${this.tools.size} MCP tools${useStandalone ? ' (standalone mode)' : ''}`)); } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: Array.from(this.tools.values()), })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const tool = this.tools.get(name); if (!tool) { throw new Error(`Tool not found: ${name}`); } const cached = await this.cache.get(name, args); if (cached) { return { content: [{ type: 'text', text: JSON.stringify(cached, null, 2) }], }; } try { const token = process.env.VEAS_PAT || (await this.authManager.getToken()); if (!token && !process.env.VEAS_PAT) { await this.authManager.refreshToken(); } const result = await this.executeTool(name, args); await this.cache.set(name, args, result); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } catch (error) { logger.error(`Error executing tool ${name}:`, error); if (error.message?.includes('authentication') && !process.env.VEAS_PAT) { await this.authManager.refreshToken(); const result = await this.executeTool(name, args); await this.cache.set(name, args, result); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } throw error; } }); } async executeTool(name, args) { const authToken = await getBestAuthToken(); const { token, type } = authToken; logger.debug(`Executing tool ${name} with ${type} token: ${token.substring(0, 20)}...`); if (this.isStandalone) { logger.debug('Executing tool in standalone mode (offline)...'); logger.warn('⚠️ Using mock data - results are not from real server'); try { const result = await executeStandaloneTool(name, args); logger.debug(`Tool executed successfully in standalone mode`); return result; } catch (error) { logger.error(`Standalone execution failed: ${error.message}`); throw error; } } try { logger.debug('Executing tool via mcp-simple endpoint...'); const apiUrl = process.env.VEAS_API_URL || 'https://veas.app'; const response = await fetch(`${apiUrl}/api/mcp-simple`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'X-MCP-Token': token, }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params: { name, arguments: args, }, id: Date.now().toString(), }), }); if (!response.ok) { const error = await response.text(); throw new Error(`Tool execution failed: ${error}`); } const result = await response.json(); if (result?.error) { logger.debug(`Tool error: ${JSON.stringify(result.error)}`); throw new Error(result.error.message); } logger.debug(`Tool executed successfully via mcp-simple`); return result?.result; } catch (error) { logger.error(`MCP simple execution failed: ${error instanceof Error ? error.message : String(error)}`); throw error; } } async start() { const transport = new StdioServerTransport(); await this.server.connect(transport); if (process.stdout.isTTY) { logger.info(pc.green('MCP server started')); logger.debug('Cache stats will be logged periodically'); setInterval(() => { const stats = this.cache.getStats(); logger.debug(`[Cache Stats] Hits: ${stats.hits}, Misses: ${stats.misses}, Keys: ${stats.keys}`); }, 60000); } } async stop() { await this.server.close(); } } //# sourceMappingURL=server.js.map