Skip to content

Tools

All research tools are prefixed research_ and grouped by provider (research_arxiv_*, research_europepmc_*), with identifier resolution exposed as a single grouping-level tool, research_resolve_identifier, that isn't tied to one provider. See the Software Requirements Specification — specifically Architecture and Interface specification — for the full design rationale and exact wire-level schemas; this page is a practical, per-tool reference.

Errors

A tool failure surfaces as an MCP ToolError — a plain error response carrying a human-readable message (Error calling tool '<name>': <detail>), not a structured field a caller can branch on programmatically; there is no {"error": "<code>", ...} envelope. The categories below name which condition produced a given failure, for reference — they aren't literal response fields. See Interface specification → Conventions for the exception type each one maps to.

Category Meaning
not_found Identifier not recognised by the provider, or the requested format hasn't been fetched yet.
format_unavailable The identifier is valid, but doesn't offer the requested format.
unsupported_provider A DOI resolved to a domain outside the v1 provider allowlist (arXiv, Europe PMC).
invalid_request Caller-supplied arguments fail validation before any outbound call or storage read (e.g. an arXiv search exceeding arXiv's own result-count bounds, an unsupported format passed to research_resolve_identifier, page passed to research_arxiv_parse_full_text with format="html", identifier passed to research_search_fetched without provider, or malformed FTS5 query syntax in research_search_fetched/research_notes_search). Also covers research_notes_search's other structured-filter validation — canonical_identifier without provider, author_filter="named" without author_name (or vice versa), and an unparseable date_from/date_to.
rate_limited The provider's outbound queue exhausted its backoff budget after repeated 429s from the source. Discovery has no such queue — a 429 from OpenAlex surfaces immediately instead of being retried.
provider_unavailable A timeout, connection failure, or 5xx from the source — surfaced immediately, never retried.
file_too_large research_localfile_fetch_full_text's decoded content exceeds PRIORIS_MCP_LOCAL_FILE_MAX_SIZE_BYTES. Also covers the chunked-upload path: a single chunk over PRIORIS_MCP_LOCAL_FILE_UPLOAD_MAX_CHUNK_BYTES in research_localfile_upload_chunk, or a reassembled total over PRIORIS_MCP_LOCAL_FILE_MAX_SIZE_BYTES in research_localfile_upload_chunk/research_localfile_finalize_upload.
configuration_error A required server-side configuration value is missing, checked before any outbound call — currently research_discovery/research://openalex/work-types when PRIORIS_MCP_OPENALEX_API_KEY isn't set (OpenAlex has required a key on every request since 2026-02-13).

arXiv tools

Tool Description Key inputs Notes
research_arxiv_search Search arXiv by keyword/query. query, max_results (default 10), start (default 0), sort_by, sort_order Validates max_results (≤2000) and start + max_results (≤30000) before calling arXiv.
research_arxiv_list_top_n List the N most recently submitted items matching one or more arXiv subject categories. include_categories (list, e.g. ["cs.CL"]), n, exclude_categories (list, optional) "Top N" means most recent, not most cited/viewed. include_categories is AND-combined, exclude_categories is ANDNOT-combined; both are deduplicated. Empty include_categories fails with invalid_request.
research_arxiv_fetch_metadata Fetch metadata for one or more arXiv identifiers in a single call. arxiv_ids (list) Unrecognised IDs are reported in not_found, not a failure.
research_arxiv_fetch_full_text Fetch (or return the already-persisted) full text for an arXiv item. arxiv_id, format (pdf|html) Unversioned IDs resolve to the current version first. html isn't available for every paper (arXiv's HTML rendering is a comparatively recent rollout).
research_arxiv_parse_full_text Convert already-fetched arXiv full text into one page of Markdown. arxiv_id, format, offset (default 0), limit (default PRIORIS_MCP_MAX_INLINE_CHARS), page (optional, 1-indexed, pdf only) Never triggers a fetch itself — fetch first, or this fails with not_found. Returns offset/limit/total_length/has_more alongside markdown so a caller can page through content longer than the limit; see Caching and rate limiting below. When page is given, offset becomes relative to that page's start rather than to the whole document. total_pages/page_range are always present in the response but null for format="html", which has no page concept. Passing page with format="html" fails with invalid_request.

All arXiv tools share a single outbound request queue, serialised to arXiv's documented limit of one request per 3 seconds.

Europe PMC tools

Tool Description Key inputs Notes
research_europepmc_search Search Europe PMC by keyword/query. query, page_size (default 25), cursor_mark (default *) Paginate by passing the previous response's next_cursor_mark back in as cursor_mark.
research_europepmc_fetch_metadata Fetch metadata for one or more Europe PMC identifiers in a single call. identifiers (list — bare PMCID or {source}:{id}) Unrecognised identifiers are reported in not_found, not a failure.
research_europepmc_fetch_full_text Fetch (or return the already-persisted) JATS XML full text for a Europe PMC item. identifier No format parameter — Europe PMC's only directly-servable full-text format is XML. Fails with format_unavailable if Europe PMC doesn't host full text for that item itself.
research_europepmc_parse_full_text Convert already-fetched Europe PMC XML full text into one page of Markdown. identifier, offset (default 0), limit (default PRIORIS_MCP_MAX_INLINE_CHARS) Never triggers a fetch itself — fetch first, or this fails with not_found. Returns offset/limit/total_length/has_more alongside markdown, same as arXiv's.

There is no research_europepmc_list_top_n — Europe PMC has no single classification field equivalent to arXiv's subject categories.

Europe PMC publishes no numeric rate limit; the provider self-imposes the same one-request-per-3-seconds policy as arXiv, through its own separate queue.

Discovery tool

v3 — see Discovery. Not a per-provider tool: research_discovery sits in front of OpenAlex's /works search.semantic parameter, ranking external, not-yet-fetched candidates by embedding similarity rather than keyword overlap.

Tool Description Key inputs Notes
research_discovery Discover external research candidates not already in the local corpus, ranked by OpenAlex's embedding-based search.semantic. query (free text, ≤2000 chars), max_results (1-50, default PRIORIS_MCP_DISCOVERY_MAX_RESULTS), page (default 1, 1-indexed), from_year/to_year (optional, inclusive publication_year bounds), open_access_only (default false) total is capped at 50 — search.semantic's own hard per-query ceiling, not a PriorisMCP-imposed limit. page/per_page/total/has_more reflect OpenAlex's own pre-exclusion counts; a returned page can carry fewer hits than requested once candidates already present in local storage are filtered out of the response. Each hit includes a fetch_route (known_provider|oa_link|manual_upload) describing how to actually retrieve full text for it — see Fetch ladder.

Authenticated via PRIORIS_MCP_OPENALEX_API_KEY, sent as OpenAlex's own api_key query parameter, not the deprecated mailto polite-pool parameter. OpenAlex has required this key on every request since 2026-02-13; if it isn't configured, the tool fails with configuration_error rather than making an unauthenticated call. See research://openalex/work-types in Resources for the reference vocabulary behind a hit's own work_type field — it isn't a filter research_discovery itself accepts.

Identifier resolution

Tool Description Key inputs Notes
research_resolve_identifier Resolve an identifier of unknown provider — an arXiv ID, a Europe PMC identifier, or a DOI — to its owning provider, canonical identifier, and a fetchable URL. identifier, format Self-identifying schemes (arXiv IDs, Europe PMC identifiers) route directly, with no network round-trip. A DOI resolves via doi.org/Crossref first; if the redirect lands outside the arXiv/Europe PMC domain allowlist, this fails with unsupported_provider rather than following it.

This is the one capability exposed at the grouping level rather than per-provider — see Architecture → Identifier routing.

Local filesystem tools

Tool Description Key inputs Notes
research_localfile_fetch_full_text Validate and persist caller-sent PDF bytes, in a single call. content_base64, filename (optional) Rejects invalid base64, or a payload whose (encoded or decoded) size exceeds PRIORIS_MCP_LOCAL_FILE_MAX_SIZE_BYTES, with file_too_large/invalid_request. Validates content as a PDF by its magic bytes, not filename's extension. Re-fetching unchanged content reuses the same server-assigned id; changed content gets a new one. Deprecation candidate now that the chunked-upload flow below exists for files at risk of hitting transport/relay size ceilings — kept unchanged as the small-file path for now, no forced migration.
research_localfile_parse_full_text Convert an already-fetched local PDF's full text into one page of Markdown. id, offset (default 0), limit (default PRIORIS_MCP_MAX_INLINE_CHARS), page (optional, 1-indexed) id is the caller-facing identifier research_localfile_fetch_full_text/research_localfile_finalize_upload returned. Never triggers a fetch — fails with not_found if id isn't recognised. page is always valid here (this source is PDF-only), unlike arXiv's format-gated version — response includes total_pages/page_range alongside markdown/offset/limit/total_length/has_more.
research_localfile_begin_upload Start a new chunked upload session for a large local PDF. filename (optional) Returns session_id and max_chunk_bytes. Fails invalid_request at PRIORIS_MCP_LOCAL_FILE_UPLOAD_MAX_CONCURRENT_SESSIONS open sessions.
research_localfile_upload_chunk Upload one chunk to an in-progress upload session. session_id, index, chunk_base64 Chunks must arrive in strict sequential order (no total_chunks argument). Fails not_found on an unknown/expired session, invalid_request on an out-of-order index, file_too_large on an oversized chunk or cumulative total.
research_localfile_finalize_upload Reassemble a session's chunks and persist them, same validation as research_localfile_fetch_full_text. session_id Same output shape as research_localfile_fetch_full_text. Removes the session whether it succeeds or fails.

Deliberately narrower than the arXiv/Europe PMC tool sets — no search, listing, metadata, or identifier resolution for this source (see Architecture → Local filesystem source). None of the five tools is subject to rate limiting — there's no outbound network request to throttle.

Storage management tools

Tool Description Key inputs Notes
research_list_fetched Enumerate persisted (provider, identifier, format, artefact) catalogue entries, read from catalogue.sqlite. provider (optional), format (optional), offset (default 0), limit (default 50) Never triggers a fetch or parse. Omitting both filters lists every persisted entry across all three sources. artefact is document or markdown — the fetched source and its parsed Markdown are listed as separate entries, each with its own fetched_at_or_parsed_at/size_bytes. Returns offset/limit/total/has_more alongside entries for paging (total is the full unfiltered-by-page count matching provider/format; has_more = offset + len(entries) < total).
research_delete_fetched Remove one or more persisted artefacts. entries (list of {provider, identifier, format, artefact}, artefact is document, markdown, or all) Tolerates entries no longer present — reports them in not_found rather than failing the whole call. Does not cascade between artefacts: deleting document leaves markdown in place and vice versa; artefact="all" removes both (plus the format directory and, if it was the last format, the document-hash directory itself). Deleting markdown or all also removes that document from the search index. identifier must match the canonical form actually in storage — use research_list_fetched first if unsure. Never touches a local filesystem source's original file.
research_search_fetched Search previously-persisted chunks (or leaves, for documents with none) — full-text, semantic-vector, or both. query (FTS5 syntax for mode="fts"; free text otherwise), provider/identifier (optional, identifier requires provider)/format (optional), mode ("fts" (default) | "vector" | "hybrid", or any other registered mechanism name), offset (default 0, per mechanism), limit (default PRIORIS_MCP_VECTOR_SEARCH_DEFAULT_LIMIT, per mechanism) mode="fts" runs against the global SQLite FTS5 index (search.sqlite3), ranked by bm25() (most relevant first). mode="vector" runs a cosine-similarity KNN search over a separate embedding index instead — unthresholded, so it always returns up to limit nearest matches regardless of how dissimilar they are; its score is a cosine distance (0 = identical, larger = less similar — lower is better, the opposite of bm25()). mode="hybrid" runs both, returned as separate fts/vector result sets (each independently paged via its own total/has_more), not fused into one ranked list. Never triggers a fetch or parse — searching before anything is persisted just returns no results. Every response also carries index_status, mapping each mechanism name to "ready"/"stale"/"not_built"/(vector only) "building", populated only when provider/identifier/format are all given. identifier without provider fails with invalid_request, as does malformed FTS5 query syntax under mode="fts". Each FTS/vector match's offset is in the document's own coordinate space and can be passed straight to the matching parse_full_text tool's offset to fetch surrounding context.

Grouping-level, like research_resolve_identifier — not split per provider, since none of the three tools validates anything provider-specific (see Architecture → list_fetched/delete_fetched). See Vector search for the full semantic-search design and Interface specification → research_search_fetched for exact wire-level fields.

Vector index reconciliation

Corpus-wide vector reindexing after an embedding-model change (PRIORIS_MCP_EMBEDDING_MODEL) runs automatically at server startup — there is no tool to trigger it — see Vector search → Reconciliation runs automatically at server startup and ADR-00030. Its live progress is readable via the research://vector-index/rebuild-status resource: {"documents": {"total": <int>, "pending": <int>, "succeeded": <int>, "failed": <int>, "cancelled": <int>, "active": <bool>}, "notes": {...}}, where total/pending/succeeded/failed/cancelled count only the documents/notes the most recent reconciliation run decided needed rebuilding (a corpus already fully ready under the configured model reports all zeros; total == pending + succeeded + failed + cancelled always holds), and active (pending > 0) is a convenience field so a caller doesn't have to derive it. A nonzero failed with active=false is a terminal state — no in-process retry runs; the item stays failed until the next server restart re-derives and re-schedules it — see ADR-00031. This state is process-local and in-memory, not a durable job log — it resets to a fresh count on every server restart, the same "derived transiently, self-healing on restart" property index_status's own building value already has (see Vector search → Index status is per-document/note).

Notes tools

Tool Description Key inputs Notes
research_notes_create Create a new user-authored note against a document, or against a bare identifier. provider, identifier, format (optional — omit for a note predating any fetch), author_name (optional, null means self), metadata (optional, caller-owned opaque dict), anchors (list of unresolved positional hints), tags (list), text (free-form Markdown, the note itself) identifier is canonicalised before storing — resolved through the owning provider (arXiv/Europe PMC) when format is given, used as-is for localfile or when format is omitted.
research_notes_read Read a single note by id. note_id Fails with not_found if the id isn't recognised.
research_notes_update Partially edit an existing note. note_id, text, anchors, tags, metadata (all optional) Fields left as null/omitted are unchanged. Fails with not_found if the id isn't recognised.
research_notes_delete Delete a note by id. note_id Returns false, not an error, if the note is already absent.
research_notes_search Search/list notes; structured filters plus optional full-text or semantic-vector search over note text. provider, canonical_identifier, format, date_from/date_to (ISO 8601), keyword (FTS5 syntax for mode="fts"; free text otherwise), author_filter (any|mine|named), author_name, tags_all/tags_any/tags_exclude, mode ("fts" (default) | "vector" | "hybrid"), offset/limit (paged, newest first) No filters at all returns everything, paged. canonical_identifier requires provider; author_filter="named" requires author_name and vice versa; both fail with invalid_request. date_from/date_to are validated as ISO 8601 and normalised to UTC before comparison — an unparseable value fails with invalid_request. Malformed FTS5 syntax in keyword under mode="fts"/"hybrid" also fails with invalid_request, same as research_search_fetched. mode="vector" requires keyword (embedded as free text, not FTS5 syntax) and runs an unthresholded cosine-similarity KNN search scoped by any structural filters given; its matches carry a score — cosine distance, 0 = identical, lower is better — instead of FTS5 relevance, and no snippet/offset (only note_id/text_preview/score). mode="hybrid" returns both fts/vector result sets independently, each with its own paging. The response's index_status.vector (populated only when a vector search actually ran) is the worst-case status — priority order "building" > "not_built" > "stale" > "ready" — aggregated across every note id in the caller's structural-filter scope (the whole corpus when unfiltered), not just the notes in vector.matches this page; an empty scope reports "not_built".

Notes are persisted separately from fetched-document storage — one user-authored note against a document, or against a bare identifier predating any fetch — see Notes storage. A note's exportable file representation is available as a resource; see Resources.

Caching and rate limiting

research_*_search, research_*_list_top_n, research_*_fetch_metadata, and research_discovery responses are covered by the server's response-caching middleware (PRIORIS_MCP_RESPONSE_CACHE_TTL, see Configuration). fetch_full_text and parse_full_text are backed by persistent storage instead (see Resources) — a repeat call returns the already-persisted content (served_from_storage: true) without a second network fetch or parse.

Response shapes are still evolving alongside the SRS — prefer the Interface specification as the source of truth for exact wire-level fields.

research_localfile_fetch_full_text/research_localfile_parse_full_text, research_localfile_begin_upload/research_localfile_upload_chunk/research_localfile_finalize_upload, and research_list_fetched/research_delete_fetched/research_search_fetched are excluded from the response cache entirely — the local file source re-hashes file content on every call by design, the chunked-upload tools mutate in-memory session state that must never be served stale, and list/delete/search must reflect live storage state. The five research_notes_* tools are excluded for the same reason as list/delete/search: notes are mutable (create/update/delete), so a cached tool response could return stale or deleted data. The notes://{note_id}/export resource is likewise never served from the response cache, for the same reason — see Resources.