#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type CallToolRequest,
type ListToolsRequest
} from '@modelcontextprotocol/sdk/types.js';
import { PortfolioApiClient } from './api-client.js';
import { PortfolioTools } from './tools.js';
class PortfolioMCPServer {
private server: Server;
private portfolioTools: PortfolioTools;
private apiClient: PortfolioApiClient;
constructor() {
// Get API URL from environment or use default
const apiUrl = process.env.PORTFOLIO_API_URL || 'http://localhost:3000';
const debug = process.env.DEBUG === 'true';
if (debug) {
console.error(`π Portfolio MCP Server starting with API URL: ${apiUrl}`);
}
// Initialize API client and tools
this.apiClient = new PortfolioApiClient(apiUrl);
this.portfolioTools = new PortfolioTools(this.apiClient);
// Create MCP server with proper initialization
this.server = new Server(
{
name: 'portfolio-tracker-mcp-server',
version: '1.0.0',
}
);
this.setupHandlers();
}
private setupHandlers() {
// Handle list tools request
this.server.setRequestHandler(ListToolsRequestSchema, async (_request: ListToolsRequest) => {
const tools = this.portfolioTools.getTools();
if (process.env.DEBUG === 'true') {
console.error(`π Listing ${tools.length} available tools`);
}
return {
tools: tools
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
const { name, arguments: args } = request.params;
if (process.env.DEBUG === 'true') {
console.error(`π§ Tool called: ${name} with args:`, args);
}
try {
const result = await this.portfolioTools.handleToolCall(name, args || {});
if (process.env.DEBUG === 'true') {
console.error(`β
Tool ${name} completed successfully`);
}
return result;
} catch (error: any) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error(`β Tool ${name} failed:`, errorMessage);
return {
content: [{
type: "text",
text: `Error executing ${name}: ${errorMessage}`
}],
isError: true
};
}
});
// Handle server errors
this.server.onerror = (error: any) => {
console.error('β MCP Server error:', error);
};
}
async start() {
// Check API connection
const isConnected = await this.apiClient.checkConnection();
if (!isConnected) {
console.error('β οΈ Warning: Cannot connect to portfolio API. Make sure the portfolio tracker is running on the configured URL.');
} else {
console.error('β
Connected to portfolio API successfully');
}
// Start server with stdio transport
const transport = new StdioServerTransport();
await this.server.connect(transport);
if (process.env.DEBUG === 'true') {
console.error('π― Portfolio MCP Server is ready and listening for requests');
}
}
}
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.error('π Received SIGINT, shutting down gracefully...');
process.exit(0);
});
process.on('SIGTERM', async () => {
console.error('π Received SIGTERM, shutting down gracefully...');
process.exit(0);
});
// Start the server
const server = new PortfolioMCPServer();
server.start().catch((error) => {
console.error('π₯ Failed to start MCP server:', error);
process.exit(1);
});