Skip to main content
Glama
cyanheads

pubmed-mcp-server

by cyanheads

Pubmed Fetch Articles

pubmed_fetch_articles
Read-only

Fetch full article metadata by PubMed ID, including abstract, authors, journal, and notices. Bound response size to defer excess articles.

Instructions

Fetch full article metadata by PubMed IDs. Returns detailed article information including abstract, authors, journal, MeSH terms, and linked retraction, erratum, and comment notices. Set maxResponseCharacters to bound the whole response: articles past the ceiling are deferred whole and listed in deferred.ids for a follow-up call.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pmidsYesPubMed IDs to fetch
includeMeshNoInclude MeSH terms
includeGrantsNoInclude grant information
maxResponseCharactersNoOpt-in ceiling for the whole response, in characters. Each article is measured as the JSON record it is returned as — title, abstract, authors, journal, MeSH terms, grants, identifiers, every field it carries. Articles are kept in response order until the next one would cross the ceiling; that article and the rest are deferred whole (never partially populated) and listed in `deferred.ids`. Response envelope fields — counts, `unavailablePmids`, `deferred` itself — are not counted. Omit to return every resolved article.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
errorNoPresent when the call failed. Absent on success.
noticeNoOptional guidance when no articles were returned — points to discovery tools — or when `maxResponseCharacters` deferred articles, naming how to retrieve them. Absent on successful unbudgeted fetches.
articlesNoParsed articles
deferredNoContinuation state for articles the whole-response budget withheld. Present only when `maxResponseCharacters` deferred at least one article.
truncatedNoTrue when `maxResponseCharacters` withheld at least one resolved article. Absent when the response carries every article that resolved. The continuation state is in `deferred`.
totalReturnedNoNumber of articles in this response. Under a `maxResponseCharacters` budget this counts the kept articles only; `deferred.deferredCount` covers the rest.
unavailablePmidsNoPMIDs PubMed returned no record for. That is all this reports: PubMed omits an unknown PMID silently, with no error and no reason, so the absence says nothing about whether the PMID exists. Reported in full regardless of where a `maxResponseCharacters` cutoff lands — these are misses, not deferrals. Use `pubmed_search_articles` to find PMIDs that do resolve.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema fields changedv2.10.19
    • addedOutput schema / properties / articles / items / properties / commentsCorrections
      Added value: +{
      +  "description": "Records NCBI links to this article — for example retraction notices, errata, expressions of concern, comments, and updates — from `CommentsCorrectionsList`, in NCBI's order and uncapped. `Cites` entries are excluded: they list a bibliography, which `pubmed_find_related` covers with its `references` relationship. Read this alongside `publicationTypes`, not in place of it: that field describes the record itself, and a corrected or questioned article often carries no matching type. Absent when NCBI links nothing other than `Cites` entries, and never set on `book-chapter` or `book` records.",
      +  "items": {
      +    "additionalProperties": false,
      +    "description": "One record NCBI links to this article and published separately from it — for example a retraction notice, erratum, expression of concern, comment, update, or republication.",
      +    "properties": {
      +      "note": {
      +        "description": "NCBI's note on the link, e.g. what an erratum corrected (\"Fuβer, Fabian [corrected to Fußer, Fabian]\"). Absent unless NCBI supplies one.",
      +        "type": "string"
      +      },
      +      "pmid": {
      +        "description": "PMID of the linked record — pass it to `pubmed_fetch_articles` to read that record. Absent when the linked record has no PMID, as with many errata.",
      +        "type": "string"
      +      },
      +      "refSource": {
      +        "description": "Citation of the linked record as NCBI writes it (e.g. \"Lancet. 2010 Feb 6;375(9713):445. doi: 10.1016/S0140-6736(10)60175-4.\").",
      +        "type": "string"
      +      },
      +      "refType": {
      +        "description": "Link type, verbatim from NCBI's `RefType` — e.g. \"RetractionIn\", \"RetractionOf\", \"ErratumIn\", \"ErratumFor\", \"ExpressionOfConcernIn\", \"CommentIn\", \"CommentOn\", \"UpdateIn\". The set is open: treat an unfamiliar value as opaque.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "refType",
      +      "refSource"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `queue_full`: The local NCBI request queue shed the call — the queue is full, or the call cannot start before its total deadline (for example behind the cooldown that follows an NCBI 429). `ncbi_unreachable`: NCBI E-utilities is unreachable after all retry attempts. `ncbi_deadline_exceeded`: Total request deadline expired before NCBI returned a response. `ncbi_invalid_response`: NCBI returned a body that could not be parsed (invalid XML/JSON). `ncbi_resource_not_found`: NCBI returned a structured \"not found\" error for the requested ID(s). `invalid_efetch_response`: NCBI EFetch returned a payload missing the PubmedArticleSet wrapper. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `queue_full`: The local NCBI request queue shed the call — the queue is full, or the call cannot start before its total deadline (for example behind the cooldown that follows an NCBI 429). `ncbi_unreachable`: NCBI E-utilities failed on every attempt the retry budget allowed — retries ran out, or the next backoff would overrun the total deadline. `ncbi_rate_limited`: NCBI answered HTTP 429 (too many requests) and the call stopped on it — retries ran out, the next backoff would overrun the total deadline, or the Retry-After NCBI named outlasts the time left or the 30-second backoff cap. `ncbi_deadline_exceeded`: The total NCBI request deadline expired before NCBI answered successfully — mid-request, while queued, or during a retry backoff. `ncbi_invalid_response`: NCBI returned a body that could not be parsed (invalid XML/JSON). `ncbi_resource_not_found`: NCBI returned a structured \"not found\" error for the requested ID(s). `invalid_efetch_response`: NCBI EFetch returned a payload missing the PubmedArticleSet wrapper. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "queue_full",
      -  "ncbi_unreachable",
      -  "ncbi_deadline_exceeded",
      -  "ncbi_invalid_response",
      -  "ncbi_resource_not_found",
      -  "invalid_efetch_response"
      -]New value: +[
      +  "queue_full",
      +  "ncbi_unreachable",
      +  "ncbi_rate_limited",
      +  "ncbi_deadline_exceeded",
      +  "ncbi_invalid_response",
      +  "ncbi_resource_not_found",
      +  "invalid_efetch_response"
      +]
  2. Changed1 schema field changedv2.10.18
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `queue_full`: Local NCBI request queue is at capacity. `ncbi_unreachable`: NCBI E-utilities is unreachable after all retry attempts. `ncbi_deadline_exceeded`: Total request deadline expired before NCBI returned a response. `ncbi_invalid_response`: NCBI returned a body that could not be parsed (invalid XML/JSON). `ncbi_resource_not_found`: NCBI returned a structured \"not found\" error for the requested ID(s). `invalid_efetch_response`: NCBI EFetch returned a payload missing the PubmedArticleSet wrapper. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `queue_full`: The local NCBI request queue shed the call — the queue is full, or the call cannot start before its total deadline (for example behind the cooldown that follows an NCBI 429). `ncbi_unreachable`: NCBI E-utilities is unreachable after all retry attempts. `ncbi_deadline_exceeded`: Total request deadline expired before NCBI returned a response. `ncbi_invalid_response`: NCBI returned a body that could not be parsed (invalid XML/JSON). `ncbi_resource_not_found`: NCBI returned a structured \"not found\" error for the requested ID(s). `invalid_efetch_response`: NCBI EFetch returned a payload missing the PubmedArticleSet wrapper. Other values are possible when a failure originates below the handler."
  3. Changed9 schema fields changedv2.10.12
    • changedOutput schema / properties / articles / items / properties / authors / description
      Previous value: -"Author list"New value: +"Author list. On a `book-chapter` these are the chapter's own authors, never the book's editors, which are in `book.editors`. Empty on a Bookshelf record that credits neither."
    • addedOutput schema / properties / articles / items / properties / book
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "The containing book of a `book-chapter`, or the book itself on a `book` record. Present only on those two record types, and never a stand-in for `journalInfo`.",
      +  "properties": {
      +    "accession": {
      +      "description": "NCBI Bookshelf accession from `ArticleIdList` (`bookaccession`), e.g. \"NBK1247\". The record is readable at `https://www.ncbi.nlm.nih.gov/books/<accession>/`.",
      +      "type": "string"
      +    },
      +    "beginningDate": {
      +      "description": "First year of a continuously-updated book, from `Book/BeginningDate` (GeneReviews runs from 1993). Absent on a book published once.",
      +      "type": "string"
      +    },
      +    "collectionTitle": {
      +      "description": "Series the book belongs to, from `Book/CollectionTitle` (e.g. \"ADA Clinical Compendia Series\"). Absent for a book outside a series.",
      +      "type": "string"
      +    },
      +    "doi": {
      +      "description": "The book's own DOI, from `Book/ELocationID` with `EIdType=\"doi\"`. Distinct from the record-level `doi`, which is the chapter's: a chapter does not inherit this one.",
      +      "type": "string"
      +    },
      +    "edition": {
      +      "description": "Edition statement from `Book/Edition`. Rare on Bookshelf titles — absent unless NCBI supplies one.",
      +      "type": "string"
      +    },
      +    "editors": {
      +      "description": "Editors of the containing book, from `Book/AuthorList` marked `Type=\"editors\"`. Kept out of `authors`, which carries the chapter's own writers. Absent when the book credits no editors.",
      +      "items": {
      +        "additionalProperties": false,
      +        "description": "One editor of the containing book. Name parts only — editors are a citation credit, not a contributor record, so no affiliations or ORCID are reported for them.",
      +        "properties": {
      +          "collectiveName": {
      +            "description": "Group or committee credited as editor, when the entry names an organization rather than a person. Mutually exclusive with the name-part fields.",
      +            "type": "string"
      +          },
      +          "firstName": {
      +            "description": "Editor given name as NCBI supplies it (`ForeName`, often \"Margaret P\"). Absent when NCBI carries initials only, or on a group editor.",
      +            "type": "string"
      +          },
      +          "initials": {
      +            "description": "Editor initials with no separators (e.g. \"MP\"). Absent when NCBI supplies none, or on a group editor.",
      +            "type": "string"
      +          },
      +          "lastName": {
      +            "description": "Editor surname, from the book's `Book/AuthorList Type=\"editors\"` entry. Absent on a group editor, which carries `collectiveName` instead.",
      +            "type": "string"
      +          }
      +        },
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "endingDate": {
      +      "description": "Last year of a closed date range, from `Book/EndingDate`. Absent while a book is still being updated, which leaves the range open-ended.",
      +      "type": "string"
      +    },
      +    "isbns": {
      +      "description": "Every `Book/Isbn` on the record. A book commonly carries a print and an electronic ISBN, so this is a list. Absent for a Bookshelf title with no ISBN, which is most of them.",
      +      "items": {
      +        "description": "One ISBN, verbatim as NCBI reports it — leading zeros intact",
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "medium": {
      +      "description": "Medium the book is published in, from `Book/Medium` — \"Internet\" wherever NCBI supplies it. Absent when NCBI supplies none; it is never defaulted.",
      +      "type": "string"
      +    },
      +    "pubDate": {
      +      "description": "Publication year from `Book/PubDate`. Year only — NCBI's month and day are not reported, since no citation style uses them for a book.",
      +      "type": "string"
      +    },
      +    "publisher": {
      +      "description": "Publisher of the book, from `Book/Publisher/PublisherName`.",
      +      "type": "string"
      +    },
      +    "publisherLocation": {
      +      "description": "Place of publication, from `Book/Publisher/PublisherLocation` (e.g. \"Seattle (WA)\"). Absent when NCBI supplies no place.",
      +      "type": "string"
      +    },
      +    "title": {
      +      "description": "Title of the containing book, from `Book/BookTitle` (e.g. \"GeneReviews®\"). On a `book` record this is the same value as the record's own `title`.",
      +      "type": "string"
      +    }
      +  },
      +  "type": "object"
      +}
    • changedOutput schema / properties / articles / items / properties / journalInfo / description
      Previous value: -"Journal information"New value: +"Journal information. Present on `journal-article` records only — absent on `book-chapter` and `book` records, because a Bookshelf record has no journal and its book title is never reported as one; read `book` for those. (#114)"
    • addedOutput schema / properties / articles / items / properties / journalInfo / properties / elocationId
      Added value: +{
      +  "description": "Electronic article locator from NCBI `ELocationID` — the publisher-assigned article number (e.g. \"2400512\"). Journals that assign article numbers instead of pages often omit pagination entirely, leaving this the only locator. Never a substitute for `pages`, and never the DOI: a DOI-typed `ELocationID` is reported in `doi` instead. Absent when the only locator NCBI supplies is marked invalid.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / articles / items / properties / journalInfo / properties / elocationIdType
      Added value: +{
      +  "description": "Type of `elocationId`, from NCBI's `EIdType` attribute — \"pii\" in practice. Free-form: NCBI does not close the set, so treat an unfamiliar value as opaque.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / articles / items / properties / recordType
      Added value: +{
      +  "description": "Which kind of PubMed record this is, set from the XML element it arrived in: `journal-article` for an ordinary article, `book-chapter` for an NCBI Bookshelf chapter, `book` for a whole Bookshelf book. Read this to tell the three apart — `publicationTypes` cannot, because PubMed labels a Bookshelf record \"Review\" or \"Study Guide\". `journalInfo` is present only on `journal-article`; `book` only on the other two.",
      +  "enum": [
      +    "journal-article",
      +    "book-chapter",
      +    "book"
      +  ],
      +  "type": "string"
      +}
    • changedOutput schema / properties / articles / items / properties / title / description
      Previous value: -"Article title"New value: +"Article title — the chapter title on a `book-chapter`, and the book title on a `book` record, where it repeats `book.title`."
    • addedOutput schema / properties / articles / items / required
      Added value: +[
      +  "recordType"
      +]
    • changedOutput schema / properties / unavailablePmids / description
      Previous value: -"PMIDs that returned no article data. Reported in full regardless of where a `maxResponseCharacters` cutoff lands — these are misses, not deferrals, and re-requesting them returns nothing."New value: +"PMIDs PubMed returned no record for. That is all this reports: PubMed omits an unknown PMID silently, with no error and no reason, so the absence says nothing about whether the PMID exists. Reported in full regardless of where a `maxResponseCharacters` cutoff lands — these are misses, not deferrals. Use `pubmed_search_articles` to find PMIDs that do resolve."
  4. Changed6 schema fields changedv2.10.8
    • addedInput schema / properties / maxResponseCharacters
      Added value: +{
      +  "description": "Opt-in ceiling for the whole response, in characters. Each article is measured as the JSON record it is returned as — title, abstract, authors, journal, MeSH terms, grants, identifiers, every field it carries. Articles are kept in response order until the next one would cross the ceiling; that article and the rest are deferred whole (never partially populated) and listed in `deferred.ids`. Response envelope fields — counts, `unavailablePmids`, `deferred` itself — are not counted. Omit to return every resolved article.",
      +  "maximum": 1000000,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • addedOutput schema / properties / deferred
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Continuation state for articles the whole-response budget withheld. Present only when `maxResponseCharacters` deferred at least one article.",
      +  "properties": {
      +    "deferredCount": {
      +      "description": "Articles that resolved but were withheld to stay under the ceiling",
      +      "type": "number"
      +    },
      +    "ids": {
      +      "description": "PMIDs of the deferred articles, in response order. Re-call `pubmed_fetch_articles` with these as `pmids` and the same other inputs to retrieve them. Never contains a PMID from `unavailablePmids`.",
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "maxResponseCharacters": {
      +      "description": "The `maxResponseCharacters` ceiling this response was budgeted against",
      +      "type": "number"
      +    },
      +    "nextDeferredCharacters": {
      +      "description": "Serialized size of the next deferred article — the first entry in `ids`, where the response stopped. Raise `maxResponseCharacters` to at least this to make progress; a smaller article further down `ids` cannot be reached until this one fits.",
      +      "type": "number"
      +    },
      +    "returnedCharacters": {
      +      "description": "Serialized characters the returned article records account for",
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "maxResponseCharacters",
      +    "returnedCharacters",
      +    "deferredCount",
      +    "ids",
      +    "nextDeferredCharacters"
      +  ],
      +  "type": "object"
      +}
    • changedOutput schema / properties / notice / description
      Previous value: -"Optional guidance when no articles were returned — points to discovery tools. Absent on successful fetches."New value: +"Optional guidance when no articles were returned — points to discovery tools — or when `maxResponseCharacters` deferred articles, naming how to retrieve them. Absent on successful unbudgeted fetches."
    • changedOutput schema / properties / totalReturned / description
      Previous value: -"Number of articles returned"New value: +"Number of articles in this response. Under a `maxResponseCharacters` budget this counts the kept articles only; `deferred.deferredCount` covers the rest."
    • addedOutput schema / properties / truncated
      Added value: +{
      +  "description": "True when `maxResponseCharacters` withheld at least one resolved article. Absent when the response carries every article that resolved. The continuation state is in `deferred`.",
      +  "type": "boolean"
      +}
    • changedOutput schema / properties / unavailablePmids / description
      Previous value: -"PMIDs that returned no article data"New value: +"PMIDs that returned no article data. Reported in full regardless of where a `maxResponseCharacters` cutoff lands — these are misses, not deferrals, and re-requesting them returns nothing."
  5. Changed6 schema fields changedv2.10.4
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedInput schema / additionalProperties
      Added value: +false
    • changedOutput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "articles",
      +      "totalReturned"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / error
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "Present when the call failed. Absent on success.",
      +  "properties": {
      +    "code": {
      +      "description": "JSON-RPC error code for this failure.",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "data": {
      +      "additionalProperties": {},
      +      "properties": {
      +        "reason": {
      +          "description": "Machine-readable failure mode. Declared by this tool: `queue_full`: Local NCBI request queue is at capacity. `ncbi_unreachable`: NCBI E-utilities is unreachable after all retry attempts. `ncbi_deadline_exceeded`: Total request deadline expired before NCBI returned a response. `ncbi_invalid_response`: NCBI returned a body that could not be parsed (invalid XML/JSON). `ncbi_resource_not_found`: NCBI returned a structured \"not found\" error for the requested ID(s). `invalid_efetch_response`: NCBI EFetch returned a payload missing the PubmedArticleSet wrapper. Other values are possible when a failure originates below the handler.",
      +          "examples": [
      +            "queue_full",
      +            "ncbi_unreachable",
      +            "ncbi_deadline_exceeded",
      +            "ncbi_invalid_response",
      +            "ncbi_resource_not_found",
      +            "invalid_efetch_response"
      +          ],
      +          "type": "string"
      +        },
      +        "recovery": {
      +          "additionalProperties": {},
      +          "description": "Actionable next step for the caller.",
      +          "properties": {
      +            "hint": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "hint"
      +          ],
      +          "type": "object"
      +        },
      +        "retryable": {
      +          "description": "Whether retrying may succeed.",
      +          "type": "boolean"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "message": {
      +      "description": "Human-readable description of what went wrong.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "code",
      +    "message"
      +  ],
      +  "type": "object"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "articles",
      -  "totalReturned"
      -]
  6. Changed1 schema field changedv2.10.2
    • changedOutput schema / properties / articles / items / properties / doi / description
      Previous value: -"DOI"New value: +"DOI, cased as NCBI reports it (usually the publisher's mixed case). DOIs are case-insensitive by spec and no case normalization is applied here, so the same DOI can arrive in a different case from `pubmed_europepmc_search` and `pubmed_europepmc_fetch` (NCBI `10.1056/NEJMoa2212948`, Europe PMC `10.1056/nejmoa2212948`) — a byte-for-byte comparison across the two reports a false mismatch."
  7. Changed1 schema field changedv2.9.1
    • addedOutput schema / properties / notice
      Added value: +{
      +  "description": "Optional guidance when no articles were returned — points to discovery tools. Absent on successful fetches.",
      +  "type": "string"
      +}
  8. Addedv2.7.6
  9. Removedv2.7.4
  10. Addedv2.3.2

TDQS

A3.9/5.0
Behavior4/5

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

Annotations already declare readOnlyHint and openWorldHint, so the safety profile is covered; the description still adds real context by disclosing what the response contains and the truncation contract — articles past the ceiling are deferred whole, never partially populated, and surfaced in deferred.ids. That deferral semantics is behaviorally meaningful beyond the annotations.

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?

Three tight sentences: purpose first, payload contents second, the truncation contract last. Every sentence carries information and none is redundant filler.

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

Completeness5/5

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

For a 4-parameter read tool with a full input schema and an output schema, the description supplies exactly the missing operational piece (how bulk responses are bounded and how to resume), and need not restate return fields because the output schema exists. Nothing required to invoke it correctly is absent.

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% and each parameter already carries a thorough description, so the baseline is 3. The description restates the maxResponseCharacters/deferred.ids contract the schema already defines, adding no syntax or format detail the agent could not read from the schema.

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?

Specific verb + resource: 'Fetch full article metadata by PubMed IDs', with an enumeration of what the payload contains (abstract, authors, journal, MeSH terms, retraction/erratum/comment notices). The contrast with the sibling pubmed_fetch_fulltext is only implicit in the word 'metadata', so there is no explicit sibling differentiation.

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 when to use it (you have PMIDs and want their metadata) and explains the follow-up pattern for deferred IDs, but it never names an alternative or states exclusions. Nothing tells the agent when to pick this over pubmed_europepmc_fetch or pubmed_fetch_fulltext.

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