/** * OECD MCP Server - HTTP transport for cloud deployment * Supports both SSE (Server-Sent Events) and synchronous HTTP/JSON-RPC */ import express from 'express'; import cors from 'cors'; import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { marked } from 'marked'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { OECDClient } from './oecd-client.js'; import { TOOL_DEFINITIONS, executeTool } from './tools.js'; // Get __dirname equivalent in ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Configure marked for GitHub Flavored Markdown marked.setOptions({ gfm: true, breaks: false, }); const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); // Initialize OECD client - direct API calls only, no caching const client = new OECDClient(); /** * Create and configure an MCP server instance with all handlers */ function createMCPServer(): Server { const server = new Server( { name: 'oecd-mcp-server', version: '4.0.0', }, { capabilities: { tools: {}, resources: {}, prompts: {}, }, } ); // ========== TOOLS ========== server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: TOOL_DEFINITIONS, }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; return await executeTool(client, name, args); }); // ========== RESOURCES ========== server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'oecd://categories', name: 'OECD Data Categories', description: 'List of all 17 OECD data categories with descriptions', mimeType: 'application/json', }, { uri: 'oecd://dataflows/popular', name: 'Popular OECD Datasets', description: 'Curated list of commonly used OECD datasets', mimeType: 'application/json', }, { uri: 'oecd://api/info', name: 'OECD API Information', description: 'Information about the OECD SDMX API endpoints and usage', mimeType: 'application/json', }, ], }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; switch (uri) { case 'oecd://categories': { const categories = client.getCategories(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(categories, null, 2), }, ], }; } case 'oecd://dataflows/popular': { const popular = client.getPopularDatasets(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(popular, null, 2), }, ], }; } case 'oecd://api/info': { const info = client.getApiInfo(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(info, null, 2), }, ], }; } default: throw new Error(`Unknown resource: ${uri}`); } }); // ========== PROMPTS ========== server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: 'analyze_economic_trend', description: 'Analyze economic indicators over time for specified countries', arguments: [ { name: 'indicator', description: 'Economic indicator to analyze (e.g., "GDP", "inflation", "unemployment")', required: true, }, { name: 'countries', description: 'Comma-separated list of country codes (e.g., "USA,GBR,DEU")', required: true, }, { name: 'time_period', description: 'Time period for analysis (e.g., "2020-2023")', required: false, }, ], }, { name: 'compare_countries', description: 'Compare data across multiple countries for a specific indicator', arguments: [ { name: 'indicator', description: 'Indicator to compare (e.g., "GDP per capita", "life expectancy")', required: true, }, { name: 'countries', description: 'Comma-separated list of countries to compare', required: true, }, { name: 'year', description: 'Year for comparison (optional)', required: false, }, ], }, { name: 'get_latest_statistics', description: 'Get the most recent statistics for a specific topic', arguments: [ { name: 'topic', description: 'Topic to get statistics for (e.g., "unemployment", "inflation", "GDP growth")', required: true, }, { name: 'country', description: 'Country code (optional, returns data for all countries if not specified)', required: false, }, ], }, ], }; }); server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case 'analyze_economic_trend': { const { indicator, countries, time_period } = args as { indicator: string; countries: string; time_period?: string; }; return { messages: [ { role: 'user', content: { type: 'text', text: `Analyze the ${indicator} trend for ${countries}${time_period ? ` during ${time_period}` : ''}. Steps: 1. Search for relevant OECD datasets containing ${indicator} data 2. Get the data structure to understand available dimensions 3. Query the data for the specified countries and time period 4. Analyze trends, compare countries, and highlight key insights 5. Provide a summary with visualizable data if possible`, }, }, ], }; } case 'compare_countries': { const { indicator, countries, year } = args as { indicator: string; countries: string; year?: string; }; return { messages: [ { role: 'user', content: { type: 'text', text: `Compare ${indicator} across ${countries}${year ? ` for the year ${year}` : ''}. Steps: 1. Search for OECD datasets containing ${indicator} 2. Query data for all specified countries 3. Compare values and rankings 4. Highlight differences and similarities 5. Provide context about what the differences might indicate`, }, }, ], }; } case 'get_latest_statistics': { const { topic, country } = args as { topic: string; country?: string }; return { messages: [ { role: 'user', content: { type: 'text', text: `Get the latest ${topic} statistics${country ? ` for ${country}` : ' for all OECD countries'}. Steps: 1. Search for datasets related to ${topic} 2. Identify the most relevant and recent dataset 3. Query the latest available data 4. Present key statistics and recent trends 5. Highlight any notable changes or patterns`, }, }, ], }; } default: throw new Error(`Unknown prompt: ${name}`); } }); return server; } // Helper to call MCP handlers directly (for HTTP/JSON-RPC mode) async function callMCPHandler(server: Server, method: string, params?: any) { // Construct proper MCP request object const request = { method, params: params || {} }; // Access server's internal request handlers const handlers = (server as any)['_requestHandlers']; const handler = handlers?.get(method); if (!handler) { throw new Error(`Unknown method: ${method}`); } return handler(request); } // Root endpoint - Mirror of GitHub README app.get('/', (_req, res) => { try { // Read README.md from project root const readmePath = join(__dirname, '..', 'README.md'); const readmeContent = readFileSync(readmePath, 'utf-8'); // Convert markdown to HTML const htmlContent = marked.parse(readmeContent); // Render with GitHub-style CSS res.send(`