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
    };

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