Skip to main content
Glama
ElliotPadfield

Unpaywall MCP Server

unpaywall_search_titles

Search academic article titles in Unpaywall to find research papers. Filter by open access status and browse results with pagination.

Instructions

Search Unpaywall for article titles matching a query. Supports optional is_oa filter and pagination (50 results per page).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesTitle search query (supports phrase, boolean operators per Unpaywall docs)
is_oaNoIf true, only return OA results; if false, only closed; omit for all
pageNoPage number (50 results per page)
emailNoEmail to identify your requests to Unpaywall (optional override)

Implementation Reference

  • The handler logic for the 'unpaywall_search_titles' tool within the CallToolRequestSchema handler. Validates arguments, handles errors, prepares parameters, calls the search helper function, and returns the JSON result.
    if (tool === TOOL_SEARCH_TITLES) {
      const args = (req.params.arguments ?? {}) as Partial<SearchTitlesArgs>;
      const query = (args.query ?? "").toString().trim();
      if (!query) {
        return { content: [{ type: "text", text: "Missing required argument: 'query'" }], isError: true };
      }
      const email = (args.email || process.env.UNPAYWALL_EMAIL || "").toString().trim();
      if (!email) {
        return { content: [{ type: "text", text: "Unpaywall requires an email. Set UNPAYWALL_EMAIL or pass 'email'." }], isError: true };
      }
      const page = args.page && Number.isFinite(args.page) ? Math.max(1, Math.floor(Number(args.page))) : undefined;
      const is_oa = typeof args.is_oa === "boolean" ? args.is_oa : undefined;
      const data = await searchUnpaywallTitles({ query, email, is_oa, page });
      return { content: [{ type: "json", json: data }] };
    }
  • Helper function that performs the actual Unpaywall API search for titles, constructs the query URL, fetches the data with timeout and error handling.
    async function searchUnpaywallTitles(args: { query: string; email: string; is_oa?: boolean; page?: number }) {
      const { query, email, is_oa, page } = args;
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 20_000);
      try {
        const params = new URLSearchParams();
        params.set("query", query);
        if (typeof is_oa === "boolean") params.set("is_oa", String(is_oa));
        if (page && Number.isFinite(page) && page > 1) params.set("page", String(Math.floor(page)));
        params.set("email", email);
        const url = `https://api.unpaywall.org/v2/search?${params.toString()}`;
        const resp = await fetch(url, { signal: controller.signal, headers: { "Accept": "application/json" } });
        if (!resp.ok) {
          const text = await resp.text().catch(() => "");
          throw new Error(`Unpaywall search HTTP ${resp.status}: ${text.slice(0, 400)}`);
        }
        return await resp.json();
      } finally {
        clearTimeout(timeout);
      }
    }
  • src/index.ts:150-164 (registration)
    Tool registration in the ListToolsRequestSchema handler, defining name, description, and input schema for 'unpaywall_search_titles'.
    {
      name: TOOL_SEARCH_TITLES,
      description: "Search Unpaywall for article titles matching a query. Supports optional is_oa filter and pagination (50 results per page).",
      inputSchema: {
        type: "object",
        properties: {
          query: { type: "string", description: "Title search query (supports phrase, boolean operators per Unpaywall docs)" },
          is_oa: { type: "boolean", description: "If true, only return OA results; if false, only closed; omit for all" },
          page: { type: "integer", minimum: 1, description: "Page number (50 results per page)" },
          email: { type: "string", description: "Email to identify your requests to Unpaywall (optional override)" },
        },
        required: ["query"],
        additionalProperties: false,
      },
    },
  • TypeScript type definition for the input arguments of the unpaywall_search_titles tool.
    type SearchTitlesArgs = {
      query: string;
      is_oa?: boolean;
      page?: number; // 1-based page index per Unpaywall docs (50 results per page)
      email?: string; // optional override
    };
Behavior3/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions 'Supports optional is_oa filter and pagination (50 results per page)', which provides useful operational context about filtering capabilities and pagination behavior. However, it doesn't address important behavioral aspects like rate limits, authentication requirements (beyond the optional email parameter), error conditions, or what the search results actually contain.

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 extremely concise (two sentences) and front-loaded with the core purpose. Every sentence earns its place: the first establishes what the tool does, and the second provides key operational context about filters and pagination. There's zero waste or 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 4 parameters, 100% schema coverage, and no output schema, the description provides adequate but minimal context. It covers the basic purpose and mentions key operational features (filtering, pagination), but doesn't explain what the search results contain, how comprehensive the search is, or any limitations. Given the lack of annotations and output schema, more completeness would be helpful for an AI agent.

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?

With 100% schema description coverage, the input schema already fully documents all 4 parameters. The description adds minimal value beyond what's in the schema - it mentions the optional is_oa filter and pagination (50 results per page), but these are already clearly documented in the parameter descriptions. The baseline score of 3 is appropriate when the schema does the heavy lifting.

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

Purpose4/5

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

The description clearly states the action ('Search Unpaywall for article titles matching a query') and specifies the resource ('article titles'), making the purpose immediately understandable. However, it doesn't explicitly differentiate this tool from its siblings (unpaywall_fetch_pdf_text, unpaywall_get_by_doi, unpaywall_get_fulltext_links), which appear to have different functions but could potentially overlap in some search contexts.

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

Usage Guidelines3/5

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

The description implies usage context through 'Search Unpaywall for article titles matching a query' and mentions optional filters/pagination, but doesn't explicitly state when to use this tool versus alternatives like unpaywall_get_by_doi (for DOI-based lookups) or unpaywall_get_fulltext_links (for link retrieval). No explicit when-not-to-use guidance or prerequisite information is provided.

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/ElliotPadfield/unpaywall-mcp'

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