Skip to main content
Glama
reetp14
by reetp14
index.ts18.2 kB
#!/usr/bin/env node /** * OpenAlex MCP Server * * This MCP server provides access to the OpenAlex API, which is a fully open catalog * of the global research system. It exposes tools for searching and retrieving: * - Works (scholarly articles, preprints, datasets, books) * - Authors (researchers and creators) * - Sources (journals, conferences, repositories) * - Institutions (universities, hospitals, labs) * - Concepts (hierarchical research topics) * - Publishers (publishing organizations) * - Funders (grant-making bodies) * - Autocomplete and text classification utilities */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, CallToolRequest } from "@modelcontextprotocol/sdk/types.js"; import dotenv from "dotenv"; // Import tool handlers import { searchWorks } from "./tools/searchWorks.js"; import { searchAuthors } from "./tools/searchAuthors.js"; import { searchSources } from "./tools/searchSources.js"; import { searchInstitutions } from "./tools/searchInstitutions.js"; import { searchTopics } from "./tools/searchTopics.js"; import { searchPublishers } from "./tools/searchPublishers.js"; import { searchFunders } from "./tools/searchFunders.js"; import { getEntity } from "./tools/getEntity.js"; import { autocomplete } from "./tools/autocomplete.js"; import { classifyText } from "./tools/classifyText.js"; import { getFilterableFields } from "./tools/getFilterableFields.js"; // Load environment variables dotenv.config(); /** * Create an MCP server for OpenAlex API access */ const server = new Server({ name: "openalex-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, }); /** * Handler that lists available tools for OpenAlex API access */ (server as any).setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search_works", description: "Search scholarly works in OpenAlex", inputSchema: { type: "object", properties: { search: { type: "string", description: "Full-text search query" }, filter: { type: "string", description: "Key:value OpenAlex filters. Supports entity attributes (e.g., 'publication_year', 'is_oa'), IDs, and convenience filters (e.g., 'title.search'). Example: 'is_oa:true,type:journal'" }, sort: { type: "string", description: "Sort field with optional :desc (e.g., 'cited_by_count:desc')" }, page: { type: "number", description: "Page number (max 10,000 results total)" }, per_page: { type: "number", description: "Results per page (max 200)" }, cursor: { type: "string", description: "Cursor for deep pagination (use '*' for first call)" }, group_by: { type: "string", description: "Group results by field for faceting" }, select: { type: "string", description: "Comma-separated list of fields to return" }, sample: { type: "number", description: "Random sample size" }, seed: { type: "number", description: "Random seed for reproducible sampling" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" }, bearer_token: { type: "string", description: "Bearer token for authentication" }, view: { type: "string", "enum": ["summary", "full"], description: "The view of the data to return. 'summary' returns a concise version, 'full' returns the complete object." } } } }, { name: "search_authors", description: "Search authors and researchers", inputSchema: { type: "object", properties: { search: { type: "string", description: "Full-text search query" }, filter: { type: "string", description: "Key:value OpenAlex filters. Supports entity attributes (e.g., 'orcid', 'last_known_institution.id'), IDs, and convenience filters (e.g., 'display_name.search'). Example: 'has_orcid:true,last_known_institution.country_code:US'" }, sort: { type: "string", description: "Sort field with optional :desc" }, page: { type: "number", description: "Page number" }, per_page: { type: "number", description: "Results per page (max 200)" }, cursor: { type: "string", description: "Cursor for deep pagination" }, group_by: { type: "string", description: "Group results by field" }, select: { type: "string", description: "Fields to return" }, sample: { type: "number", description: "Random sample size" }, seed: { type: "number", description: "Random seed" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } } } }, { name: "search_sources", description: "Search journals and sources", inputSchema: { type: "object", properties: { search: { type: "string", description: "Full-text search query" }, filter: { type: "string", description: "Key:value OpenAlex filters. Supports entity attributes (e.g., 'issn', 'country_code', 'is_oa'), IDs, and convenience filters (e.g., 'display_name.search'). Example: 'is_oa:true,type:journal'" }, sort: { type: "string", description: "Sort field with optional :desc" }, page: { type: "number", description: "Page number" }, per_page: { type: "number", description: "Results per page (max 200)" }, cursor: { type: "string", description: "Cursor for deep pagination" }, group_by: { type: "string", description: "Group results by field" }, select: { type: "string", description: "Fields to return" }, sample: { type: "number", description: "Random sample size" }, seed: { type: "number", description: "Random seed" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } } } }, { name: "search_institutions", description: "Search institutions", inputSchema: { type: "object", properties: { search: { type: "string", description: "Full-text search query" }, filter: { type: "string", description: "Key:value OpenAlex filters. Supports entity attributes (e.g., 'ror', 'country_code', 'type'), IDs, and convenience filters (e.g., 'display_name.search'). Example: 'country_code:US,type:education'" }, sort: { type: "string", description: "Sort field with optional :desc" }, page: { type: "number", description: "Page number" }, per_page: { type: "number", description: "Results per page (max 200)" }, cursor: { type: "string", description: "Cursor for deep pagination" }, group_by: { type: "string", description: "Group results by field" }, select: { type: "string", description: "Fields to return" }, sample: { type: "number", description: "Random sample size" }, seed: { type: "number", description: "Random seed" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } } } }, { name: "search_topics", description: "Search research topics (formerly concepts)", inputSchema: { type: "object", properties: { search: { type: "string", description: "Full-text search query" }, filter: { type: "string", description: "Key:value OpenAlex filters. Supports entity attributes (e.g., 'domain.id', 'level'), IDs, and convenience filters (e.g., 'display_name.search'). Example: 'domain.id:D1,level:0'" }, sort: { type: "string", description: "Sort field with optional :desc" }, page: { type: "number", description: "Page number" }, per_page: { type: "number", description: "Results per page (max 200)" }, cursor: { type: "string", description: "Cursor for deep pagination" }, group_by: { type: "string", description: "Group results by field" }, select: { type: "string", description: "Fields to return" }, sample: { type: "number", description: "Random sample size" }, seed: { type: "number", description: "Random seed" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } } } }, { name: "search_publishers", description: "Search publishers", inputSchema: { type: "object", properties: { search: { type: "string", description: "Full-text search query" }, filter: { type: "string", description: "Key:value OpenAlex filters. Supports entity attributes (e.g., 'country_codes', 'hierarchy_level'), IDs, and convenience filters (e.g., 'display_name.search'). Example: 'country_codes:US,hierarchy_level:0'" }, sort: { type: "string", description: "Sort field with optional :desc" }, page: { type: "number", description: "Page number" }, per_page: { type: "number", description: "Results per page (max 200)" }, cursor: { type: "string", description: "Cursor for deep pagination" }, group_by: { type: "string", description: "Group results by field" }, select: { type: "string", description: "Fields to return" }, sample: { type: "number", description: "Random sample size" }, seed: { type: "number", description: "Random seed" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } } } }, { name: "search_funders", description: "Search funders", inputSchema: { type: "object", properties: { search: { type: "string", description: "Full-text search query" }, filter: { type: "string", description: "Key:value OpenAlex filters. Supports entity attributes (e.g., 'country_code', 'grants_count'), IDs, and convenience filters (e.g., 'display_name.search'). Example: 'country_code:DE,grants_count:>10'" }, sort: { type: "string", description: "Sort field with optional :desc" }, page: { type: "number", description: "Page number" }, per_page: { type: "number", description: "Results per page (max 200)" }, cursor: { type: "string", description: "Cursor for deep pagination" }, group_by: { type: "string", description: "Group results by field" }, select: { type: "string", description: "Fields to return" }, sample: { type: "number", description: "Random sample size" }, seed: { type: "number", description: "Random seed" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } } } }, { name: "get_entity", description: "Get a single entity by its OpenAlex ID", inputSchema: { type: "object", properties: { entity_type: { type: "string", enum: ["works", "authors", "sources", "institutions", "topics", "publishers", "funders"], description: "Type of entity to retrieve" }, openalex_id: { type: "string", description: "OpenAlex ID (e.g., W2741809807, A1969205038)" }, select: { type: "string", description: "Comma-separated list of fields to return" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } }, required: ["entity_type", "openalex_id"] } }, { name: "autocomplete", description: "Type ahead search across any OpenAlex entity type", inputSchema: { type: "object", properties: { search: { type: "string", description: "Search query for autocomplete", required: true }, type: { type: "string", enum: ["works", "authors", "sources", "institutions", "concepts", "publishers", "funders"], description: "Entity type to search within" }, per_page: { type: "number", description: "Number of suggestions (max 50)" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } }, required: ["search"] } }, { name: "classify_text", description: "Classify arbitrary text to predict research concepts and confidence scores", inputSchema: { type: "object", properties: { title: { type: "string", description: "Title text to classify" }, abstract: { type: "string", description: "Abstract text to classify" }, mailto: { type: "string", description: "Email for rate limits" }, api_key: { type: "string", description: "Premium API key" } } } }, { name: "get_filterable_fields", description: "Get a list of filterable field names and their types for a specified OpenAlex entity.", inputSchema: { type: "object", properties: { entity_type: { type: "string", enum: ["works", "authors", "sources", "institutions", "topics", "publishers", "funders"], description: "The type of OpenAlex entity for which to retrieve filterable fields." } }, required: ["entity_type"] } } ] }; }); /** * Handler for tool execution */ (server as any).setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { const { name, arguments: args } = request.params; try { switch (name) { case "search_works": return await searchWorks(args); case "search_authors": return await searchAuthors(args); case "search_sources": return await searchSources(args); case "search_institutions": return await searchInstitutions(args); case "search_topics": return await searchTopics(args); case "search_publishers": return await searchPublishers(args); case "search_funders": return await searchFunders(args); case "get_entity": return await getEntity(args); case "autocomplete": return await autocomplete(args); case "classify_text": return await classifyText(args); case "get_filterable_fields": return await getFilterableFields(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }); /** * Start the server using stdio transport */ async function main() { const transport = new StdioServerTransport(); await (server as any).connect(transport); } main().catch((error) => { console.error("Server error:", error); process.exit(1); });

Implementation Reference

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/reetp14/openalex-mcp'

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