Skip to main content
Glama
index.js18.1 kB
#!/usr/bin/env node import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, Server, StdioServerTransport, createMcpError } from './mcp-sdk.js'; import { DataFetcher } from './data-fetcher.js'; import { filterItems, formatErrorResponse, getRandomItem, processTemplate } from './utils.js'; // Initialize the data fetcher const dataFetcher = new DataFetcher(); class OperaOmniaMcpServer { constructor() { this.server = new Server({ name: 'opera-omnia-server', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, }, }); // Set up error handling this.server.onerror = (error) => console.error('[MCP Error]', error); // Handle process termination process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); // Register request handlers this.setupResourceHandlers(); this.setupToolHandlers(); } /** * Set up resource handlers for browsing datasets */ setupResourceHandlers() { // List available resources this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: 'opera-omnia://categories', name: 'Opera Omnia Categories', description: 'List of all available data categories', mimeType: 'application/json', }, ], })); // List resource templates this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [ { uriTemplate: 'opera-omnia://category/{category}', name: 'Datasets in Category', description: 'List of datasets available in a specific category', mimeType: 'application/json', }, { uriTemplate: 'opera-omnia://dataset/{category}/{dataset}', name: 'Dataset Contents', description: 'Contents of a specific dataset', mimeType: 'application/json', }, ], })); // Handle resource requests this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { try { const { uri } = request.params; let content; // Categories resource if (uri === 'opera-omnia://categories') { const categories = await dataFetcher.getCategories(); content = JSON.stringify(categories, null, 2); return { contents: [ { uri, mimeType: 'application/json', text: content, }, ], }; } // Category datasets resource const categoryMatch = uri.match(/^opera-omnia:\/\/category\/(.+)$/); if (categoryMatch) { const category = decodeURIComponent(categoryMatch[1]); const datasets = await dataFetcher.getDatasetsInCategory(category); content = JSON.stringify(datasets, null, 2); return { contents: [ { uri, mimeType: 'application/json', text: content, }, ], }; } // Dataset contents resource const datasetMatch = uri.match(/^opera-omnia:\/\/dataset\/(.+)\/(.+)$/); if (datasetMatch) { const category = decodeURIComponent(datasetMatch[1]); const dataset = decodeURIComponent(datasetMatch[2]); const datasetContent = await dataFetcher.getDataset(category, dataset); content = JSON.stringify(datasetContent, null, 2); return { contents: [ { uri, mimeType: 'application/json', text: content, }, ], }; } throw createMcpError(ErrorCode.InvalidRequest, `Invalid resource URI: ${uri}`); } catch (error) { console.error('Error handling resource request:', error); throw createMcpError(ErrorCode.InternalError, formatErrorResponse(error)); } }); } /** * Set up tool handlers for interacting with datasets */ setupToolHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'list_categories', description: 'List all available data categories', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'list_datasets', description: 'List all datasets within a category', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Category name', }, }, required: ['category'], }, }, { name: 'get_dataset', description: 'Get the complete contents of a specific dataset', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Category name', }, dataset: { type: 'string', description: 'Dataset name', }, }, required: ['category', 'dataset'], }, }, { name: 'get_random_item', description: 'Get a random item from a specific dataset', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Category name', }, dataset: { type: 'string', description: 'Dataset name', }, }, required: ['category', 'dataset'], }, }, { name: 'get_filtered_items', description: 'Get items from a dataset that match specific criteria', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Category name', }, dataset: { type: 'string', description: 'Dataset name', }, filter: { type: 'string', description: 'Text to filter items by (case-insensitive)', }, }, required: ['category', 'dataset', 'filter'], }, }, { name: 'combine_datasets', description: 'Combine multiple datasets and get random selections', inputSchema: { type: 'object', properties: { datasets: { type: 'array', description: 'Array of datasets to combine', items: { type: 'object', properties: { category: { type: 'string', description: 'Category name', }, dataset: { type: 'string', description: 'Dataset name', }, }, required: ['category', 'dataset'], }, }, count: { type: 'number', description: 'Number of items to return (default: 1)', }, }, required: ['datasets'], }, }, { name: 'generate_content', description: 'Generate creative content based on multiple datasets', inputSchema: { type: 'object', properties: { template: { type: 'string', description: 'Template string with placeholders in {curly braces}', }, datasets: { type: 'object', description: 'Mapping of placeholder names to datasets', additionalProperties: { type: 'object', properties: { category: { type: 'string', description: 'Category name', }, dataset: { type: 'string', description: 'Dataset name', }, }, required: ['category', 'dataset'], }, }, }, required: ['template', 'datasets'], }, }, ], })); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case 'list_categories': return this.handleListCategories(); case 'list_datasets': { const typedArgs = args; return this.handleListDatasets(typedArgs); } case 'get_dataset': { const typedArgs = args; return this.handleGetDataset(typedArgs); } case 'get_random_item': { const typedArgs = args; return this.handleGetRandomItem(typedArgs); } case 'get_filtered_items': { const typedArgs = args; return this.handleGetFilteredItems(typedArgs); } case 'combine_datasets': { const typedArgs = args; return this.handleCombineDatasets(typedArgs); } case 'generate_content': { const typedArgs = args; return this.handleGenerateContent(typedArgs); } default: throw createMcpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { console.error('Error handling tool call:', error); return { content: [ { type: 'text', text: formatErrorResponse(error), }, ], isError: true, }; } }); } /** * Handle list_categories tool */ async handleListCategories() { const categories = await dataFetcher.getCategories(); return { content: [ { type: 'text', text: JSON.stringify({ categories }, null, 2), }, ], }; } /** * Handle list_datasets tool */ async handleListDatasets(args) { const { category } = args; const datasets = await dataFetcher.getDatasetsInCategory(category); return { content: [ { type: 'text', text: JSON.stringify({ datasets }, null, 2), }, ], }; } /** * Handle get_dataset tool */ async handleGetDataset(args) { const { category, dataset } = args; const datasetContent = await dataFetcher.getDataset(category, dataset); return { content: [ { type: 'text', text: JSON.stringify(datasetContent, null, 2), }, ], }; } /** * Handle get_random_item tool */ async handleGetRandomItem(args) { const { category, dataset } = args; const datasetContent = await dataFetcher.getDataset(category, dataset); const randomItem = getRandomItem(datasetContent.items); return { content: [ { type: 'text', text: randomItem, }, ], }; } /** * Handle get_filtered_items tool */ async handleGetFilteredItems(args) { const { category, dataset, filter } = args; const datasetContent = await dataFetcher.getDataset(category, dataset); const filteredItems = filterItems(datasetContent.items, filter); return { content: [ { type: 'text', text: JSON.stringify(filteredItems, null, 2), }, ], }; } /** * Handle combine_datasets tool */ async handleCombineDatasets(args) { const { datasets, count = 1 } = args; // Fetch all datasets const allItems = []; for (const { category, dataset } of datasets) { const datasetContent = await dataFetcher.getDataset(category, dataset); allItems.push(...datasetContent.items); } // Get random items const result = []; for (let i = 0; i < count; i++) { result.push(getRandomItem(allItems)); } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } /** * Handle generate_content tool */ async handleGenerateContent(args) { const { template, datasets } = args; // Get a random item from each dataset const values = {}; for (const [key, { category, dataset }] of Object.entries(datasets)) { const datasetContent = await dataFetcher.getDataset(category, dataset); values[key] = getRandomItem(datasetContent.items); } // Process the template const result = processTemplate(template, values); return { content: [ { type: 'text', text: result, }, ], }; } /** * Start the MCP server */ async run() { try { // Initialize data fetcher await dataFetcher.initialize(); // Connect to transport const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Opera Omnia MCP server running on stdio'); } catch (error) { console.error('Failed to start MCP server:', error); process.exit(1); } } } // Create and run the server const server = new OperaOmniaMcpServer(); server.run().catch(console.error); //# sourceMappingURL=index.js.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/triptych/opera-omnia-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server