search_semantic_scholar
Search academic papers on Semantic Scholar with citation data, filters for year and fields of study, and customizable result limits.
Instructions
Search Semantic Scholar for academic papers with citation data
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query string | |
| maxResults | No | Maximum number of results to return | |
| year | No | Year filter (e.g., "2023", "2020-2023") | |
| fieldsOfStudy | No | Fields of study filter (e.g., ["Computer Science", "Biology"]) |
Implementation Reference
- Core handler function that performs the Semantic Scholar API search, handles rate limiting, parameters, and parses results into Paper objects.async search(query: string, options: SemanticSearchOptions = {}): Promise<Paper[]> { await this.rateLimiter.waitForPermission(); try { const params: Record<string, any> = { query: query, limit: Math.min(options.maxResults || 10, 100), // API限制最大100 fields: [ 'paperId', 'title', 'abstract', 'venue', 'year', 'referenceCount', 'citationCount', 'influentialCitationCount', 'isOpenAccess', 'openAccessPdf', 'fieldsOfStudy', 's2FieldsOfStudy', 'publicationTypes', 'publicationDate', 'journal', 'authors', 'externalIds', 'url' ].join(',') }; // 添加年份过滤 if (options.year) { params.year = options.year; } // 添加研究领域过滤 if (options.fieldsOfStudy && options.fieldsOfStudy.length > 0) { params.fieldsOfStudy = options.fieldsOfStudy.join(','); } const url = `${this.baseApiUrl}/paper/search`; const headers: Record<string, string> = { 'User-Agent': USER_AGENT, 'Accept': 'application/json', 'Accept-Language': 'en-US,en;q=0.9' }; // 添加API密钥(如果有)- 根据官方文档推荐的方式 if (this.apiKey) { headers['x-api-key'] = this.apiKey; } logDebug(`Semantic Scholar API Request: GET ${url}`); logDebug('Semantic Scholar Request params:', params); const response = await axios.get(url, { params, headers, timeout: TIMEOUTS.DEFAULT, // 改善请求可靠性 maxRedirects: 5, validateStatus: (status) => status < 500 // allow 4xx through so we can provide consistent messaging }); logDebug(`Semantic Scholar API Response: ${response.status} ${response.statusText}`); // 处理可能的错误响应 if (response.status >= 400) { // Convert non-throwing 4xx response to unified error handling this.handleHttpError({ response, config: response.config }, 'search'); } const papers = this.parseSearchResponse(response.data); logDebug(`Semantic Scholar Parsed ${papers.length} papers`); return papers; } catch (error: any) { logDebug('Semantic Scholar Search Error:', error.message); // 处理速率限制错误 if (error.response?.status === 429) { const retryAfter = error.response.headers['retry-after']; logDebug( `Rate limited by Semantic Scholar API. ${retryAfter ? `Retry after ${retryAfter} seconds.` : 'Please wait before making more requests.'}` ); } // 处理API限制错误 if (error.response?.status === 403) { logDebug('Access denied. Please check your API key or ensure you are within the free tier limits.'); } this.handleHttpError(error, 'search'); } }
- src/mcp/tools.ts:205-227 (registration)Tool registration in the exported TOOLS array used by MCP server.{ name: 'search_semantic_scholar', description: 'Search Semantic Scholar for academic papers with citation data', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query string' }, maxResults: { type: 'number', minimum: 1, maximum: 100, description: 'Maximum number of results to return' }, year: { type: 'string', description: 'Year filter (e.g., "2023", "2020-2023")' }, fieldsOfStudy: { type: 'array', items: { type: 'string' }, description: 'Fields of study filter (e.g., ["Computer Science", "Biology"])' } }, required: ['query'] } },
- src/mcp/schemas.ts:92-99 (schema)Zod input schema for validating tool arguments.export const SearchSemanticScholarSchema = z .object({ query: z.string().min(1), maxResults: z.number().int().min(1).max(100).optional().default(10), year: z.string().optional(), fieldsOfStudy: z.array(z.string()).optional() }) .strip();
- src/mcp/handleToolCall.ts:187-208 (handler)Main tool handler case that delegates to SemanticScholarSearcher and formats response.case 'search_semantic_scholar': { const { query, maxResults, year, fieldsOfStudy } = args; const results = await searchers.semantic.search(query, { maxResults, year, fieldsOfStudy }); const rateStatus = searchers.semantic.getRateLimiterStatus(); const apiKeyStatus = searchers.semantic.hasApiKey() ? 'configured' : 'not configured (using free tier)'; const rateLimit = searchers.semantic.hasApiKey() ? '200 requests/minute' : '20 requests/minute'; return jsonTextResponse( `Found ${results.length} Semantic Scholar papers.\n\nAPI Status: ${apiKeyStatus} (${rateLimit})\nRate Limiter: ${rateStatus.availableTokens}/${rateStatus.maxTokens} tokens available\n\n${JSON.stringify( results.map((paper: Paper) => PaperFactory.toDict(paper)), null, 2 )}` ); }
- src/mcp/searchers.ts:47-67 (helper)Initialization of the SemanticScholarSearcher instance provided to handlers.const semanticSearcher = new SemanticScholarSearcher(process.env.SEMANTIC_SCHOLAR_API_KEY); const iacrSearcher = new IACRSearcher(); const googleScholarSearcher = new GoogleScholarSearcher(); const sciHubSearcher = new SciHubSearcher(); const scienceDirectSearcher = new ScienceDirectSearcher(process.env.ELSEVIER_API_KEY); const springerSearcher = new SpringerSearcher( process.env.SPRINGER_API_KEY, process.env.SPRINGER_OPENACCESS_API_KEY ); const wileySearcher = new WileySearcher(process.env.WILEY_TDM_TOKEN); const scopusSearcher = new ScopusSearcher(process.env.ELSEVIER_API_KEY); const crossrefSearcher = new CrossrefSearcher(process.env.CROSSREF_MAILTO); searchers = { arxiv: arxivSearcher, webofscience: wosSearcher, pubmed: pubmedSearcher, wos: wosSearcher, biorxiv: biorxivSearcher, medrxiv: medrxivSearcher, semantic: semanticSearcher,