veas
Version:
Veas CLI - Command-line interface for Veas platform
212 lines • 8.89 kB
JavaScript
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