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;
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It discloses that the same API key works for both API modes, which is useful behavioral context about authentication. However, it doesn't mention rate limits, pagination behavior, error handling, or what the response format looks like, leaving significant gaps for a search tool with 8 parameters.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured in two sentences with zero waste. The first sentence states the core purpose, and the second provides important behavioral context about API modes and authentication. Every sentence earns its place without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with 8 parameters, no annotations, and no output schema, the description provides adequate basic information about purpose and API behavior. However, it lacks details about response format, error conditions, or performance characteristics that would be helpful given the tool's complexity and lack of structured metadata.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 8 parameters thoroughly. The description adds minimal value by mentioning the openAccess parameter's effect on API selection, but doesn't provide additional semantic context beyond what's in the schema descriptions. Baseline 3 is appropriate when schema does heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches academic papers from Springer Nature database, specifying both the action (search) and resource (academic papers). It distinguishes itself from siblings by mentioning the Springer Nature database specifically, unlike other search tools like search_arxiv or search_pubmed that target different sources.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context about when to use different API modes (Metadata API vs OpenAccess API based on openAccess parameter), which helps guide usage. However, it doesn't explicitly state when to choose this tool over sibling search tools like search_google_scholar or search_scopus, missing explicit alternatives guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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