Repository navigation
v4.0.0 — MCP-Spec-Konformanz
Warning
Breaking changes — MCP integrations that parse content[0].text directly as an array need to be adjusted. Details under Changed.
Docker
docker pull natorus87/kiwiki:4.0.0natorus87/kiwiki:latest pointed to the same digest (sha256:6119aaf31ada…) at release time.
Security
- File history stays inside the user's namespace —
/ui/historynow validates thepathparameter
with the same check as the MCP tools. Previously a path like../<other-user>/notes/x.mdreached
git logunfiltered; if a repository existed above the user directory, the view exposed other users'
commit metadata. - Background greps are bound to their owner —
grep_statusonly returns results to the user who
started the job. Foreign job IDs behave like unknown ones. - Failed attempts on the OAuth form have their own budget —
POST /oauth/authorizechecks the same
API key as/loginbut sits in the more generousoauthtier. Failed entries now count against a
separate limit (KIWIKI_KEY_ATTEMPT_LIMIT, default 5/minute) without throttling the connector
handshake. - Internal files are blocked for reading too —
find,read_linesandfile_infoexclude
.kiwiki, matching the existing write block.
Changed
- Knowledge tools declare their response —
entity_details,entity_neighbors,fact_timeline,
explain_relationandknowledge_reindexused{"type": "object", "additionalProperties": true}and
thus declared nothing. The schemas now name fields, types, ranges (depth1–3,confidence0–1) and
allowedstatusvalues.entityandrelationare explicitly nullable:nullmeans "looked, found
nothing", a missing field means "knowledge engine off". - MCP now negotiates revision 2025-06-18 —
outputSchemaandstructuredContentonly belong to the
specification from this revision on. kiwiki shipped both but announced2025-03-26in the handshake;
a client aligning its tool model with the negotiated revision saw unknown fields. Clients requesting
2024-11-05or2025-03-26still get exactly that revision; a newer request (e.g.2025-11-25) is
answered with2025-06-18. fetchreturns list metadata readably —tags: [python, mcp]appears as"python, mcp"instead
of the Python representation"['python', 'mcp']".- BREAKING:
searchandfetchfollow the OpenAI connector contract —searchreturns
{"results": [{"id", "title", "text", "url"}]},fetchreturns{"id", "title", "text", "url", "metadata"}. Theidis the note path and can be passed tofetchunchanged;urlcites
/ui/fileviaKIWIKI_BASE_URL. Without a base URL the link stays relative, because no request is
available in the tool dispatcher.read_filekeeps its previous format and is not affected. - BREAKING: list tools return an object instead of an array —
list_files,sort,
list_all_files,recent_files,tag_indexandsearch_historynow return their results under the
itemskey ({"items": [...]}). The MCP specification only allows objects foroutputSchemaand
structuredContent; strictly validating clients rejected the previous array responses. Integrations
that parsecontent[0].textdirectly as an array need to be adjusted.
Fixed
- Free-form frontmatter values respect the tool schemas —
title: 2026andtags: pythonare valid
YAML and yield an int and a scalar.list_all_filesandrecent_filespassed them through unchecked
and violated their ownoutputSchema; a validating client then rejected the whole response. A scalar
tag was also silently dropped instead of being read as a one-element list. batch_tagno longer splits scalar tags into single letters —list("python")produced six tags
(p,y,t, …) and wrote them back to the note. Every note with a scalartags:was affected.templatedeclares thetemplate_typefield it returns — the tool shared_STATUS_SCHEMAwith
six others, which forbade any further field viaadditionalProperties: false.NaNandInfinityin frontmatter no longer break the response —score: .nanended up as a bare
JSON literal in the output. RFC 8259 knows neither; strict parsers failed on the entire response.pingis answered — the MCP specification requires an immediate empty response in every
revision. kiwiki answered-32601 Method not foundinstead, delivered as HTTP 404; clients using
pingas keepalive dropped the session.- Unquoted dates in frontmatter no longer break the tools — YAML reads
created: 2026-01-01as a
datetime.date. That value reachedjson.dumps()and sorting and maderead_file,read_many,
list_files,list_all_files,recent_filesandstatisticsfail with an internal error. The
server instructions explicitly ask forcreated/updated, andwrite_filewrote the date back
unquoted — the server produced the unreadable note itself. Frontmatter is now normalised to
JSON-compatible types during parsing, for the read and the write path alike. list_all_filesrespects its ownoutputSchema— the response containedcreated, while the
schema forbade any further field viaadditionalProperties: false. Strictly validating clients
rejected the result.grep_statusno longer returnsnullforresult— the field is optional and is omitted for
not_foundandrunninginstead of violating the object type declared in the schema.resources/templates/listanswers with an empty list — kiwiki offers no URI templates, but
clients query them during discovery anyway. The previous-32601answer came back as HTTP 404.- Notes with a duplicate tag no longer break the knowledge index — frontmatter lists are
deduplicated before indexing. Previouslytags: [python, python]produced two relations with the
same primary key; the document stayed unindexed after three failed attempts and the tenant status
reporteddegraded. - Saving during indexing is no longer lost — a job is only completed if it is actually still
running. If a file was saved again in the meantime, completion used to discard the queued revision,
and the knowledge index stayed stale until restart. - Search recovers from a removed index —
init_db()detects a vanished database and recreates
tables and connections; the LIKE fallback catches SQLite errors just like the FTS path. Previously a
user's search stayed broken until process restart after a workspace rollback. - Sessions follow the configured data directory — the location of
sessions.jsonis resolved at
runtime instead of being frozen at import, and loading from disk runs entirely under the lock. - Export copes with commas in file names — the selection is sent as one form field per path;
previouslynotes/Meeting, Q4.mdfell apart into two unusable fragments and was silently missing
from the result. templatereports invalid input — an unknowntemplate_typeand a title without usable
characters now produce a clear error instead of an empty note or-.md.- Background greps stay referenced — the task is held on to, so it is not garbage-collected mid-run
and the job does not stay atrunningforever.