Skip to main content
Glama

Verify Citations

verify_citations

Verify citations in an output by fetching sources and using an LLM judge to confirm each source supports its claim, returning per-citation verdicts and an overall score.

Instructions

Extract the citations in an output, fetch the sources (opt-in, SSRF-guarded) and ask an LLM judge on your key whether each source supports its claim.

What it does. Three phases. Extraction, no network: [N] references, (Author, Year), bare URLs and DOIs. Fetch of URL and DOI citations only when allow_fetch is true or IRIS_CITATION_ALLOW_FETCH=1, through a scheme allowlist, private and cloud-metadata address blocking, an optional hostname allowlist (domain_allowlist, merged with IRIS_CITATION_DOMAINS), a per-source timeout and byte cap, and at most three re-checked redirects. Then one judge call per resolved citation on your own key, reading the first part of each source, capped in total by max_cost_usd_total. Up to max_citations are verified; extras are skipped, not errored. overall_score is supported / judged and null when nothing was judged. Per-citation failures (bad scheme, blocked address, timeout, too large, cost cap, fetch disabled) are reported on the citation, never scored as unsupported. One evaluation row is stored.

When not to use it. When the output has no citations: the score is null, and evaluate_output's hallucination signals are the cheap check. Without a key (IRIS_ANTHROPIC_API_KEY or IRIS_OPENAI_API_KEY): the call returns IRIS_JUDGE_NOT_ENABLED with the enable steps. With fetch enabled and an open allowlist on untrusted output: you are running a user-directed fetcher — set IRIS_CITATION_DOMAINS.

Returns. JSON with id (the evaluation id; read it back at iris://evaluations/{id}); trace_id (the linked trace, when one was named); overall_score (supported / judged; null when nothing was judged); passed (true when every judged citation was supported; false when any judged citation was not; NULL when nothing was judged — no verdict, because nothing was verified. Until 0.10.0 that last case returned true.); total_unsupported (judged citations the judge ruled unsupported — the number the verdict turns on); total_citations_found (citations extracted from the output); total_resolved (citations whose source was fetched); total_judged (citations the judge ruled on); total_supported (citations the judge found supported); total_cost_usd (the spend across every judge call); citations (per citation: the citation (raw, kind, identifier, offsets), resolve_status ok | skipped | error, resolve_error, source (url, status, content_type, bytes_fetched, truncated), judge (supported, confidence, rationale, cost_usd, latency_ms, tokens)).

Errors. IRIS_JUDGE_NOT_ENABLED, IRIS_JUDGE_UNKNOWN_MODEL and IRIS_UNKNOWN_TRACE before any fetch or spend. IRIS_JUDGE_FAILED when citations resolved but the judge failed on every one — an error, not a passing verdict; nothing is stored. Every failure returns {"error":{"code","message","recovery":[]}} with isError true; follow recovery before retrying.

Siblings. evaluate_with_llm_judge — general semantic scoring; evaluate_output — the free deterministic path, including the hallucination signals; log_trace — record the execution first.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
modelYesJudge model for per-citation verification. Supported: anthropic = claude-opus-4-7 | claude-sonnet-4-6 | claude-haiku-4-5 | claude-haiku-4-5-20251001; openai = gpt-4o | gpt-4o-mini | o1-mini.
outputYesThe agent output containing citations to verify
providerNoAuto-detected from model when omitted
trace_idNoLink verification result to a stored trace (id from log_trace / get_traces); an unknown id is rejected before any fetch or judge call
allow_fetchNoPermit outbound HTTP to resolve URLs/DOIs. Defaults to IRIS_CITATION_ALLOW_FETCH=1; false otherwise. SSRF-guarded regardless.
max_citationsNoMax citations to verify (extras skipped, not errored); default 20, at most 50
domain_allowlistNoRestrict fetches to hostnames in this list (suffix match allowed). Merged with IRIS_CITATION_DOMAINS env.
max_cost_usd_totalNoCap TOTAL judge cost across all citations in this call; default 1.00 USD — the pipeline stops when the next call would exceed it
per_source_max_bytesNoPer-URL body cap; default 5MB
per_source_timeout_msNoPer-URL fetch timeout; default 10_000

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesthe evaluation id; read it back at iris://evaluations/{id}
passedYestrue when every judged citation was supported; false when any judged citation was not; NULL when nothing was judged — no verdict, because nothing was verified. Until 0.10.0 that last case returned true.
trace_idNothe linked trace, when one was named
citationsYesper citation: the citation (raw, kind, identifier, offsets), resolve_status ok | skipped | error, resolve_error, source (url, status, content_type, bytes_fetched, truncated), judge (supported, confidence, rationale, cost_usd, latency_ms, tokens)
total_judgedYescitations the judge ruled on
overall_scoreYessupported / judged; null when nothing was judged
total_cost_usdYesthe spend across every judge call
total_resolvedYescitations whose source was fetched
total_supportedYescitations the judge found supported
total_unsupportedYesjudged citations the judge ruled unsupported — the number the verdict turns on
total_citations_foundYescitations extracted from the output

Schema Changelog

Changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. Changed4 schema fields changedv0.10.0
    • changedOutput schema / properties / passed / description
      Previous value: -"true when every judged citation was supported, or nothing was judged and nothing failed"New value: +"true when every judged citation was supported; false when any judged citation was not; NULL when nothing was judged — no verdict, because nothing was verified. Until 0.10.0 that last case returned true."
    • changedOutput schema / properties / passed / type
      Previous value: -"boolean"New value: +[
      +  "boolean",
      +  "null"
      +]
    • addedOutput schema / properties / total_unsupported
      Added value: +{
      +  "description": "judged citations the judge ruled unsupported — the number the verdict turns on",
      +  "maximum": 9007199254740991,
      +  "minimum": -9007199254740991,
      +  "type": "integer"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "id",
      -  "overall_score",
      -  "passed",
      -  "total_citations_found",
      -  "total_resolved",
      -  "total_judged",
      -  "total_supported",
      -  "total_cost_usd",
      -  "citations"
      -]New value: +[
      +  "id",
      +  "overall_score",
      +  "passed",
      +  "total_unsupported",
      +  "total_citations_found",
      +  "total_resolved",
      +  "total_judged",
      +  "total_supported",
      +  "total_cost_usd",
      +  "citations"
      +]
  2. Changed4 schema fields changedv0.9.0
    • changedInput schema / properties / max_citations / description
      Previous value: -"Max citations to verify (extras skipped); default 20"New value: +"Max citations to verify (extras skipped, not errored); default 20, at most 50"
    • changedInput schema / properties / max_cost_usd_total / description
      Previous value: -"Cap TOTAL judge cost across all citations in this call; default $1.00"New value: +"Cap TOTAL judge cost across all citations in this call; default 1.00 USD — the pipeline stops when the next call would exceed it"
    • changedInput schema / properties / model / description
      Previous value: -"Judge model for per-citation verification. Supported: anthropic = claude-opus-4-7 | claude-sonnet-4-6 | claude-haiku-4-5-20251001; openai = gpt-4o | gpt-4o-mini | o1-mini."New value: +"Judge model for per-citation verification. Supported: anthropic = claude-opus-4-7 | claude-sonnet-4-6 | claude-haiku-4-5 | claude-haiku-4-5-20251001; openai = gpt-4o | gpt-4o-mini | o1-mini."
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "$schema": "http://json-schema.org/draft-07/schema#",
      +  "additionalProperties": {},
      +  "properties": {
      +    "citations": {
      +      "description": "per citation: the citation (raw, kind, identifier, offsets), resolve_status ok | skipped | error, resolve_error, source (url, status, content_type, bytes_fetched, truncated), judge (supported, confidence, rationale, cost_usd, latency_ms, tokens)",
      +      "items": {
      +        "additionalProperties": {},
      +        "properties": {
      +          "resolve_status": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "resolve_status"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "id": {
      +      "description": "the evaluation id; read it back at iris://evaluations/{id}",
      +      "type": "string"
      +    },
      +    "overall_score": {
      +      "description": "supported / judged; null when nothing was judged",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "passed": {
      +      "description": "true when every judged citation was supported, or nothing was judged and nothing failed",
      +      "type": "boolean"
      +    },
      +    "total_citations_found": {
      +      "description": "citations extracted from the output",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "total_cost_usd": {
      +      "description": "the spend across every judge call",
      +      "type": "number"
      +    },
      +    "total_judged": {
      +      "description": "citations the judge ruled on",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "total_resolved": {
      +      "description": "citations whose source was fetched",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "total_supported": {
      +      "description": "citations the judge found supported",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "trace_id": {
      +      "description": "the linked trace, when one was named",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "id",
      +    "overall_score",
      +    "passed",
      +    "total_citations_found",
      +    "total_resolved",
      +    "total_judged",
      +    "total_supported",
      +    "total_cost_usd",
      +    "citations"
      +  ],
      +  "type": "object"
      +}
  3. Changed2 schema fields changedv0.8.0
    • addedInput schema / additionalProperties
      Added value: +false
    • changedInput schema / properties / trace_id / description
      Previous value: -"Link verification result to a trace"New value: +"Link verification result to a stored trace (id from log_trace / get_traces); an unknown id is rejected before any fetch or judge call"
  4. Changed3 schema fields changedv0.4.6
    • removedInput schema / additionalProperties
      Removed value: -false
    • addedInput schema / properties / per_source_max_bytes / maximum
      Added value: +9007199254740991
    • addedInput schema / properties / per_source_timeout_ms / maximum
      Added value: +9007199254740991
  5. Addedv0.1.10

TDQS

A4.6/5.0
Behavior5/5

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

Beyond annotations, the description discloses: fetch is opt-in via allow_fetch or env var, SSRF protections (scheme allowlist, blocked private/cloud-metadata addresses, hostname allowlist, timeouts, byte caps, redirect limits), cost caps, per-citation failures never scored as unsupported, one evaluation row stored, and the null-verdict behavior including a 0.10.0 change note. It also explains errors like IRIS_JUDGE_FAILED. Annotations are minimal (readOnly=false, openWorld=true, idempotent=false, destructive=false), so the description carries the burden and does so thoroughly.

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?

The description is long but earns its length: it uses labeled sections (What it does, When not to use it, Returns, Errors, Siblings) and front-loads the headline behavior. Some redundancy exists (e.g., 'When not to use it' repeats the key requirement mentioned in the opener), and the Returns section is dense, but the structure makes it navigable.

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 10-parameter, complex tool with an output schema, the description covers the pipeline phases, error codes, env var interactions, security posture, edge cases, and return fields. The output schema exists, so the description need not spell out return shapes, but it still summarizes them and adds the critical behavioral notes about passed/NULL and stored evaluations. Nothing essential is missing.

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%, so the schema documents every parameter meaning. The description adds operational context that interacts with parameters — e.g., allow_fetch defaulting from env, domain_allowlist merging with env, max_citations extras being skipped, max_cost_usd_total behavior — but the core semantic meaning per parameter is already in the schema. Baseline 3 is appropriate.

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 description opens with a specific verb phrase — 'Extract the citations in an output, fetch the sources (opt-in, SSRF-guarded) and ask an LLM judge on your key whether each source supports its claim.' This precisely states the tool's operation and resource. It differentiates from siblings by name ('verify_citations') and behavior, and the Siblings section explicitly distinguishes it from evaluate_with_llm_judge and evaluate_output.

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?

The description includes an explicit 'When not to use it' section naming concrete conditions: no citations, no key, and the risk of an open allowlist on untrusted output. It even points to evaluate_output's hallucination signals as the cheap alternative. This is exemplary guidance for when to use the tool vs alternatives.

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

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/iris-eval/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server