Skip to main content
Glama

Audit tags against a controlled vocabulary

zotero_tag_audit
Read-only

Audit a Zotero library against a controlled tag vocabulary, reporting off-vocabulary tags and items lacking required-tier tags. Optionally check coverage per collection.

Instructions

Audit a library against a controlled tag vocabulary with priority tiers. Provide the vocabulary inline as vocabulary (or a JSON file via vocabulary_path): { tags:[{name,tier?}], tiers?:[{name,required?}] }. Reports (1) off-taxonomy tags (library tags not in the vocabulary; Zotero auto-applied tags are bucketed separately unless include_auto), (2) items missing a tag from each required tier, and (3) optional per-collection coverage when scope.collection_keys is given. A key that none of these objects knows is refused and named, never dropped: a dropped scope, tier or required would change the question without changing the answer. Read-only. Tag and item enumeration both follow the library route, so a running Zotero desktop app serves the whole audit with no cloud API key.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitNoMax items listed per report (default 50).
scopeNoPer-collection coverage: `{ collection_keys: [...] }`. A key this tool does not know is refused, never ignored.
library_idNoNumeric id of the library to address, e.g. 5234875 for a group (zotero_groups lists the ids you can reach). Omit to use the configured default library; an id given without library_type is read as a group id.
vocabularyNoThe controlled vocabulary, inline: { tags: [{name, tier?}], tiers?: [{name, required?}] }. Use `vocabulary_path` instead to read it from a JSON file; passing both is refused.
include_autoNoTreat Zotero auto-applied tags as off-taxonomy too.
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.
vocabulary_pathNoPath to a JSON file with the vocabulary.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
autoTagsYesZotero auto-applied tags, bucketed apart unless include_auto was set.
collectionsNoPer-collection coverage; present only when scope.collection_keys was given.
offTaxonomyYesLibrary tags the vocabulary does not list, capped at `limit`.
itemsScannedYesTop-level items audited (notes and attachments are skipped).
autoTagsTotalYesHow many of those there are in total.
missingByTierYesPer required tier, the items carrying no tag from it.
offTaxonomyTotalYesHow many there are in total.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changedv1.21.0
    • addedInput schema / properties / library_id / exclusiveMinimum
      Added value: +0
  2. 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#"
  3. Changed11 schema fields changedv1.20.0
    • addedInput schema / properties / library_id / description
      Added value: +"Numeric id of the library to address, e.g. 5234875 for a group (zotero_groups lists the ids you can reach). Omit to use the configured default library; an id given without library_type is read as a group id."
    • 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."
    • addedInput schema / properties / scope / properties / collection_keys / description
      Added value: +"8-character collection keys to report coverage for, one report per key, e.g. [\"ABCD1234\"]."
    • addedInput schema / properties / vocabulary / description
      Added value: +"The controlled vocabulary, inline: { tags: [{name, tier?}], tiers?: [{name, required?}] }. Use `vocabulary_path` instead to read it from a JSON file; passing both is refused."
    • addedInput schema / properties / vocabulary / properties / tags / description
      Added value: +"The tags the library is allowed to use; anything else is reported as off-taxonomy."
    • addedInput schema / properties / vocabulary / properties / tags / items / properties / name / description
      Added value: +"The tag exactly as it is spelled in Zotero (case-sensitive), e.g. \"method/bayesian\"."
    • addedInput schema / properties / vocabulary / properties / tags / items / properties / tier / description
      Added value: +"Name of the tier this tag belongs to, matching a `vocabulary.tiers` entry, e.g. \"topic\"."
    • addedInput schema / properties / vocabulary / properties / tiers / description
      Added value: +"Tier definitions referenced by the tags, e.g. [{\"name\":\"topic\",\"required\":true}]."
    • addedInput schema / properties / vocabulary / properties / tiers / items / properties / name / description
      Added value: +"Tier name, referenced by a tag's `tier`, e.g. \"topic\" or \"status\"."
    • addedInput schema / properties / vocabulary / properties / tiers / items / properties / required / description
      Added value: +"Whether every item must carry a tag from this tier (default false). Items that do not are reported per tier."
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "$schema": "http://json-schema.org/draft-07/schema#",
      +  "additionalProperties": true,
      +  "properties": {
      +    "autoTags": {
      +      "description": "Zotero auto-applied tags, bucketed apart unless include_auto was set.",
      +      "items": {
      +        "$ref": "#/properties/offTaxonomy/items"
      +      },
      +      "type": "array"
      +    },
      +    "autoTagsTotal": {
      +      "description": "How many of those there are in total.",
      +      "type": "number"
      +    },
      +    "collections": {
      +      "description": "Per-collection coverage; present only when scope.collection_keys was given.",
      +      "items": {
      +        "additionalProperties": true,
      +        "properties": {
      +          "collectionKey": {
      +            "description": "The collection this report is for.",
      +            "type": "string"
      +          },
      +          "missingByTier": {
      +            "description": "The same per-tier gaps, inside that collection.",
      +            "items": {
      +              "$ref": "#/properties/missingByTier/items"
      +            },
      +            "type": "array"
      +          }
      +        },
      +        "required": [
      +          "collectionKey",
      +          "missingByTier"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "itemsScanned": {
      +      "description": "Top-level items audited (notes and attachments are skipped).",
      +      "type": "number"
      +    },
      +    "missingByTier": {
      +      "description": "Per required tier, the items carrying no tag from it.",
      +      "items": {
      +        "additionalProperties": true,
      +        "properties": {
      +          "itemCount": {
      +            "description": "Items carrying no tag from this tier.",
      +            "type": "number"
      +          },
      +          "items": {
      +            "description": "The first `limit` of those items.",
      +            "items": {
      +              "additionalProperties": true,
      +              "properties": {
      +                "key": {
      +                  "description": "Item key.",
      +                  "type": "string"
      +                },
      +                "title": {
      +                  "description": "Item title.",
      +                  "type": "string"
      +                }
      +              },
      +              "required": [
      +                "key"
      +              ],
      +              "type": "object"
      +            },
      +            "type": "array"
      +          },
      +          "omitted": {
      +            "description": "How many more there are beyond `limit`.",
      +            "type": "number"
      +          },
      +          "tier": {
      +            "description": "Tier name from the vocabulary.",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "tier",
      +          "itemCount",
      +          "items",
      +          "omitted"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "offTaxonomy": {
      +      "description": "Library tags the vocabulary does not list, capped at `limit`.",
      +      "items": {
      +        "additionalProperties": true,
      +        "properties": {
      +          "name": {
      +            "description": "The tag as the library spells it.",
      +            "type": "string"
      +          },
      +          "numItems": {
      +            "description": "Items carrying it.",
      +            "type": "number"
      +          }
      +        },
      +        "required": [
      +          "name"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "offTaxonomyTotal": {
      +      "description": "How many there are in total.",
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "offTaxonomy",
      +    "offTaxonomyTotal",
      +    "autoTags",
      +    "autoTagsTotal",
      +    "missingByTier",
      +    "itemsScanned"
      +  ],
      +  "type": "object"
      +}
  4. Changed1 schema field changedv1.18.0
    • addedInput schema / properties / scope / description
      Added value: +"Per-collection coverage: `{ collection_keys: [...] }`. A key this tool does not know is refused, never ignored."
  5. First observedv1.0.4

TDQS

A4.7/5.0
Behavior5/5

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

Even though annotations already declare readOnlyHint=true and destructiveHint=false, the description adds substantial behavioral detail: it is explicitly 'Read-only', unknown keys are 'refused and named, never dropped', auto tags are bucketed separately, and enumeration follows the library route so no cloud API key is needed. No contradiction with 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?

The description is dense but every sentence earns its place: purpose, vocabulary format, report list, unknown-key policy, read-only guarantee, and data-source route. Key information is front-loaded in the opening sentence.

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 tool with 7 parameters, nested objects, and an output schema, the description covers what the tool reports, how the vocabulary is supplied, how unknown keys are handled, what include_auto does, and how the library is accessed. Nothing an agent needs to invoke it correctly is missing.

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 schema carries most parameter meaning, but the description enriches key behaviors: passing both vocabulary and vocabulary_path is refused, include_auto changes how auto-applied tags are reported, and the unknown-key policy applies across scope, tier, and required. These additions go beyond the raw schema.

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?

Opens with a specific verb and resource: 'Audit a library against a controlled tag vocabulary with priority tiers.' This clearly distinguishes it from sibling tools like zotero_list_tags or zotero_manage_tags, which list or mutate tags rather than audit them against a vocabulary.

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

Usage Guidelines4/5

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

The description establishes clear use context: run an audit when you have a controlled vocabulary and want reports on off-taxonomy tags, missing required-tier tags, and per-collection coverage. It does not explicitly name alternatives or exclusions, but the audit framing makes when-to-use apparent.

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