loci-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| LOCI_VAULT_ROOT | No | Path to the vault the MCP server should operate on. Set this to use a specific vault for one run (e.g. LOCI_VAULT_ROOT=~/vault loci). If unset, the vault already configured in the app is used. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| generate_uidA | Generate a random 8-character hex UID for a new vault item. |
| set_vault_rootA | Point Loci at the user's vault directory. Returns {vault_root, created,
configured_at}, plus Call this when a tool has failed with LOCI_NOT_CONFIGURED, or when the user asks to move Loci to a different vault. It records the path, creates and seeds the directory, and takes effect immediately — no restart, and the call that failed can simply be retried. Ask the user for the path first. Never guess one and never offer a default:
the vault holds everything they save, and a path they did not choose is a
vault they will never find again. If they are unsure, ask whether they keep
notes in a synced folder (Dropbox, Nextcloud, iCloud) and suggest a Args: path: Absolute path to the vault directory (~ is expanded). create: Create and seed the directory. Pass False to adopt a vault that already exists and nothing else: any other path — missing, or there but not a vault — fails without writing anything, so you can confirm it with the user first. Fails when the deployment sets LOCI_VAULT_ROOT: that is a managed install where the operator fixed the path, and it cannot be changed from chat. |
| check_duplicateA | Check whether a similar vault item already exists. Returns {"duplicate": false} or {"duplicate": true, "path": "...", "name": "..."}. write_vault runs this check itself and refuses on a hit, so a separate call is only needed to look before building the node — e.g. to decide between a new save and an update before drafting content. |
| write_vaultA | Write a new item to the Loci vault. Returns {uid, path, type, title}, plus tag_warnings when a tag names a project/entity/topic (belongs as a relation) or repeats the node's own type or subtype. The write has already happened; surface the warning and offer the fix. Checks for duplicates itself: if a node with the same title or the same extra_fields.source_url already exists, nothing is written and the result is {duplicate: true, uid, path, name, message} describing the existing node. Ask the user whether to update that node (patch_vault) or save a new one anyway, and in the latter case call again with allow_duplicate=true. Args: type: document | article | bookmark | conversation | project | task | event | entity | procedure | skill | topic | asset | log title: Concise descriptive title body: Main content body (markdown) tags: 3-6 freeform lowercase hyphenated tags domains: One or more from: technology-engineering, science-research, business-strategy, finance-economics, health-biology, philosophy-ethics, creativity-design, personal-development, current-events-society summary: One-paragraph summary — what this is, why it matters, key insight author: "self" for user-produced content; entity name/UID for external relations: List of {uid: str, type: str} dicts references: List of {url: str, title: str, source_type: str} dicts extra_fields: Type-specific frontmatter fields. Do NOT put the body here — 'body' is not a frontmatter field. A 'body' key in extra_fields is rerouted to the body argument and a warning is returned. A 'uid' key (from generate_uid) is honoured and reported back; one another node already holds is refused with {errors: [...]}. Omit it to have one generated. allow_duplicate: Write even though a similar node exists. Only after the user has chosen "save as new". |
| delete_vaultA | Delete a vault item permanently. Args: path: 8-character hex UID (e.g. "0b74b77c"), absolute path, or path relative to vault_root (e.g. "notes/my-note.md") Always confirm with the user before calling this. Deletion is irreversible. |
| patch_vaultA | Patch frontmatter fields on an existing vault item. updated_at is always bumped. Use this to correct or extend an existing item — e.g. add/remove relations, update tags, change status — without rewriting the whole file. Workflow:
Args: path: 8-character hex UID (e.g. "0b74b77c"), absolute path, or path relative to vault_root (e.g. "notes/my-note.md"). Prefer the UID when you have it — no lookup call is needed. updates: Dict of frontmatter fields to overwrite. Pass complete new values. Common uses: relations: [{uid: "a1b2c3d4", type: "belongs-to"}, ...] tags: ["tag-a", "tag-b"] status: "done" summary: "Updated summary." Do NOT put the body here — 'body' is not a frontmatter field. A 'body' key in updates is rerouted to the body argument and a warning is returned; a null value there clears the body. Any stale 'body' key already in the file's frontmatter is stripped on every patch. body: If provided, replaces the full body text. Always pass new body text here, never inside updates. Pass "" to clear it. Returns {uid, path, type, title, ...}, plus tag_warnings when a tag names a project/entity/topic (belongs as a relation) or repeats the node's own type or subtype. The patch has already happened; surface the warning and offer the fix. Other warnings, all of which also arrive after the write: relation_warnings (unknown relation type), has_asset_warnings (unknown scheme or a missing scheme field in has_asset), and field_warnings (a field the patch touched now breaks its type's required fields or enum; a change to type or subtype checks the whole node). |
| read_vaultA | Read a vault file. Accepts an 8-character hex UID (e.g. "0b74b77c"), an absolute path, or a path relative to vault_root (e.g. "my-note--0b74b77c.md"). Prefer passing the UID when you have it — the path will be resolved automatically. With commit (a hash from history_vault), returns the file as it was right
after that commit — also for a node that has since been changed or deleted.
For a deleted node, pass the hash of the commit that deleted it with " |
| history_vaultA | List the commits that changed a node, newest first: {commits: [{commit, author, date, subject}]}. Every vault write is one commit. Accepts a UID, absolute path or vault-relative path. A UID also works for a node that was deleted: history finds the file it last lived in. Use this to find what happened to a node — for example when content is missing after a write — then diff_vault to see the change and restore_vault to undo it. |
| diff_vaultA | Show the change one commit made to a node, as a unified diff: lines starting with "-" were removed, "+" were added. Get the commit hash from history_vault. Returns {diff} or {error}. |
| restore_vaultA | Put a node back as it was right after You may call this without asking the user first: a restore is recorded as a new commit, nothing is lost, and it can itself be undone. But:
To restore a deleted node, pass its UID and the hash of the commit that
deleted it with " |
| undo_last_vault_changeA | Undo the newest vault commit: a node it created is removed, a node it changed or deleted is put back as before. Recorded as a new commit, so calling it again undoes the undo. Use it right after a write that went wrong. If other writes came after the
mistake, use history_vault and restore_vault on the one node instead.
Pass |
| list_vaultA | List items in a vault type folder. Omit type to list all folders. Returns {type_name: [filename, ...], ...}. If meta files were auto-updated at startup, result includes a "_notice" key. |
| read_metaA | Read a vault meta file. file: type-dictionary | type-decision | tag-dictionary | field-dictionary | relation-dictionary | relation-dictionary-user | foreign-definitions | ontology | ontology-user | scheme-dictionary | scheme-dictionary-user | ontology-guide | has-asset-guide | update-workflow | config type-decision — the decision tree for picking a type when the content does not name one. Read with type-dictionary when classifying. ontology — machine-readable canonical type definitions (required fields, optional fields, enum values). Source of truth for structure. ontology-user — user-defined type extensions. Read before writing a node of a user-defined type to confirm the type is registered. relation-dictionary — machine-readable canonical relation types (name, group, description, inverse). Source of truth for relation types. relation-dictionary-user — user-defined relation additions. Read to include custom relation types alongside canonical ones. field-dictionary — behavioral guidance: field descriptions, complex object schemas, archiving patterns, temporal field rules (LLM-readable). scheme-dictionary — canonical location schemes for has_asset fields (scheme names, required/optional fields, capabilities). Read before constructing any has_asset entry to confirm valid schemes and required fields. scheme-dictionary-user — user-defined location scheme additions. ontology-guide — the user's prose guide to the ontology: what each type is for, how relations and schemes work, how to extend them. Read this when the user asks how the ontology works rather than what a specific type requires — for structure, read ontology instead. has-asset-guide — working guidance for has_asset: when to use each scheme, where its identifiers come from, adopt vs. reference. Read before writing or editing a has_asset entry. update-workflow — the procedure for changing an existing node: patch semantics, temporal-field archiving for entities, archive/unarchive, delete. Read when the user wants to update rather than save. config — where Loci is pointed and which version it runs. Answerable with no vault configured, so it also tells you why the other tools are refusing. |
| fetch_url_metadataA | Lightly fetch a URL and extract title, site name, and description. Use this for bookmark saves — does not return full page content. If the user wants full content extracted, use the article type instead. |
| scan_directoryA | Scan a directory for files and identify which already have vault nodes. For each file found, checks whether any vault node has a has_asset location with scheme: local pointing to that file (by resolved absolute path). Returns: { directory: str, total_files: int, unregistered: [{path, filename, format, suggested_type, suggested_subtype, suggested_title}, ...], registered: [{path, filename, format, node_uid}, ...], } suggested_type is Args: path: Absolute path to the directory to scan. recursive: Scan subdirectories recursively (default false). |
| read_skillA | Read a Loci skill: the packaged procedure with the user's own additions appended. Follow the returned document for the whole interaction. skill: loci-save | loci-query | loci-inbox | loci-ingest-directory Answers without a vault too — the procedure is what tells the model to ask for one — but the additions only load once a vault is configured. |
| write_skillA | Write the user's additions to a Loci skill — the "Your additions" section read_skill returns, kept in vault/skills/.md. skill: loci-save | loci-query | loci-inbox | loci-ingest-directory Replaces the whole file unless append=True, so read the current additions (the tail of read_skill) before rewriting them. The packaged procedure is not writable: it ships inside Loci and is replaced on upgrade, while the additions live in the vault and survive one. Nothing to reinstall — the next read_skill call returns the change. |
| validate_typeA | Validate a proposed user-defined type before writing it to ontology-user.yaml. Returns {"valid": bool, "errors": [...], "resolved_fields": {...}}. Args: name: Proposed type name — must not collide with any canonical type. extends: A canonical type to inherit from (e.g. "procedure", "task"). fields: Optional list of additional fields: [{name, type, values?}]. 'type' is one of: string | array | datetime | enum | boolean. For enum fields, include 'values': [...]. |
| define_typeA | Define a new user type and append it to ontology-user.yaml. Returns {"defined": true, "name": str, "extends": str} on success, or {"defined": false, "errors": [...]} if validation fails. Rules enforced before writing:
Args: name: New type name (lowercase, hyphenated) extends: Canonical root parent type (document, article, task, procedure, etc.) description: Short description of what this type represents fields: Optional extra fields: [{name, type, values?}]. Omit 'values' for non-enum fields. |
| define_relationA | Define a new user relation and append it to relation-dictionary-user.yaml. Returns {"defined": true, "name": str, "warnings": [...]} on success, or {"defined": false, "errors": [...], "warnings": [...]} if validation fails. Rules enforced before writing:
If inverse_of is provided and names an unknown relation, a warning is returned (not an error) — the inverse may be defined in a subsequent call. Every relation needs an inverse or must be symmetric, or lint_vault warns. If inverse_of names a user relation with no inverse, that relation is updated to name this one back. A relation cannot be both symmetric and have inverse_of. Args: name: New relation name (lowercase, hyphenated) description: What this relation means; include directionality in the wording group: Optional group label (e.g. "entity", "semantic", "custom") inverse_of: Optional name of the inverse relation symmetric: True when the relation is its own inverse (A relates B means B relates A) |
| lint_vaultA | Lint all vault files against the Loci schema. Returns errors (schema violations, unknown types, bad uids, duplicate uids) and warnings (dangling relations, orphaned nodes, empty summaries; a tag that names a project/entity/topic node and should be a relation; a tag that repeats the node's own type or subtype; vault-wide near-duplicate tag spellings). stats.tags carries the tag census: distinct count, uses, the tags used only once, and the near-duplicate groups. Run this after manual vault edits or when debugging unexpected behavior. |
| write_metaA | Overwrite a vault meta file. file: tag-dictionary | foreign-definitions | ontology-user ontology-user — write updated YAML to register a new user-defined type. Read the current content first with read_meta(file="ontology-user"), append the new type entry, then write the full updated content here. |
| query_neighborsA | Return the direct neighbors of a vault node — one hop only. Includes both outgoing and incoming edges with their relation type and direction. Each entry: {uid, title, type, rel_type, direction} where direction is 'in' or 'out'. |
| query_relatedB | Return all vault nodes within |
| query_pathB | Return the shortest path between two vault nodes as an ordered list. Each entry: {uid, title, type}. Returns an empty list if no path exists. |
| find_nodesA | Find vault nodes matching any combination of filters. At least one should be provided. Each entry: {uid, title, type, subtype, path, state, status, due_date, language}. Args: type: Node type (task, document, project, article, asset, …) tag: Tag value — node must include this tag status: Exact status match (todo, done, active, …) subtype: Exact subtype match — task: bug|feature|question|chore; document: meeting-notes|report|spec|plan|transcript|essay|journal|note; article: the medium (book, paper, …); entity: person|organization|place; asset: book|video|music|podcast|presentation|visual|physical state: Lifecycle state filter — "active" (default) excludes archived nodes; pass "archived" to see only archived nodes, or None to see all. due_before: ISO date (YYYY-MM-DD) — nodes whose due_date <= this value due_after: ISO date (YYYY-MM-DD) — nodes whose due_date >= this value created_after: ISO date — nodes created on or after this date updated_after: ISO date — nodes updated on or after this date related_to: UID — only return nodes connected to this node by any edge language: Language code (en, ru, de, …) has_asset: True to return only nodes with an asset; False for nodes without has_asset_scheme: scheme name (e.g. "google-drive") — return only nodes with a has_asset location using that scheme (case-insensitive) |
| search_vaultA | Search vault nodes by title and summary text (case-insensitive substring match). Use this to find a node when you know a keyword but not the exact title or uid. Each entry: {uid, title, type, path, summary}. Args: query: Keyword to search for (case-insensitive substring match) state: Lifecycle state filter — "active" (default) excludes archived nodes; pass "archived" to search only archived nodes, or None to search all. |
| resolve_mentionsA | Match names mentioned in content — people, organisations, projects, topics — against existing vault nodes, so a save links to what is already there instead of creating a second "John Smith". Pull every proper name out of the content first, then pass them all in one
call. Each name is compared against node titles and aliases, case- and
whitespace-insensitive. Returns:
{
found: [{name, uid, title, type, path, summary, via}, ...],
missing: [name, ...],
}
Args: names: Names to resolve, as they appear in the content. types: Optional list of node types to search (e.g. ["entity", "project"]). Omit to search every active node. |
| resolve_assetA | Resolve an asset UID to its file path and metadata. Returns {path, mime_type, format, size_bytes}. Use this when you need the physical file location for a vault node with has_asset: true. |
| query_recentA | Return the most recently updated vault nodes. Use for "what have I been thinking about lately?" queries. Each entry: {uid, title, type, path, updated_at, status}. Args: days: Look-back window in days (default 30) limit: Maximum number of results to return (default 20) state: Lifecycle state filter — "active" (default) excludes archived nodes; pass "archived" to see only archived nodes, or None to see all. |
| query_insightsA | Surface vault nodes that bridge different domains. Finds nodes connected to items from domains outside their own — these are the unexpected cross-domain links that reveal non-obvious patterns. Each entry: {uid, title, type, domains, connects_to_domains, degree}. Args: limit: Maximum number of bridge nodes to return (default 10) |
| find_unregistered_assetsA | Find files in assets/ that have no registry entry. Returns a list of relative paths (e.g. ["assets/abc123.pdf"]). Use this to surface ghost files that were dropped into assets/ without going through save_asset. |
| save_assetA | Save a new binary/rich-file asset with its vault proxy node. Executes the full crash-safe write sequence. Returns {uid, asset_path, vault_path}, plus tag_warnings when a tag names a project/entity/topic or repeats the node's own type or subtype — the same warning write_vault returns. Checks for duplicates itself, like write_vault: if a node with the same title already exists it writes nothing and returns {duplicate: true, uid, path, name, message} describing the existing node. Ask the user whether to update that node or save a second copy anyway, and in the latter case call again with allow_duplicate=true. Provide either source_path OR content — not both. Choosing the right mode:
Args: source_path: Absolute path to a file on disk. content: File content as a UTF-8 string (text) or base64-encoded string (binary). Use is_base64=True for binary content. is_base64: Set True when content is base64-encoded binary data. format: File extension: html, pdf, png, mp4, mp3, mov, etc. mime_type: MIME type: text/html, application/pdf, video/mp4, etc. title: Title for the vault proxy node. summary: One-paragraph summary. tags: 3-6 freeform lowercase hyphenated tags. domains: One or more domain taxonomy values. relations: List of {uid, type} dicts linking to other vault nodes. node_type: Vault node type: 'document' or 'asset'. subtype: asset → 'book', 'video', 'music', 'podcast', 'presentation', 'visual', 'physical'. document → 'report', 'transcript', 'spec', 'plan', 'meeting-notes', 'essay', 'journal', or 'note' (the default when omitted). author: Author or creator name. published_at: Publication or release date string. language: ISO 639-1 language code. allow_duplicate: Write even though a node with this title exists. Only after the user has chosen "save as new". |
| scan_inboxA | Phase 1 of the inbox workflow: scan loci-stage/, classify all files, and write .inbox-plan.yaml. Read-only — no files are moved or vault nodes created. Returns a summary: {status, total_files, total_content, runs_estimated, plan_path}. Call this before approve_inbox_plan(). If a plan already exists with pending items, this overwrites it. |
| get_inbox_planA | Return the current inbox plan and progress. Returns {status: 'no-plan'} if no plan exists. Includes: status, budget_per_run, progress, next items in each category. |
| approve_inbox_planA | Approve the pending inbox plan to enable execution. Must be called after scan_inbox() and before execute_inbox_file_batch() or get_inbox_content_batch(). Returns the approved plan summary. |
| execute_inbox_file_batchA | Execute the next batch of file-category items from the approved plan. Processes up to budget_per_run.files items. Each item: reads file from loci-stage/, saves as an asset vault node (type=asset, subtype=book), deletes the staging file, updates plan status. An item whose title already exists in the vault is not saved: it is parked
with status Returns {processed: [{path, uid, title, tag_warnings?}], failed: [{path, error}], duplicates: [{path, title, existing_uid, existing_path, existing_name}], remaining: int}. Plan must be approved before calling. |
| get_inbox_content_batchA | Return the next batch of content-category items for Claude to process. Includes file content for text files. Claude should classify, summarize, and call loci__write_vault for each, then call update_inbox_item() to mark each as done. Returns {items: [{path, type, content, ...}], remaining: int}. Plan must be approved before calling. |
| update_inbox_itemA | Update a plan item's status. Content items: call after writing a vault node for the item.
File items parked as Args: path: Relative path of the item within loci-stage (as it appears in the plan) status: 'done' | 'failed' | 'skipped' | 'pending' uid: UID of the created vault node (required when status='done') allow_duplicate: when true, the file batch saves this item even if its title already exists in the vault |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 39 tools
Most tools target a distinct resource+action (write/read/patch/delete/search on vault nodes, separate versioning, inbox, ontology, and asset tools). A few boundaries blur: search_vault vs find_nodes vs resolve_mentions all retrieve nodes, and define_type competes with write_meta(file="ontology-user") which is also described as the way to register a user type. Descriptions do help sort these out, but the registration overlap is a real source of misselection.
Nearly everything is snake_case verb_noun (write_vault, read_meta, scan_inbox, define_relation, execute_inbox_file_batch). The only wrinkle is inconsistent retrieval verbs — query_*, find_*, search_vault, get_inbox_plan, list_vault, read_vault — so similar read operations don't share a prefix. Still very readable and predictable overall.
39 tools is heavy for a single server and sits in the top band of the rubric. The domain is genuinely broad (vault CRUD, git-style history, ontology extension, assets, staged inbox, skills), so most tools earn their place, but several could be folded together (find_nodes/search_vault, define_type/write_meta).
Coverage is unusually thorough: full node lifecycle plus versioning/restore, filtered and keyword retrieval, graph queries, ontology/relation extension, asset registration, and a staged-inbox workflow. Minor gaps remain — search_vault only matches title and summary (no body full-text search), and there is no rename/move or delete operation for user-defined types/relations beyond overwriting ontology-user.