Skip to main content
Glama

Attach a file (PDF, snapshot) to an item

zotero_attach_file

Add a stored file attachment (e.g. a PDF full text) under an existing item. Give parent (the item key) and one of url (Zoteus downloads it, then stores it), path (a file on the machine running Zoteus), or find_oa: true (Zoteus…

Instructions

Add a stored file attachment (e.g. a PDF full text) under an existing item. Give parent (the item key) and one of url (Zoteus downloads it, then stores it), path (a file on the machine running Zoteus), or find_oa: true (Zoteus looks the parent item's DOI up in OpenAlex and attaches the open-access PDF, if there is one). find_oa finds only copies OpenAlex already knows about, which is arXiv, PubMed Central, DOAJ journals and institutional repositories: it is not a way past a paywall, and it says so plainly when there is no free copy. The copy it finds is often the author's accepted or submitted manuscript rather than the published version, so the source, the version and the licence come back in the result and go into the attachment's title. filename and content_type are inferred when omitted. Saves through the Zotero desktop app when one is reachable (Zotero 10+ local API; you may be asked once to allow Zoteus write access, choose "Always Allow"), and otherwise through the cloud Web API, which needs ZOTERO_API_KEY with file access and uses your Zotero file-storage quota. url works on every setup including a remote/hosted Zoteus that cannot see your desktop, so prefer it over path unless the file really is on the server. Returns the new attachment key.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "parent": "<parent>"
    },
    "name": "zotero_attach_file"
  }
}

Input Schema

NameRequiredDescriptionDefault
urlNoURL to download the file from; works on remote/hosted servers, where it must be an https link to a public host (no private or loopback addresses, 64 MB at most).
pathNoFilesystem path to the file, on the machine running Zoteus.
titleNoAttachment title, e.g. "Full Text PDF".
parentYesKey of the parent item to attach the file to.
find_oaNoFind the open-access PDF for the parent item by its DOI (OpenAlex) and attach it. Use instead of `url`/`path`, not alongside them. Refuses, saying why, when the item has no DOI, when OpenAlex reports no open-access copy, when the item already has a PDF, or when what the link serves is not a PDF.
filenameNoFile name to store; inferred from path/url if omitted.
library_idNoGroup library to attach the file in (from zotero_groups). Goes to the desktop app when it holds that group (Zotero 10+), otherwise to the cloud.
content_typeNoMIME type; inferred from the extension if omitted (pdf -> application/pdf).
library_typeNoWhich library to address: "user" (a personal library) or "group" (a shared group library). Omit to use the library this server is configured for. "group" on its own is refused: pass library_id with it.

Output Schema

NameRequiredDescriptionDefault
oaNoWhere an automatically discovered open-access PDF came from, and what version it is. Present only for find_oa.
bytesYesSize of the stored file.
parentYesThe item it hangs off.
targetNoWhere the write went: "local" (Zotero desktop local API), "desktop" (connector protocol) or "cloud" (Zotero Web API).
filenameYesFile name stored.
attachmentYesKey of the attachment item created.
contentTypeYesMIME type stored, e.g. "application/pdf".
alreadyInStorageNoTrue when Zotero already held these bytes and only the item was created (cloud path).

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changedv1.23.0
    • changedInput schema / properties / library_id / description
      Previous value: -"Group library to attach the file in (from zotero_groups); forces the cloud path instead of the desktop app."New value: +"Group library to attach the file in (from zotero_groups). Goes to the desktop app when it holds that group (Zotero 10+), otherwise to the cloud."
  2. Changed4 schema fields changedv1.21.0
    • addedInput schema / properties / find_oa
      Added value: +{
      +  "description": "Find the open-access PDF for the parent item by its DOI (OpenAlex) and attach it. Use instead of `url`/`path`, not alongside them. Refuses, saying why, when the item has no DOI, when OpenAlex reports no open-access copy, when the item already has a PDF, or when what the link serves is not a PDF.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / library_id / exclusiveMinimum
      Added value: +0
    • changedInput schema / properties / url / description
      Previous value: -"URL to download the file from; works on remote/hosted servers."New value: +"URL to download the file from; works on remote/hosted servers, where it must be an https link to a public host (no private or loopback addresses, 64 MB at most)."
    • addedOutput schema / properties / oa
      Added value: +{
      +  "additionalProperties": true,
      +  "description": "Where an automatically discovered open-access PDF came from, and what version it is. Present only for find_oa.",
      +  "properties": {
      +    "landingPage": {
      +      "description": "The human landing page for this copy, when the location has one.",
      +      "type": "string"
      +    },
      +    "licence": {
      +      "description": "The licence the host declares, as OpenAlex reports it, e.g. \"cc-by\". Absent when none is stated.",
      +      "type": "string"
      +    },
      +    "source": {
      +      "description": "Who hosts the copy, as OpenAlex names them, e.g. \"arXiv\" or \"PubMed Central\".",
      +      "type": "string"
      +    },
      +    "url": {
      +      "description": "The open-access PDF link OpenAlex reported, and where these bytes came from.",
      +      "type": "string"
      +    },
      +    "version": {
      +      "description": "Which version this copy is: \"published\" (the version of record), \"accepted\" (the reviewed author manuscript) or \"submitted\" (a preprint). Absent when OpenAlex does not say.",
      +      "enum": [
      +        "published",
      +        "accepted",
      +        "submitted"
      +      ],
      +      "type": "string"
      +    },
      +    "versionCaveat": {
      +      "description": "Why this copy is not the publisher’s version of record; absent when it is.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "url"
      +  ],
      +  "type": "object"
      +}
  3. Changed2 schema fields changedv1.20.2
    • removedInput schema / $schema
      Removed value: -"http://json-schema.org/draft-07/schema#"
    • removedOutput schema / $schema
      Removed value: -"http://json-schema.org/draft-07/schema#"
  4. Changed3 schema fields changedv1.20.0
    • changedInput schema / properties / library_id / description
      Previous value: -"Group library to attach in; forces the cloud path."New value: +"Group library to attach the file in (from zotero_groups); forces the cloud path instead of the desktop app."
    • addedInput schema / properties / library_type / description
      Added value: +"Which library to address: \"user\" (a personal library) or \"group\" (a shared group library). Omit to use the library this server is configured for. \"group\" on its own is refused: pass library_id with it."
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "$schema": "http://json-schema.org/draft-07/schema#",
      +  "additionalProperties": true,
      +  "properties": {
      +    "alreadyInStorage": {
      +      "description": "True when Zotero already held these bytes and only the item was created (cloud path).",
      +      "type": "boolean"
      +    },
      +    "attachment": {
      +      "description": "Key of the attachment item created.",
      +      "type": "string"
      +    },
      +    "bytes": {
      +      "description": "Size of the stored file.",
      +      "type": "number"
      +    },
      +    "contentType": {
      +      "description": "MIME type stored, e.g. \"application/pdf\".",
      +      "type": "string"
      +    },
      +    "filename": {
      +      "description": "File name stored.",
      +      "type": "string"
      +    },
      +    "parent": {
      +      "description": "The item it hangs off.",
      +      "type": "string"
      +    },
      +    "target": {
      +      "description": "Where the write went: \"local\" (Zotero desktop local API), \"desktop\" (connector protocol) or \"cloud\" (Zotero Web API).",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "attachment",
      +    "parent",
      +    "filename",
      +    "bytes",
      +    "contentType"
      +  ],
      +  "type": "object"
      +}
  5. Changed4 schema fields changedv1.6.0
    • addedInput schema / properties / library_id
      Added value: +{
      +  "description": "Group library to attach in; forces the cloud path.",
      +  "type": "integer"
      +}
    • addedInput schema / properties / library_type
      Added value: +{
      +  "enum": [
      +    "user",
      +    "group"
      +  ],
      +  "type": "string"
      +}
    • changedInput schema / properties / path / description
      Previous value: -"Local filesystem path to the file."New value: +"Filesystem path to the file, on the machine running Zoteus."
    • changedInput schema / properties / url / description
      Previous value: -"URL to download the file from."New value: +"URL to download the file from; works on remote/hosted servers."
  6. Addedv1.3.1

TDQS

A4.8/5.0
Behavior5/5

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

Annotations only declare the write/open-world/safe-mutation profile; the description adds substantial context beyond them: desktop-app vs cloud-Web-API routing, the one-time 'Always Allow' write prompt, the ZOTERO_API_KEY and file-storage-quota requirement for cloud, the accepted-vs-published manuscript caveat, and the four refusal conditions for find_oa.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core action and the three input modes, and every sentence is substantive (paywall limits, version/licence propagation, storage routing). It is dense and long, but for a nine-parameter tool with fallback behavior the length is largely earned; only occasional flourishes ('says so plainly') are non-essential.

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?

Given an output schema exists, the description need not explain return values, yet it usefully notes the return (a new attachment key). Combined with the parameter-usage, auth, and storage details, it is complete enough for an agent to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: it frames url/path/find_oa as a mutually exclusive triple with platform-dependent applicability ('url works on every setup ... a remote/hosted Zoteus') and notes filename/content_type are inferred when omitted.

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

Purpose5/5

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

The opening sentence states a specific verb and resource ('Add a stored file attachment ... under an existing item'), and the third-source routing (url/path/find_oa) makes clear what it does beyond a generic 'attach'. It is readily distinguished from the read-oriented sibling zotero_attachment/zotero_get_fulltext.

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

Usage Guidelines5/5

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

Explicitly prescribes when to use each of the three input modes ('prefer url over path unless the file really is on the server', 'use find_oa instead of url/path, not alongside them'), and clarifies that find_oa is not a paywall bypass. Alternatives and exclusions are named, not inferred.

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