Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
LOCI_VAULT_ROOTNoPath 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

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
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 notice when adopting the vault moved files the user owns — pass it on to them verbatim.

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 vault directory inside it — but let them confirm the full path.

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:

  1. Call read_vault(path) to get the current content.

  2. Parse the fields you want to change and construct their new values in full (e.g. the complete new relations list, not just the items to add).

  3. Call patch_vault(path, updates={...}) with only the fields that changed.

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 "1" added (e.g. "a1b2c3d1") to get the last version before the deletion.

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 commit. Use it to repair a node that a write damaged, or to bring back a deleted node.

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:

  1. Read the old version first (read_vault with commit) and check it holds the content you want back.

  2. If the node was changed after the damage, a restore also drops those newer changes. Ask the user first, or use read_vault with commit and patch_vault to put back only the missing part.

  3. Tell the user what you restored: node, commit and why.

To restore a deleted node, pass its UID and the hash of the commit that deleted it with "1" added (e.g. "a1b2c3d1"). Returns {restored, uid, path, from_commit, was_deleted, commit}.

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 commit (the hash the bad write returned, at least 7 characters) to undo only that change: if a newer one has landed since, nothing is undone and an error is returned. Tell the user what you undid. Returns {undone, undone_commit, files, commit}.

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 asset (with suggested_subtype book) for ebook formats and document otherwise; suggested_title is the humanized filename. Both come from the filename alone — confirm with the user when they look wrong.

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:

  • name must not collide with any canonical or existing user type

  • extends must name a canonical root type (not a user type, and not skill, which extends procedure)

  • all user-defined fields are optional; inherited fields cannot be redefined

  • field type must be one of: string | array | datetime | enum | boolean

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:

  • name must not collide with any canonical or existing user relation

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 depth hops of the given uid. Results include distance from the origin node, sorted nearest-first. Each entry: {uid, title, type, distance, summary}.

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, ...], } via is "title" or "alias" for an exact match, or "partial" when the name only appears as a whole word inside a title or alias (e.g. "Loci" against "Loci — knowledge vault") — treat partial hits as candidates to confirm, not as matches. A name listed more than once in found matched several nodes; ask the user which one they meant.

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:

  • Use source_path whenever the file exists on disk. The server reads it directly; no file data passes through MCP. Works for any file size.

  • Use content only for material already present in the conversation context: text that Claude just generated, or base64 data returned by another tool in this same session. Never base64-encode a file yourself just to pass it here — use source_path instead.

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 duplicate, its staging file kept, and reported under duplicates with the existing node's uid and path. The plan stays open until each is resolved with update_inbox_item: status="skipped" to drop it, or status="pending" with allow_duplicate=True to save a second copy on the next batch.

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 duplicate by execute_inbox_file_batch: pass status="skipped" to drop the item, or status="pending" with allow_duplicate=True to re-queue it so the next file batch saves a second copy. The plan is not done while an item is still parked.

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

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A3.8/5.0

Scored across 39 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count3/5

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

Completeness4/5

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.

Maintenance

ActivityActive
ResponsivenessSyncing