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
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Title search query (supports phrase, boolean operators per Unpaywall docs) | |
| is_oa | No | If true, only return OA results; if false, only closed; omit for all | |
| page | No | Page number (50 results per page) | |
| No | Email to identify your requests to Unpaywall (optional override) |
Implementation Reference
- src/index.ts:231-245 (handler)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 }] }; }
- src/index.ts:98-118 (helper)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, }, },
- src/index.ts:24-29 (schema)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 };