Skip to main content
Glama
index.ts42.8 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import axios, { AxiosInstance } from 'axios'; // BioOntology API interfaces interface OntologyInfo { '@id': string; '@type': string; acronym: string; name: string; administeredBy?: string[]; flat?: boolean; summaryOnly?: boolean; ontologyType?: string; submissions?: any[]; projects?: any[]; notes?: any[]; reviews?: any[]; group?: string[]; hasDomain?: string[]; links?: { [key: string]: string; }; } interface ClassInfo { '@id': string; '@type': string; prefLabel?: string; synonym?: string[]; definition?: string[]; obsolete?: boolean; semanticType?: string[]; cui?: string[]; notation?: string; prefixIRI?: string; parents?: any[]; children?: any[]; ancestors?: any[]; descendants?: any[]; properties?: any; links?: { [key: string]: string; }; } // Validation functions const isValidSearchTermsArgs = ( args: any ): args is { query: string; ontologies?: string; require_exact_match?: boolean; suggest?: boolean; also_search_views?: boolean; require_definitions?: boolean; also_search_properties?: boolean; also_search_obsolete?: boolean; cui?: string; semantic_types?: string; include?: string; page?: number; pagesize?: number; language?: string; } => { return ( typeof args === 'object' && args !== null && typeof args.query === 'string' && (args.ontologies === undefined || typeof args.ontologies === 'string') && (args.require_exact_match === undefined || typeof args.require_exact_match === 'boolean') && (args.suggest === undefined || typeof args.suggest === 'boolean') && (args.also_search_views === undefined || typeof args.also_search_views === 'boolean') && (args.require_definitions === undefined || typeof args.require_definitions === 'boolean') && (args.also_search_properties === undefined || typeof args.also_search_properties === 'boolean') && (args.also_search_obsolete === undefined || typeof args.also_search_obsolete === 'boolean') && (args.cui === undefined || typeof args.cui === 'string') && (args.semantic_types === undefined || typeof args.semantic_types === 'string') && (args.include === undefined || typeof args.include === 'string') && (args.page === undefined || (typeof args.page === 'number' && args.page > 0)) && (args.pagesize === undefined || (typeof args.pagesize === 'number' && args.pagesize > 0 && args.pagesize <= 500)) && (args.language === undefined || typeof args.language === 'string') ); }; const isValidSearchPropertiesArgs = ( args: any ): args is { query: string; ontologies?: string; require_exact_match?: boolean; also_search_views?: boolean; require_definitions?: boolean; include?: string; ontology_types?: string; property_types?: string; page?: number; pagesize?: number; } => { return ( typeof args === 'object' && args !== null && typeof args.query === 'string' && (args.ontologies === undefined || typeof args.ontologies === 'string') && (args.require_exact_match === undefined || typeof args.require_exact_match === 'boolean') && (args.also_search_views === undefined || typeof args.also_search_views === 'boolean') && (args.require_definitions === undefined || typeof args.require_definitions === 'boolean') && (args.include === undefined || typeof args.include === 'string') && (args.ontology_types === undefined || typeof args.ontology_types === 'string') && (args.property_types === undefined || typeof args.property_types === 'string') && (args.page === undefined || (typeof args.page === 'number' && args.page > 0)) && (args.pagesize === undefined || (typeof args.pagesize === 'number' && args.pagesize > 0 && args.pagesize <= 500)) ); }; const isValidAnnotateTextArgs = ( args: any ): args is { text: string; ontologies?: string; semantic_types?: string; expand_semantic_types_hierarchy?: boolean; expand_class_hierarchy?: boolean; class_hierarchy_max_level?: number; expand_mappings?: boolean; stop_words?: string; minimum_match_length?: number; exclude_numbers?: boolean; whole_word_only?: boolean; exclude_synonyms?: boolean; longest_only?: boolean; } => { return ( typeof args === 'object' && args !== null && typeof args.text === 'string' && args.text.length > 0 && (args.ontologies === undefined || typeof args.ontologies === 'string') && (args.semantic_types === undefined || typeof args.semantic_types === 'string') && (args.expand_semantic_types_hierarchy === undefined || typeof args.expand_semantic_types_hierarchy === 'boolean') && (args.expand_class_hierarchy === undefined || typeof args.expand_class_hierarchy === 'boolean') && (args.class_hierarchy_max_level === undefined || (typeof args.class_hierarchy_max_level === 'number' && args.class_hierarchy_max_level >= 0)) && (args.expand_mappings === undefined || typeof args.expand_mappings === 'boolean') && (args.stop_words === undefined || typeof args.stop_words === 'string') && (args.minimum_match_length === undefined || (typeof args.minimum_match_length === 'number' && args.minimum_match_length > 0)) && (args.exclude_numbers === undefined || typeof args.exclude_numbers === 'boolean') && (args.whole_word_only === undefined || typeof args.whole_word_only === 'boolean') && (args.exclude_synonyms === undefined || typeof args.exclude_synonyms === 'boolean') && (args.longest_only === undefined || typeof args.longest_only === 'boolean') ); }; const isValidRecommendOntologiesArgs = ( args: any ): args is { input: string; input_type?: number; output_type?: number; max_elements_set?: number; wc?: number; wa?: number; wd?: number; ws?: number; ontologies?: string; } => { return ( typeof args === 'object' && args !== null && typeof args.input === 'string' && args.input.length > 0 && (args.input_type === undefined || (typeof args.input_type === 'number' && [1, 2].includes(args.input_type))) && (args.output_type === undefined || (typeof args.output_type === 'number' && [1, 2].includes(args.output_type))) && (args.max_elements_set === undefined || (typeof args.max_elements_set === 'number' && [2, 3, 4].includes(args.max_elements_set))) && (args.wc === undefined || (typeof args.wc === 'number' && args.wc >= 0 && args.wc <= 1)) && (args.wa === undefined || (typeof args.wa === 'number' && args.wa >= 0 && args.wa <= 1)) && (args.wd === undefined || (typeof args.wd === 'number' && args.wd >= 0 && args.wd <= 1)) && (args.ws === undefined || (typeof args.ws === 'number' && args.ws >= 0 && args.ws <= 1)) && (args.ontologies === undefined || typeof args.ontologies === 'string') ); }; const isValidGetOntologyInfoArgs = ( args: any ): args is { acronym: string; include_views?: boolean } => { return ( typeof args === 'object' && args !== null && typeof args.acronym === 'string' && args.acronym.length > 0 && (args.include_views === undefined || typeof args.include_views === 'boolean') ); }; const isValidGetClassInfoArgs = ( args: any ): args is { ontology: string; class_id: string; include?: string } => { return ( typeof args === 'object' && args !== null && typeof args.ontology === 'string' && args.ontology.length > 0 && typeof args.class_id === 'string' && args.class_id.length > 0 && (args.include === undefined || typeof args.include === 'string') ); }; const isValidSearchOntologiesArgs = ( args: any ): args is { query?: string; also_search_views?: boolean; include_views?: boolean; display_context?: boolean; display_links?: boolean; } => { return ( typeof args === 'object' && args !== null && (args.query === undefined || typeof args.query === 'string') && (args.also_search_views === undefined || typeof args.also_search_views === 'boolean') && (args.include_views === undefined || typeof args.include_views === 'boolean') && (args.display_context === undefined || typeof args.display_context === 'boolean') && (args.display_links === undefined || typeof args.display_links === 'boolean') ); }; const isValidGetAnalyticsArgs = ( args: any ): args is { month?: number; year?: number; ontology?: string } => { return ( typeof args === 'object' && args !== null && (args.month === undefined || (typeof args.month === 'number' && args.month >= 1 && args.month <= 12)) && (args.year === undefined || (typeof args.year === 'number' && args.year >= 2013)) && (args.ontology === undefined || typeof args.ontology === 'string') ); }; class BioOntologyServer { private server: Server; private apiClient: AxiosInstance; private apiKey: string; constructor() { this.server = new Server( { name: 'bioontology-server', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, }, } ); // Get API key from environment this.apiKey = process.env.BIOONTOLOGY_API_KEY || ''; if (!this.apiKey) { console.error('Warning: BIOONTOLOGY_API_KEY environment variable not set. Some operations may fail.'); } // Initialize BioOntology API client this.apiClient = axios.create({ baseURL: 'https://data.bioontology.org', timeout: 60000, headers: { 'User-Agent': 'BioOntology-MCP-Server/0.1.0', }, }); this.setupResourceHandlers(); this.setupToolHandlers(); // Error handling this.server.onerror = (error: any) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupResourceHandlers() { // List available resource templates this.server.setRequestHandler( ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [ { uriTemplate: 'bioontology://ontology/{acronym}', name: 'BioOntology ontology information', mimeType: 'application/json', description: 'Complete ontology information and metadata', }, { uriTemplate: 'bioontology://class/{ontology}/{class_id}', name: 'Ontology class details', mimeType: 'application/json', description: 'Detailed class information including hierarchy and properties', }, { uriTemplate: 'bioontology://search/{query}', name: 'Term search results', mimeType: 'application/json', description: 'Search results for ontology terms matching the query', }, { uriTemplate: 'bioontology://annotations/{text}', name: 'Text annotation results', mimeType: 'application/json', description: 'Ontology term annotations for the provided text', }, { uriTemplate: 'bioontology://recommendations/{input}', name: 'Ontology recommendations', mimeType: 'application/json', description: 'Recommended ontologies for the input text or keywords', }, { uriTemplate: 'bioontology://analytics/{ontology}', name: 'Ontology analytics data', mimeType: 'application/json', description: 'Usage statistics and analytics for an ontology', }, ], }) ); // Handle resource requests this.server.setRequestHandler( ReadResourceRequestSchema, async (request: any) => { const uri = request.params.uri; // Handle ontology info requests const ontologyMatch = uri.match(/^bioontology:\/\/ontology\/([A-Z0-9_-]+)$/i); if (ontologyMatch) { const acronym = ontologyMatch[1]; try { const response = await this.apiClient.get(`/ontologies/${acronym}`, { params: { apikey: this.apiKey }, }); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to fetch ontology ${acronym}: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } // Handle class info requests const classMatch = uri.match(/^bioontology:\/\/class\/([A-Z0-9_-]+)\/(.+)$/i); if (classMatch) { const ontology = classMatch[1]; const classId = decodeURIComponent(classMatch[2]); try { const response = await this.apiClient.get(`/ontologies/${ontology}/classes/${encodeURIComponent(classId)}`, { params: { apikey: this.apiKey }, }); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to fetch class ${classId} from ${ontology}: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } // Handle search requests const searchMatch = uri.match(/^bioontology:\/\/search\/(.+)$/); if (searchMatch) { const query = decodeURIComponent(searchMatch[1]); try { const response = await this.apiClient.get('/search', { params: { q: query, apikey: this.apiKey, pagesize: 25, }, }); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to search for "${query}": ${error instanceof Error ? error.message : 'Unknown error'}` ); } } // Handle annotation requests const annotationMatch = uri.match(/^bioontology:\/\/annotations\/(.+)$/); if (annotationMatch) { const text = decodeURIComponent(annotationMatch[1]); try { const response = await this.apiClient.get('/annotator', { params: { text: text, apikey: this.apiKey, }, }); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to annotate text: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } // Handle recommendation requests const recommendationMatch = uri.match(/^bioontology:\/\/recommendations\/(.+)$/); if (recommendationMatch) { const input = decodeURIComponent(recommendationMatch[1]); try { const response = await this.apiClient.get('/recommender', { params: { input: input, apikey: this.apiKey, }, }); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to get recommendations for "${input}": ${error instanceof Error ? error.message : 'Unknown error'}` ); } } // Handle analytics requests const analyticsMatch = uri.match(/^bioontology:\/\/analytics\/([A-Z0-9_-]+)$/i); if (analyticsMatch) { const ontology = analyticsMatch[1]; try { const response = await this.apiClient.get(`/ontologies/${ontology}/analytics`, { params: { apikey: this.apiKey }, }); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to fetch analytics for ${ontology}: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } throw new McpError( ErrorCode.InvalidRequest, `Invalid URI format: ${uri}` ); } ); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ // Search & Discovery Tools { name: 'search_terms', description: 'Search across ontology terms with advanced filtering options', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for ontology terms' }, ontologies: { type: 'string', description: 'Comma-separated list of ontology acronyms to search in' }, require_exact_match: { type: 'boolean', description: 'Require exact match (default: false)' }, suggest: { type: 'boolean', description: 'Enable suggestion mode for type-ahead (default: false)' }, also_search_views: { type: 'boolean', description: 'Include ontology views in search (default: false)' }, require_definitions: { type: 'boolean', description: 'Only return terms with definitions (default: false)' }, also_search_properties: { type: 'boolean', description: 'Search in properties as well (default: false)' }, also_search_obsolete: { type: 'boolean', description: 'Include obsolete terms (default: false)' }, cui: { type: 'string', description: 'Comma-separated CUIs to filter by' }, semantic_types: { type: 'string', description: 'Comma-separated semantic types to filter by' }, include: { type: 'string', description: 'Comma-separated attributes to include (e.g., prefLabel,synonym,definition)' }, page: { type: 'number', description: 'Page number (default: 1)', minimum: 1 }, pagesize: { type: 'number', description: 'Results per page (default: 50, max: 500)', minimum: 1, maximum: 500 }, language: { type: 'string', description: 'Language code (e.g., en, fr)' }, }, required: ['query'], }, }, { name: 'search_properties', description: 'Search ontology properties by their labels and IDs', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for properties' }, ontologies: { type: 'string', description: 'Comma-separated list of ontology acronyms' }, require_exact_match: { type: 'boolean', description: 'Require exact match (default: false)' }, also_search_views: { type: 'boolean', description: 'Include ontology views (default: false)' }, require_definitions: { type: 'boolean', description: 'Only return properties with definitions (default: false)' }, include: { type: 'string', description: 'Attributes to include (default: label,labelGenerated,definition,parents)' }, ontology_types: { type: 'string', description: 'Ontology types to include (e.g., ONTOLOGY,VALUE_SET_COLLECTION)' }, property_types: { type: 'string', description: 'Property types (object,annotation,datatype)' }, page: { type: 'number', description: 'Page number (default: 1)', minimum: 1 }, pagesize: { type: 'number', description: 'Results per page (default: 50, max: 500)', minimum: 1, maximum: 500 }, }, required: ['query'], }, }, { name: 'search_ontologies', description: 'Search for ontologies by name, description, or domain', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for ontologies (optional for listing all)' }, also_search_views: { type: 'boolean', description: 'Include ontology views (default: false)' }, include_views: { type: 'boolean', description: 'Include views in results (default: false)' }, display_context: { type: 'boolean', description: 'Include JSON-LD context (default: true)' }, display_links: { type: 'boolean', description: 'Include hypermedia links (default: true)' }, }, required: [], }, }, { name: 'get_ontology_info', description: 'Get detailed information about a specific ontology', inputSchema: { type: 'object', properties: { acronym: { type: 'string', description: 'Ontology acronym (e.g., NCIT, GO, MESH)' }, include_views: { type: 'boolean', description: 'Include ontology views (default: false)' }, }, required: ['acronym'], }, }, // Text Analysis Tools { name: 'annotate_text', description: 'Analyze text and identify relevant ontology terms with configurable parameters', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Text to annotate with ontology terms' }, ontologies: { type: 'string', description: 'Comma-separated ontology acronyms to use for annotation' }, semantic_types: { type: 'string', description: 'Comma-separated semantic types to filter by' }, expand_semantic_types_hierarchy: { type: 'boolean', description: 'Include children of semantic types (default: false)' }, expand_class_hierarchy: { type: 'boolean', description: 'Include class ancestors in annotation (default: false)' }, class_hierarchy_max_level: { type: 'number', description: 'Maximum hierarchy depth (default: 0)', minimum: 0 }, expand_mappings: { type: 'boolean', description: 'Use manual mappings (UMLS, REST, CUI, OBOXREF) (default: false)' }, stop_words: { type: 'string', description: 'Comma-separated custom stop words' }, minimum_match_length: { type: 'number', description: 'Minimum character length for matches', minimum: 1 }, exclude_numbers: { type: 'boolean', description: 'Exclude numeric matches (default: false)' }, whole_word_only: { type: 'boolean', description: 'Match whole words only (default: true)' }, exclude_synonyms: { type: 'boolean', description: 'Exclude synonym matches (default: false)' }, longest_only: { type: 'boolean', description: 'Return only longest matches (default: false)' }, }, required: ['text'], }, }, { name: 'recommend_ontologies', description: 'Get ontology recommendations for text or keywords with customizable weights', inputSchema: { type: 'object', properties: { input: { type: 'string', description: 'Input text or comma-separated keywords' }, input_type: { type: 'number', description: 'Input type: 1=text, 2=keywords (default: 1)', enum: [1, 2] }, output_type: { type: 'number', description: 'Output type: 1=individual ontologies, 2=ontology sets (default: 1)', enum: [1, 2] }, max_elements_set: { type: 'number', description: 'Max ontologies per set (2-4, default: 3)', enum: [2, 3, 4] }, wc: { type: 'number', description: 'Weight for coverage criterion (0-1, default: 0.55)', minimum: 0, maximum: 1 }, wa: { type: 'number', description: 'Weight for acceptance criterion (0-1, default: 0.15)', minimum: 0, maximum: 1 }, wd: { type: 'number', description: 'Weight for detail criterion (0-1, default: 0.15)', minimum: 0, maximum: 1 }, ws: { type: 'number', description: 'Weight for specialization criterion (0-1, default: 0.15)', minimum: 0, maximum: 1 }, ontologies: { type: 'string', description: 'Comma-separated ontology acronyms to limit evaluation to' }, }, required: ['input'], }, }, { name: 'batch_annotate', description: 'Process multiple texts for annotation efficiently', inputSchema: { type: 'object', properties: { texts: { type: 'array', items: { type: 'string' }, description: 'Array of texts to annotate (max 10)', minItems: 1, maxItems: 10 }, ontologies: { type: 'string', description: 'Comma-separated ontology acronyms' }, longest_only: { type: 'boolean', description: 'Return only longest matches (default: true)' }, whole_word_only: { type: 'boolean', description: 'Match whole words only (default: true)' }, }, required: ['texts'], }, }, // Ontology Navigation Tools { name: 'get_class_info', description: 'Get detailed information about a specific ontology class', inputSchema: { type: 'object', properties: { ontology: { type: 'string', description: 'Ontology acronym' }, class_id: { type: 'string', description: 'Class ID/URI (URL-encoded if necessary)' }, include: { type: 'string', description: 'Comma-separated attributes to include (e.g., prefLabel,definition,parents,children)' }, }, required: ['ontology', 'class_id'], }, }, // Analytics & Metadata Tools { name: 'get_ontology_metrics', description: 'Get usage statistics and quality metrics for an ontology', inputSchema: { type: 'object', properties: { ontology: { type: 'string', description: 'Ontology acronym' }, }, required: ['ontology'], }, }, { name: 'get_analytics_data', description: 'Get visitor statistics and popularity trends with optional date filtering', inputSchema: { type: 'object', properties: { month: { type: 'number', description: 'Month (1-12) for specific data', minimum: 1, maximum: 12 }, year: { type: 'number', description: 'Year for specific data (2013+)', minimum: 2013 }, ontology: { type: 'string', description: 'Specific ontology acronym (optional)' }, }, required: [], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => { const { name, arguments: args } = request.params; switch (name) { // Search & Discovery Tools case 'search_terms': return this.handleSearchTerms(args); case 'search_properties': return this.handleSearchProperties(args); case 'search_ontologies': return this.handleSearchOntologies(args); case 'get_ontology_info': return this.handleGetOntologyInfo(args); // Text Analysis Tools case 'annotate_text': return this.handleAnnotateText(args); case 'recommend_ontologies': return this.handleRecommendOntologies(args); case 'batch_annotate': return this.handleBatchAnnotate(args); // Ontology Navigation Tools case 'get_class_info': return this.handleGetClassInfo(args); // Analytics & Metadata Tools case 'get_ontology_metrics': return this.handleGetOntologyMetrics(args); case 'get_analytics_data': return this.handleGetAnalyticsData(args); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } }); } // Tool handler implementations private async handleSearchTerms(args: any) { if (!isValidSearchTermsArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid search terms arguments'); } try { const params: any = { q: args.query, apikey: this.apiKey, }; // Add optional parameters if (args.ontologies) params.ontologies = args.ontologies; if (args.require_exact_match !== undefined) params.require_exact_match = args.require_exact_match; if (args.suggest !== undefined) params.suggest = args.suggest; if (args.also_search_views !== undefined) params.also_search_views = args.also_search_views; if (args.require_definitions !== undefined) params.require_definitions = args.require_definitions; if (args.also_search_properties !== undefined) params.also_search_properties = args.also_search_properties; if (args.also_search_obsolete !== undefined) params.also_search_obsolete = args.also_search_obsolete; if (args.cui) params.cui = args.cui; if (args.semantic_types) params.semantic_types = args.semantic_types; if (args.include) params.include = args.include; if (args.page) params.page = args.page; if (args.pagesize) params.pagesize = args.pagesize; if (args.language) params.language = args.language; const response = await this.apiClient.get('/search', { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error searching terms: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleSearchProperties(args: any) { if (!isValidSearchPropertiesArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid search properties arguments'); } try { const params: any = { q: args.query, apikey: this.apiKey, }; // Add optional parameters if (args.ontologies) params.ontologies = args.ontologies; if (args.require_exact_match !== undefined) params.require_exact_match = args.require_exact_match; if (args.also_search_views !== undefined) params.also_search_views = args.also_search_views; if (args.require_definitions !== undefined) params.require_definitions = args.require_definitions; if (args.include) params.include = args.include; if (args.ontology_types) params.ontology_types = args.ontology_types; if (args.property_types) params.property_types = args.property_types; if (args.page) params.page = args.page; if (args.pagesize) params.pagesize = args.pagesize; const response = await this.apiClient.get('/property_search', { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error searching properties: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleSearchOntologies(args: any) { if (!isValidSearchOntologiesArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid search ontologies arguments'); } try { const params: any = { apikey: this.apiKey, }; // Add optional parameters if (args.query) params.q = args.query; if (args.also_search_views !== undefined) params.also_search_views = args.also_search_views; if (args.include_views !== undefined) params.include_views = args.include_views; if (args.display_context !== undefined) params.display_context = args.display_context; if (args.display_links !== undefined) params.display_links = args.display_links; const response = await this.apiClient.get('/ontologies', { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error searching ontologies: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetOntologyInfo(args: any) { if (!isValidGetOntologyInfoArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid ontology info arguments'); } try { const params: any = { apikey: this.apiKey, }; if (args.include_views !== undefined) params.include_views = args.include_views; const response = await this.apiClient.get(`/ontologies/${args.acronym}`, { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error fetching ontology info: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleAnnotateText(args: any) { if (!isValidAnnotateTextArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid annotate text arguments'); } try { const params: any = { text: args.text, apikey: this.apiKey, }; // Add optional parameters if (args.ontologies) params.ontologies = args.ontologies; if (args.semantic_types) params.semantic_types = args.semantic_types; if (args.expand_semantic_types_hierarchy !== undefined) params.expand_semantic_types_hierarchy = args.expand_semantic_types_hierarchy; if (args.expand_class_hierarchy !== undefined) params.expand_class_hierarchy = args.expand_class_hierarchy; if (args.class_hierarchy_max_level !== undefined) params.class_hierarchy_max_level = args.class_hierarchy_max_level; if (args.expand_mappings !== undefined) params.expand_mappings = args.expand_mappings; if (args.stop_words) params.stop_words = args.stop_words; if (args.minimum_match_length !== undefined) params.minimum_match_length = args.minimum_match_length; if (args.exclude_numbers !== undefined) params.exclude_numbers = args.exclude_numbers; if (args.whole_word_only !== undefined) params.whole_word_only = args.whole_word_only; if (args.exclude_synonyms !== undefined) params.exclude_synonyms = args.exclude_synonyms; if (args.longest_only !== undefined) params.longest_only = args.longest_only; const response = await this.apiClient.get('/annotator', { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error annotating text: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleRecommendOntologies(args: any) { if (!isValidRecommendOntologiesArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid recommend ontologies arguments'); } try { const params: any = { input: args.input, apikey: this.apiKey, }; // Add optional parameters if (args.input_type !== undefined) params.input_type = args.input_type; if (args.output_type !== undefined) params.output_type = args.output_type; if (args.max_elements_set !== undefined) params.max_elements_set = args.max_elements_set; if (args.wc !== undefined) params.wc = args.wc; if (args.wa !== undefined) params.wa = args.wa; if (args.wd !== undefined) params.wd = args.wd; if (args.ws !== undefined) params.ws = args.ws; if (args.ontologies) params.ontologies = args.ontologies; const response = await this.apiClient.get('/recommender', { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error recommending ontologies: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleBatchAnnotate(args: any) { if (!Array.isArray(args.texts) || args.texts.length === 0 || args.texts.length > 10) { throw new McpError(ErrorCode.InvalidParams, 'Invalid batch annotate arguments - texts must be array of 1-10 items'); } try { const results = []; for (const text of args.texts) { const params: any = { text: text, apikey: this.apiKey, }; if (args.ontologies) params.ontologies = args.ontologies; if (args.longest_only !== undefined) params.longest_only = args.longest_only; if (args.whole_word_only !== undefined) params.whole_word_only = args.whole_word_only; try { const response = await this.apiClient.get('/annotator', { params }); results.push({ text, annotations: response.data, success: true }); } catch (error: any) { results.push({ text, error: error.message, success: false }); } } return { content: [ { type: 'text', text: JSON.stringify({ batch_results: results }, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error in batch annotation: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetClassInfo(args: any) { if (!isValidGetClassInfoArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid class info arguments'); } try { const params: any = { apikey: this.apiKey, }; if (args.include) params.include = args.include; const response = await this.apiClient.get(`/ontologies/${args.ontology}/classes/${encodeURIComponent(args.class_id)}`, { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error fetching class info: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetOntologyMetrics(args: any) { if (!args.ontology) { throw new McpError(ErrorCode.InvalidParams, 'Invalid ontology metrics arguments'); } try { const params: any = { apikey: this.apiKey, }; const response = await this.apiClient.get(`/ontologies/${args.ontology}/metrics`, { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error fetching ontology metrics: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetAnalyticsData(args: any) { if (!isValidGetAnalyticsArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid analytics data arguments'); } try { const params: any = { apikey: this.apiKey, }; if (args.month !== undefined) params.month = args.month; if (args.year !== undefined) params.year = args.year; let endpoint = '/analytics'; if (args.ontology) { endpoint = `/ontologies/${args.ontology}/analytics`; } const response = await this.apiClient.get(endpoint, { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error fetching analytics data: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('BioOntology MCP server running on stdio'); } } const server = new BioOntologyServer(); server.run().catch(console.error);

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/Augmented-Nature/BioOntology-MCP-Server'

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