Skip to content

v4.0.0 — MCP-Spec-Konformanz

Choose a tag to compare

@natorus87 natorus87 released this 18 Sep 14:25
· 68 commits to main since this release

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.0

natorus87/kiwiki:latest pointed to the same digest (sha256:6119aaf31ada…) at release time.

Security

  • File history stays inside the user's namespace — /ui/history now validates the path parameter
    with the same check as the MCP tools. Previously a path like ../<other-user>/notes/x.md reached
    git log unfiltered; if a repository existed above the user directory, the view exposed other users'
    commit metadata.
  • Background greps are bound to their owner — grep_status only 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/authorize checks the same
    API key as /login but sits in the more generous oauth tier. 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_lines and file_info exclude
    .kiwiki, matching the existing write block.

Changed

  • Knowledge tools declare their response — entity_details, entity_neighbors, fact_timeline,
    explain_relation and knowledge_reindex used {"type": "object", "additionalProperties": true} and
    thus declared nothing. The schemas now name fields, types, ranges (depth 1–3, confidence 0–1) and
    allowed status values. entity and relation are explicitly nullable: null means "looked, found
    nothing", a missing field means "knowledge engine off".
  • MCP now negotiates revision 2025-06-18 — outputSchema and structuredContent only belong to the
    specification from this revision on. kiwiki shipped both but announced 2025-03-26 in the handshake;
    a client aligning its tool model with the negotiated revision saw unknown fields. Clients requesting
    2024-11-05 or 2025-03-26 still get exactly that revision; a newer request (e.g. 2025-11-25) is
    answered with 2025-06-18.
  • fetch returns list metadata readably — tags: [python, mcp] appears as "python, mcp" instead
    of the Python representation "['python', 'mcp']".
  • BREAKING: search and fetch follow the OpenAI connector contract — search returns
    {"results": [{"id", "title", "text", "url"}]}, fetch returns {"id", "title", "text", "url", "metadata"}. The id is the note path and can be passed to fetch unchanged; url cites
    /ui/file via KIWIKI_BASE_URL. Without a base URL the link stays relative, because no request is
    available in the tool dispatcher. read_file keeps 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_index and search_history now return their results under the
    items key ({"items": [...]}). The MCP specification only allows objects for outputSchema and
    structuredContent; strictly validating clients rejected the previous array responses. Integrations
    that parse content[0].text directly as an array need to be adjusted.

Fixed

  • Free-form frontmatter values respect the tool schemas — title: 2026 and tags: python are valid
    YAML and yield an int and a scalar. list_all_files and recent_files passed them through unchecked
    and violated their own outputSchema; 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_tag no 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 scalar tags: was affected.
  • template declares the template_type field it returns — the tool shared _STATUS_SCHEMA with
    six others, which forbade any further field via additionalProperties: false.
  • NaN and Infinity in frontmatter no longer break the response — score: .nan ended up as a bare
    JSON literal in the output. RFC 8259 knows neither; strict parsers failed on the entire response.
  • ping is answered — the MCP specification requires an immediate empty response in every
    revision. kiwiki answered -32601 Method not found instead, delivered as HTTP 404; clients using
    ping as keepalive dropped the session.
  • Unquoted dates in frontmatter no longer break the tools — YAML reads created: 2026-01-01 as a
    datetime.date. That value reached json.dumps() and sorting and made read_file, read_many,
    list_files, list_all_files, recent_files and statistics fail with an internal error. The
    server instructions explicitly ask for created/updated, and write_file wrote 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_files respects its own outputSchema — the response contained created, while the
    schema forbade any further field via additionalProperties: false. Strictly validating clients
    rejected the result.
  • grep_status no longer returns null for result — the field is optional and is omitted for
    not_found and running instead of violating the object type declared in the schema.
  • resources/templates/list answers with an empty list — kiwiki offers no URI templates, but
    clients query them during discovery anyway. The previous -32601 answer came back as HTTP 404.
  • Notes with a duplicate tag no longer break the knowledge index — frontmatter lists are
    deduplicated before indexing. Previously tags: [python, python] produced two relations with the
    same primary key; the document stayed unindexed after three failed attempts and the tenant status
    reported degraded.
  • 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.json is 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;
    previously notes/Meeting, Q4.md fell apart into two unusable fragments and was silently missing
    from the result.
  • template reports invalid input — an unknown template_type and 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 at running forever.