Skip to main content
Glama
Dianel555

Paper Search MCP

by Dianel555

search_springer

Search academic papers from Springer Nature database using metadata or open access filters to find relevant research publications.

Instructions

Search academic papers from Springer Nature database. Uses Metadata API by default (all content) or OpenAccess API when openAccess=true (full text available). Same API key works for both.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query string
maxResultsNoMaximum number of results to return
yearNoYear filter (e.g., "2023", "2020-2023")
authorNoAuthor name filter
journalNoJournal name filter
subjectNoSubject area filter
openAccessNoSearch only open access content
typeNoPublication type filter

Implementation Reference

  • MCP tool handler case for 'search_springer'. Validates API key, calls SpringerSearcher.search with parameters, formats and returns JSON response with paper results.
    case 'search_springer': {
      const { query, maxResults, year, author, journal, subject, openAccess, type } = args;
      if (!process.env.SPRINGER_API_KEY) {
        throw new Error('Springer API key not configured. Please set SPRINGER_API_KEY environment variable.');
      }
    
      const results = await searchers.springer.search(query, {
        maxResults,
        year,
        author,
        journal,
        subject,
        openAccess,
        type
      } as any);
    
      return jsonTextResponse(
        `Found ${results.length} Springer papers.\n\n${JSON.stringify(
          results.map((paper: Paper) => PaperFactory.toDict(paper)),
          null,
          2
        )}`
      );
    }
  • Zod input schema validation for search_springer tool parameters including query, filters, and pagination.
    export const SearchSpringerSchema = z
      .object({
        query: z.string().min(1),
        maxResults: z.number().int().min(1).max(100).optional().default(10),
        year: z.string().optional(),
        author: z.string().optional(),
        journal: z.string().optional(),
        subject: z.string().optional(),
        openAccess: z.boolean().optional(),
        type: z.enum(['Journal', 'Book', 'Chapter']).optional()
      })
      .strip();
  • MCP tool registration entry for 'search_springer' including name, description, and input schema definition.
    {
      name: 'search_springer',
      description:
        'Search academic papers from Springer Nature database. Uses Metadata API by default (all content) or OpenAccess API when openAccess=true (full text available). Same API key works for both.',
      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")' },
          author: { type: 'string', description: 'Author name filter' },
          journal: { type: 'string', description: 'Journal name filter' },
          subject: { type: 'string', description: 'Subject area filter' },
          openAccess: {
            type: 'boolean',
            description: 'Search only open access content'
          },
          type: {
            type: 'string',
            enum: ['Journal', 'Book', 'Chapter'],
            description: 'Publication type filter'
          }
        },
        required: ['query']
      }
    },
  • Core search implementation in SpringerSearcher class. Handles API selection (Meta v2 or OpenAccess), builds query with filters, calls Springer API, parses responses, and returns Paper objects. Includes rate limiting, error handling, and fallback logic.
    async search(query: string, options: SearchOptions = {}): Promise<Paper[]> {
      const customOptions = options as any;
      if (!this.apiKey) {
        throw new Error('Springer API key is required');
      }
    
      const maxResults = Math.min(options.maxResults || 10, 100);
      const papers: Paper[] = [];
    
      try {
        // Decide which API to use
        let useOpenAccess = customOptions.openAccess === true;
        
        // If openAccess is requested and we haven't tested the API yet, test it
        if (useOpenAccess && this.hasOpenAccessAPI === undefined) {
          await this.testOpenAccessAPI();
        }
        
        // Fall back to Meta API if OpenAccess API is not available
        if (useOpenAccess && !this.hasOpenAccessAPI) {
          logDebug('OpenAccess API not available, using Meta API with filtering');
          useOpenAccess = false;
        }
        
        // Build query parameters
        const params: any = {
          q: query,
          api_key: useOpenAccess ? this.openAccessApiKey : this.apiKey,
          s: 1, // start index
          p: maxResults // page size
        };
    
        // Add filters - Note: Some filters may require premium access
        if (options.author) {
          const sanitizedAuthor = this.sanitizeQueryValue(options.author);
          params.q += ` name:"${sanitizedAuthor}"`;
        }
    
        if (options.journal) {
          const sanitizedJournal = this.sanitizeQueryValue(options.journal);
          params.q += ` pub:"${sanitizedJournal}"`;
        }
    
        if (options.year) {
          // Year filter may cause 403 for some API keys
          if (options.year.includes('-')) {
            const [startYear, endYear] = options.year.split('-');
            params.q += ` year:${startYear} TO ${endYear || '*'}`;
          } else {
            params.q += ` year:${options.year}`;
          }
        }
    
        if (customOptions.subject) {
          // Subject filter may cause 403 for some API keys
          const sanitizedSubject = this.sanitizeQueryValue(customOptions.subject);
          params.q += ` subject:"${sanitizedSubject}"`;
        }
    
        if (customOptions.type) {
          // Type filter generally works
          params.q += ` type:${customOptions.type}`;
        }
    
        await this.rateLimiter.waitForPermission();
    
        // Choose the appropriate API
        let response: any;
        if (useOpenAccess) {
          // Use OpenAccess API (if available)
          response = await this.openAccessClient.get<SpringerResponse>('/json', { params });
        } else {
          // Use Meta v2 API
          response = await this.metadataClient.get<SpringerResponse>('/json', { params });
        }
    
        // Handle different response structures
        // Meta v2 API: records contains the actual papers, result contains metadata
        // OpenAccess API: might use either records or result for the actual papers
        let results: SpringerResult[] = [];
        
        // For Meta v2 API, records is always the array of papers
        if (response.data.records && Array.isArray(response.data.records)) {
          results = response.data.records;
        } 
        // For older API versions or different response format
        else if (response.data.result && Array.isArray(response.data.result) && 
                 response.data.result.length > 0 && 
                 response.data.result[0].title) {
          // If result contains actual papers (has title field), use it
          results = response.data.result as SpringerResult[];
        }
        
        if (results && results.length > 0) {
          for (const result of results) {
            const paper = this.parseResult(result);
            if (paper) {
              // If openAccess filter was requested but using Meta API, filter results
              if (customOptions.openAccess && !useOpenAccess && result.openaccess !== 'true') {
                continue;
              }
              papers.push(paper);
            }
          }
        }
    
        return papers;
      } catch (error: any) {
        if (error.response?.status === 401) {
          this.handleHttpError(error, 'search');
        }
        if (error.response?.status === 403) {
          // Some filters require premium access
          logWarn('Springer API returned 403 - some filters may require premium access');
          // Try a simpler query without advanced filters
          if (options.year || customOptions.subject) {
            logDebug('Retrying without year/subject filters...');
            const simpleOptions = { ...options };
            delete simpleOptions.year;
            delete (simpleOptions as any).subject;
            return this.search(query, simpleOptions);
          }
          this.handleHttpError(error, 'search');
        }
        if (error.response?.status === 429) {
          this.handleHttpError(error, 'search');
        }
        this.handleHttpError(error, 'search');
      }
    }
  • Initializes all searchers including SpringerSearcher instance using environment variables, making it available as searchers.springer for tool handlers.
    export function initializeSearchers(): Searchers {
      if (searchers) return searchers;
    
      logDebug('Initializing searchers...');
    
      const arxivSearcher = new ArxivSearcher();
      const wosSearcher = new WebOfScienceSearcher(process.env.WOS_API_KEY, process.env.WOS_API_VERSION);
      const pubmedSearcher = new PubMedSearcher(process.env.PUBMED_API_KEY);
      const biorxivSearcher = new BioRxivSearcher('biorxiv');
      const medrxivSearcher = new MedRxivSearcher();
      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,
        iacr: iacrSearcher,
        googlescholar: googleScholarSearcher,
        scholar: googleScholarSearcher,
        scihub: sciHubSearcher,
        sciencedirect: scienceDirectSearcher,
        springer: springerSearcher,
        wiley: wileySearcher,
        scopus: scopusSearcher,
        crossref: crossrefSearcher
      };
    
      logDebug('Searchers initialized successfully');
      return searchers;
    }

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/Dianel555/paper-search-mcp-nodejs'

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