Skip to content

Releases: herbertkokholm/cite-caddy

v2.3.4

Choose a tag to compare

@herbertkokholm herbertkokholm released this 19 Aug 20:40

Serve /.well-known/glama.json and /llms.txt

Glama.ai's Connector listing requires glama.json to be served at
/.well-known/glama.json to verify server ownership -- confirmed missing in
production (nginx access log showed Glama's crawler getting a 404 there).
Also adds llms.txt (the llmstxt.org convention),
served at /llms.txt, following the same pattern. Both are unauthenticated
routes, same as the existing /.well-known/mcp/server-card.json//healthz/
/status routes -- no change to /mcp's auth behavior. See #14.

Dependencies

Bumps pyzotero 1.13.4 -> 1.13.6 (#13).

v2.3.3

Choose a tag to compare

@herbertkokholm herbertkokholm released this 16 Aug 14:13

Enable DNS-rebinding protection on the streamable-http transport

The mcp SDK ships Host/Origin header validation
(TransportSecurityMiddleware) but disables it by default unless a
deployment explicitly opts in -- this server wasn't. Flagged by an
external MCP compliance scanner (mcpdebugger.dev)
as failing the 2026 Streamable HTTP transport contract.

main()'s streamable-http path now wires up TransportSecuritySettings,
scoped to MCP_PUBLIC_URL (host + origin) -- see #11.

Dependencies

Bumps pyzotero 1.13.3 -> 1.13.4 (#9).

v2.3.2

Choose a tag to compare

@herbertkokholm herbertkokholm released this 09 Aug 13:10

server-card.json: stop emitting explicit nulls

/.well-known/mcp/server-card.json was serializing each tool's
outputSchema/annotations with a plain model_dump(), so a tool
without an output schema -- or any unset annotation hint like
destructiveHint/idempotentHint -- came through as an explicit JSON
null instead of being omitted. That's inconsistent with the MCP SDK's
own tools/list serialization, which always excludes unset fields, and
a strict client-side JSON Schema validator could reasonably reject a
null where it expects either a missing key or a concrete value.

Fixed by using exclude_none=True and only including outputSchema/
annotations in a tool's card entry when the tool actually has them.

v2.3.1

Choose a tag to compare

@herbertkokholm herbertkokholm released this 09 Aug 11:59

Server-card metadata: fill in description, homepage, icon, output schemas, annotations

/.well-known/mcp/server-card.json was omitting serverInfo.description/
homepage/icon and each tool's outputSchema/annotations, even
though that data already exists via MCP_WEBSITE_URL and each tool's own
@mcp.tool() registration -- it just wasn't being copied over into the
card. Filling these in is what Smithery's own quality-score dashboard
flagged as missing after publishing there.

Smithery badges

README and the landing page now link to the
Smithery listing,
alongside the existing Glama badge.

v2.3.0

Choose a tag to compare

@herbertkokholm herbertkokholm released this 09 Aug 11:12

/.well-known/mcp/server-card.json discovery endpoint

A pragmatic approximation of SEP-2127
("MCP Server Cards -- HTTP Server Discovery", superseding the withdrawn
SEP-1649) -- which is not a ratified part of the MCP spec: the PR is
still open, and even its own well-known path has shifted between drafts
(some revisions use .well-known/ai-catalog.json instead of this one).

If SEP-2127 (or a successor) ratifies with a different
path/shape, this endpoint will need to change to match.

Generated live from the actual tool registry (mcp.list_tools()) on every
request rather than a static file, so it reports the real 39 tools with
accurate schemas and can't drift out of sync as tools are added or changed.

v2.2.0

Choose a tag to compare

@herbertkokholm herbertkokholm released this 09 Aug 09:48

list_creator_fields

Zotero's /creatorFields lists the name-shape fields (firstName,
lastName, name, ...) valid on a creators entry -- distinct from
list_item_creator_types, which lists creatorType roles (author,
editor, ...) for one item type. Nothing in this server previously
exposed it.

Idempotency-key + update_publication_status

delete_item_permanently, delete_collection, delete_tag,
delete_saved_search, and move_item_to_different_library now accept
an optional idempotency_key. A retry with the same key and arguments
replays the original outcome -- success or error -- instead of
running against Zotero again.

Caching failures, not just successes, is the point: it's what actually
closes move_item_to_different_library's documented duplicate-on-retry
risk. That tool recreates the item in the target library, then deletes
it from the source. If the create succeeds but the delete then fails,
a bare retry today redoes the whole thing -- since the source item's
version hasn't changed -- creating a second duplicate in the target
library. With idempotency_key, the retry replays the cached failure
instead of touching Zotero again.

Also new: update_publication_status, for "a preprint just got a
DOI." Same in-place, key-preserving patch machinery as update_item,
but the one tool allowed to change item_type too (e.g. preprint ->
journalArticle) -- update_item still forbids that, since changing
itemType changes which fields are valid and this is the one place
that's the deliberate point of the call. Idempotency-key-protected by
default.

export_bibliography

Formatted HTML bibliography/citation entries (in a given CSL style)
or portable export data (csljson, bibtex) for a list of item keys.
BibTeX comes back as one combined, ready-to-paste text blob rather than
a fragmented parsed structure. RIS isn't supported yet -- pyzotero's
response-format dispatch only recognizes it by exact Content-Type
header, and that path is unverified.

v2.1.0

Choose a tag to compare

@herbertkokholm herbertkokholm released this 08 Aug 14:04

/status and /status.html monitoring endpoints

Two new unauthenticated GET routes, HTTP mode only (alongside /healthz):

  • /status -- JSON snapshot of process uptime, onboarded-tenant count,
    and per-tool call/error totals. Aggregate only, by tool name -- no
    per-tenant or per-library breakdown, which is what keeps it safe to
    leave unauthenticated.
  • /status.html -- the same data rendered as a small page (connector
    icon + name, a metrics table, a per-tool call/error table) for a human
    checking in a browser rather than a script.

Counts are tracked by a new tools/call middleware and in-memory
counters in app/metrics.py -- deliberately not a metrics/analytics
system, just an "is it up and roughly how busy is it" signal that
resets on every restart/redeploy.

The login page (/login) also gets the same connector icon now,
top-centered above the heading.

v2.0.0

Choose a tag to compare

@herbertkokholm herbertkokholm released this 05 Aug 08:50

Structured creators

get_item/search_items/list_trash/create_item/update_item's
creators field was a single flattened "Fornavn Efternavn, ..."
string. That discarded each creator's creatorType (author vs. editor
vs. seriesEditor vs. translator vs. contributor) and the
firstName/lastName vs. single-field name (institutions) distinction --
already leading to a real misread, where a proceedings' editors were
parsed as co-authors.

creators is now a list of {creatorType, firstName, lastName} (or
{creatorType, name} for single-field/institutional creators) entries --
the same shape create_item/update_item already accepted as input, so
a result from get_item/search_items can be passed straight back into
an update_item call.

Breaking change: creators's type changes from str to list[dict]
across all five tools built on the shared item summary. No
backward-compat shim -- any client parsing the old comma-joined string
needs updating to read the structured entries instead.

v1.5.0

Choose a tag to compare

@herbertkokholm herbertkokholm released this 30 Jul 15:05

Upgraded to mcp 2.0.0

FastMCP was renamed to MCPServer and moved from mcp.server.fastmcp
to mcp.server; host/port/stateless_http moved from the
constructor to mcp.run()'s kwargs. Reporting our own package version
in serverInfo no longer needs the _mcp_server private-attribute
workaround, since MCPServer now takes version= directly.

Verified against the real mcp==2.0.0 package: the
OAuthAuthorizationServerProvider protocol app/oauth_provider.py
implements is unchanged, and all 139 tests pass.

No user-facing behavior change -- same tools, same OAuth flow, same
transport selection.

v1.4.0

Choose a tag to compare

@herbertkokholm herbertkokholm released this 30 Jul 09:15

Correct serverInfo.version, pin Docker builds to uv.lock, and make the
connector website/icon configurable.

Surfaced while verifying v1.3.0's icon/website_url wiring end-to-end
against the live server via MCPJam Inspector's OAuth debugger: it showed
v1.29.0, which turned out to be the installed mcp SDK package's own
version, not zotero-mcp's (FastMCP has no version= constructor param, so
the SDK silently falls back to its own package version). Digging into why
production had mcp==1.29.0 while the local lockfile pins 1.28.1 found
the real bug: the Dockerfile installed via pip install . straight from
pyproject.toml, completely bypassing uv.lock, so production dependency
versions could silently drift from what's tested locally/in CI.

  • serverInfo.version now correctly reports zotero-mcp's own installed
    version (mcp._mcp_server.version set from importlib.metadata),
    matching pyproject.toml.
  • Dockerfile now installs via uv sync --frozen --no-dev, so the image
    gets exactly what's pinned in uv.lock -- a stale/drifted lock now
    fails the build loudly instead of silently resolving to newer releases.
  • MCP_WEBSITE_URL is now an optional env var (previously hardcoded) --
    website_url/icons are cleanly omitted when it's unset instead of
    building a broken icon URL. Documented in .env.example, the README's
    Configuration section, and the module docstring.
  • The docs landing page now shows the connector icon too (favicon +
    header), not just the MCP server's own serverInfo.