MCP server and JSON APIs¶
Claude Desktop reaches Kosmos through a small MCP server that each user
runs on their own machine, talking to token-authenticated JSON APIs in
apps/notes, apps/case and apps/search. User-facing setup is in the
operator guide: Claude Desktop access to Kosmos.
Access is per user: requests authenticate with the same CompanionToken
the LibreOffice companion uses (header X-Kosmos-Token), so a user's
Claude sees exactly what that user can see, and deactivating the user
kills the token.
Where the code is¶
| Module | Holds |
|---|---|
tools/kosmos_notes_mcp.py |
The stdio MCP server Claude Desktop launches; one tool per API call |
apps/drafts/api_auth.py |
kosmos_api_auth, the token decorator that sets request.api_user |
apps/notes/api.py |
Matter list, note manifests, note read and the gated note write, note search |
apps/case/api.py |
A matter's sections as AI-ready text, documents, email threads, conversations, invoices, hybrid search, the three write endpoints |
apps/search/api.py |
Practice-wide search, as the search page does it |
apps/settings/claude/views.py |
Settings → Claude Desktop: the config block, script download, rotate and revoke |
apps/case/ai/context.py |
The section formatters the matter API reuses |
Data model¶
CompanionToken (apps/drafts/models.py) is one row per user
(OneToOneField, CASCADE) with a unique key. There is no separate MCP
token: rotating the key from the Claude Desktop page re-keys the
LibreOffice companion too. Note.ai_write_until is the per-note write
grant (below).
The three pieces¶
- JSON APIs (every view wrapped in
kosmos_api_auth): apps/notes/api.py, undernotes/api/:matters/?q=,notes/?scope=,notes/<id>/,search/?q=&scope=&limit=(default 20 per band, max 100), and the one note write pathnotes/<id>/write/(POST append or replace, gated per note byNote.ai_write_until). Scopes areall | library | matters | matter:<id>, restricted to open matters. Ranking and scope parsing are shared with the Ctrl+K palette so the two surfaces cannot drift.apps/case/api.py, undercase/api/:matter/<id>/<section>/serves the sixteenSECTIONSas AI-ready text (overview, contacts, rates, activity, events, tasks, proceedings, settlement, documents, highlights, timeline, witnesses, emails, conversations, ledger, trust), reusing the in-app chat's formatters fromapps/case/ai/context.pywhere they exist;documents/<id>/,matter/<id>/emails/<thread_id>/,matter/<id>/conversations/<id>/andinvoices/<id>/serve full content;matter/<id>/search/runs the agent's ownsearch_materialshandler so ranking is identical; POSTs tomatter/<id>/facts/,matter/<id>/witnesses/andtasks/create rows through the same validated entry creators the in-app AI's fenced blocks use.apps/search/api.py, undersearch/api/: the practice-wide search (matters of any status, proceedings, contacts, intakes, notes) as manifest lines with handles.- MCP server (
tools/kosmos_notes_mcp.py): a single-file stdio translator on the mcp SDK'sMCPServerthat Claude Desktop launches itself. Tools:find_matter,list_library,list_matter_notes,search_notes,read_note,write_note,read_matter,search_matter,search_kosmos,read_document,read_email_thread,read_conversation,read_invoice,add_fact,add_witness,add_task. Long reads truncate atREAD_TRUNCATE_CHARS(40k) with an explicit marker. All intelligence lives in the APIs; the script only formats, so a port to another runtime stays trivial. - Settings page (Settings → Claude Desktop,
apps/settings/claude/): renders the exactclaude_desktop_config.jsonblock with the user's real token substituted (the.oxtprecedent, generalized), serves the script for download, and offers rotate and revoke.
Access¶
Matters are the user's accessible open matters only, through
filter_matters_for_user(), and every denial of a matter, document,
thread or conversation is a 404 so it does not confirm existence. The
money sections (FINANCIAL_SECTIONS: rates, ledger, trust) and invoice
reads additionally need has_financial_access() (admin or
perm_financial), like the in-app Rates and Ledger tabs; that denial is a
403, since the matter's existence is already known to the caller. The
activity section is open to every user who can see the matter, as the
Activity screens are. Intakes in the practice-wide search need
perm_intakes. Inactive users are refused at the token check. See the
permissions reference.
Write access¶
Three write surfaces, all create-or-append, none destructive:
- Notes:
write_noteappends to (default) or replaces one note's markdown. It works only while that note's AI button in the editor toolbar (the sparkles icon) is switched on: a per-note grant stored as a 24-hour expiry timestamp (Note.ai_write_until), toggled off by a second click and self-expiring otherwise. The grant is per note and note-wide (any user's Claude with access to the note may write while it is on), because the MCP API is stateless token auth: there is no browser session to consult, so the note itself carries the grant. Writes land in the note's history like any other edit, and an open editor detects them through the normal conflict machinery. Claude cannot create or delete notes. The in-app AI's note writes are not gated by this field (see AI chat and context). - Timeline facts and witnesses:
add_factandadd_witnessreuse_create_fact_from_entry()and_create_witness_from_entry()(validation, matter scoping of cited ids, name dedup): exactly what the in-app chat's fenced blocks can do, no more. - Tasks:
add_taskreusescreate_task_from_ai_entry(), assigned to the token's user; the API authorizes the matter itself, because the service resolves by name without an access check, and re-points the task after creation in case a duplicate name resolved elsewhere.
Things that bite¶
- The formatters are shared on purpose. A change to a section
formatter in
apps/case/ai/context.pychanges what both the in-app chat and Claude Desktop see; a new section needs aSECTIONSentry and, if it carries money, a place inFINANCIAL_SECTIONS. - Matter scope differs by API. The notes and case APIs serve open matters only; the practice-wide search lists matters of any status, because it is navigational.
- The script is the shipped artefact. Users download it from the
settings page and run it with
uv; a change to a tool's signature or docstring reaches them only after they re-download. - Email threads list most-recent-activity first (2026-09-25), so the
40k truncation drops the oldest; keep that order when touching
_format_email_threads().
Where the transport and packaging may go next is recorded in Claude Desktop: packaging roadmap.
Related¶
- Claude Desktop access to Kosmos for setup; AI chat and context and Agentic chat for the formatters and the search handler this reuses; Notes and drafts for the editor toggle and the companion token.