---
title: "MCP Tools Reference"
description: "Basic Memory 1.0 MCP tools, parameters, responses, and migration guidance."
canonical_url: "https://docs.basicmemory.com/reference/mcp-tools-reference"
---
# MCP Tools Reference

> Basic Memory 1.0 MCP tools, parameters, responses, and migration guidance.

<note>

Upgrading from v0.24? See [Migrating to 1.0](https://docs.basicmemory.com/reference/migrating-to-1-0) for the changes.

</note>

The twelve core tools share target, routing, pagination, and error conventions. Most default to text; `bm_context` defaults to JSON. Pass `output_format="json"` for machine-readable results.

Pass a project name, `workspace/project`, or the project's external UUID as `project`. Get IDs from `bm_project(command="list")`. A constrained server wins over a supplied project; otherwise use an explicit route when several projects are addressable. Unqualified note/search calls are refused in that case. `bm_list(path="/")` without a project lists project mounts.

## Errors and revision guards

Refusals are MCP tool errors (`isError: true`). In JSON mode their text contains `error`, `message`, and optionally `try_instead`. Codes include `NOT_FOUND`, `AMBIGUOUS`, `ALREADY_EXISTS`, `REVISION_CONFLICT`, and `INVALID_ARGUMENTS`. A missing read, edit, or delete is an error. Only `bm_write` creates notes.

`OUTCOME_UNKNOWN` means a request may have landed: inspect the note before retrying. `PARTIAL_FAILURE` means some items in a directory operation changed. Neither means nothing was written.

Read a note's full `checksum` and pass it as `expected_checksum` to guard a write, edit, move, or delete. On conflict, read again and reconcile. `bm_write(expected_checksum=...)` implies overwrite; explicitly setting `overwrite=false` contradicts it.

```python
note = bm_read(identifier="plans/launch.md", project="main", output_format="json")
bm_write(identifier="plans/launch.md", content="Updated content", project="main",
         expected_checksum=note["checksum"], output_format="json")
```

## Tools

## `bm_read`

### NAME

**bm-read** — read a note or file by path, permalink, memory:// URL or title

### SYNOPSIS

```text
bm_read(identifier, section=None, start_line=None, end_line=None,
        max_tokens=None, include_frontmatter=True, include_content=True,
        output_format="text", project=None)
```

### DESCRIPTION

Returns one note's Markdown, frontmatter included. The identifier can be a file
path (`notes/plan.md`), a permalink, a `memory://` URL, an exact title, an
external id, or a project-qualified path (`research/notes/plan.md`).

Read part of a long note with `section` (a heading), `start_line`/`end_line`,
or `max_tokens`. Line numbers count the whole file, frontmatter included, so
they match `cat -n`. A partial read says which lines it returned and where to
continue.

`include_content=false` returns only the title, paths, checksum and
`updated_at`. The checksum is the `expected_checksum` that bm_write, bm_edit,
bm_move and bm_delete take to guard a change.

Images come back as images and other binary files as documents. A missing note
is a `NOT_FOUND` error; find notes with bm_search.

The shell counterpart is cat(1).

### PARAMETERS

- **identifier** (string, required) — The note or file: a file path ('notes/plan.md'), permalink, memory:// URL, exact title, external id, or '<project>

/path'.

</project>
- **section** (string | null, optional, default: None) — A heading to read: "Decisions", "Auth/Decisions" for a heading under "Auth", or "Decisions<span>

1

</span>

" for the second "Decisions" heading.
- **start_line** (integer | null, optional, default: None)
- **end_line** (integer | null, optional, default: None)
- **max_tokens** (integer | null, optional, default: None) — Approximate token budget. Longer content is cut at a section or paragraph boundary, and the result says which line to continue from.
- **include_frontmatter** (boolean, optional, default: True) — Include the YAML frontmatter in a whole-note read.
- **include_content** (boolean, optional, default: True) — False returns only title, paths, checksum and updated_at.
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_read(identifier="plans/launch-plan.md", project="demo")
# → ---
#   title: launch-plan
#   type: plan
#   permalink: demo/plans/launch-plan
#   ...
#   # Launch Plan
#   ...

bm_read(identifier="plans/launch-plan.md", section="Decisions", project="demo")
# → Lines 15-18 of 21 (document, including frontmatter); next: start_line=19, end_line=21
#   15: ## Decisions
#   16:
#   17: - [decision] one PR per tool group
#   18:

bm_read(identifier="plans/launch-plan.md", include_content=False,
        output_format="json", project="demo")
# → {"title": "launch-plan", "permalink": "demo/plans/launch-plan",
#    "file_path": "plans/launch-plan.md", "checksum": "13e0d14c…",
#    "updated_at": "2026-10-23T14:47:04.550062-05:00"}

bm_read(identifier="demo/plans/launch-plan.md", start_line=1, end_line=4)
# → Lines 1-4 of 21 (document, including frontmatter); next: start_line=5, end_line=8
#   1: ---
#   2: title: launch-plan
#   ...

bm_read(identifier="plans/missing.md", project="demo")
# → error: NOT_FOUND: No note matches 'plans/missing.md' in project 'demo'.
#   Try instead: bm_search(query='plans/missing.md')
```

### GOTCHAS

- With more than one project, an unqualified identifier is refused even when a
default project is set. Pass `project`, or qualify the path (`demo/plans/...`).
- A section read returns numbered lines, like `start_line`/`end_line`; a whole
note comes back without line numbers.

### SEE ALSO

- see_also [[cat(1)]]
- see_also [[head(1)]]
- [bm_search](#bm_search)
- [bm_edit](#bm_edit)
- [bm_context](#bm_context)

## `bm_write`

### NAME

**bm-write** — create or replace a note

### SYNOPSIS

```text
bm_write(content, identifier=None, title=None, directory=None,
         note_type="note", tags=None, metadata=None, overwrite=None,
         expected_checksum=None, output_format="text", project=None)
```

### DESCRIPTION

Writes a whole note. Give the file path as `identifier` (`notes/plan.md`), or a
`title` plus a `directory`. With a path the file lands exactly there.

The title comes from the content's frontmatter `title:`, then the `title`
argument, then the file name. A `# Heading` in the content does not set it.

Writing over an existing note is an `ALREADY_EXISTS` error unless
`overwrite=true`. Pass `expected_checksum` (from bm_read) to replace a note only
if it is still the revision you read. To change part of a note, use bm_edit.

Content may hold frontmatter, observations (`- [category] fact #tag`) and
relations (`- relation_type [[Other Note]]`). Observations and relations are
indexed after the write, so they show up in bm_context and bm_search moments
later.

### PARAMETERS

- **content** (string, required) — The note's Markdown. Saved as given; it may start with frontmatter.
- **identifier** (string | null, optional, default: None) — File path for the note, e.g. 'notes/plan.md'; '.md' is added when missing. '<project>

/path' writes into another project. Use instead of title + directory.

</project>
- **title** (string | null, optional, default: None) — Note title. With `directory`, it also names the file. With `identifier`, it is used when the content has no frontmatter title.
- **directory** (string | null, optional, default: None) — Folder for a note addressed by title, e.g. 'meetings'; '/' is the root.
- **note_type** (string, optional, default: "note") — Frontmatter `type:`. A `type:` in the content wins.
- **tags** (array | string | null, optional, default: None) — Tags, as a list or 'a,b,c'.
- **metadata** (object | null, optional, default: None) — Extra frontmatter fields, merged into the note's frontmatter.
- **overwrite** (boolean | null, optional, default: None) — Replace a note that already exists. Default: the write_note_overwrite_default setting (false unless changed).
- **expected_checksum** (string | null, optional, default: None) — Replace the note only while it is still this revision (the checksum from bm_read). Implies overwrite=true.
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_write(identifier="plans/launch-plan.md", note_type="plan", tags=["release"],
         content="# Launch Plan\n\n- [goal] ship the 1.0 tools #release\n"
                 "- depends_on [[Release Checklist]]\n",
         project="demo")
# → Created plans/launch-plan.md
#   title: launch-plan
#   permalink: demo/plans/launch-plan
#   checksum: 13e0d14c…

bm_write(title="Release Checklist", directory="plans",
         content="# Release Checklist\n\n- [step] tag the release\n", project="demo")
# → Created plans/Release Checklist.md

bm_write(identifier="plans/launch-plan.md", content="# again", project="demo")
# → error: ALREADY_EXISTS: A note already exists at 'plans/launch-plan.md'. Nothing was written.
#   Try instead: bm_edit(identifier='plans/launch-plan.md', command='append', content='...')
#   to add to it, or bm_write(..., overwrite=true) to replace it
```

### GOTCHAS

- A note written by path is titled after the file (`launch-plan`) unless the
content's frontmatter or the `title` argument says otherwise.
- With `title` plus `directory` the file name keeps the title's spelling
(`Release Checklist.md`); its permalink is the slug (`release-checklist`).
- A folder name the model would rewrite (unsupported characters) is refused
rather than silently changed.

### SEE ALSO

- [bm_edit](#bm_edit)
- [bm_read](#bm_read)
- [bm_move](#bm_move)
- [bm_delete](#bm_delete)

## `bm_edit`

### NAME

**bm-edit** — change part of an existing note

### SYNOPSIS

```text
bm_edit(identifier, command, content=None, section=None, find_text=None,
        expected_replacements=1, replace_subsections=True, metadata=None,
        expected_checksum=None, output_format="text", project=None)
```

### DESCRIPTION

Edits a note in place. `command` picks the change:

- `append` / `prepend` — add `content` at the end or after the frontmatter.
- `find_replace` — replace the exact text `find_text` with `content`. It must
occur `expected_replacements` times (default 1).
- `replace_section`, `insert_before_section`, `insert_after_section` — act on the
`section` heading, named as bm_read takes it (`Decisions`, `Auth/Decisions`,
`Decisions[1]`).
- `set_metadata` — merge `metadata` into the frontmatter without touching the
body. `metadata` also merges with any other command.

Pass `expected_checksum` (from bm_read) to edit only the revision you read; a
changed note is a `REVISION_CONFLICT` error carrying the current checksum. A
missing note is an error; create notes with bm_write.

### PARAMETERS

- **identifier** (string, required) — The note or file: a file path ('notes/plan.md'), permalink, memory:// URL, exact title, external id, or '<project>

/path'.

</project>
- **command** (required) — The edit.
- **content** (string | null, optional, default: None) — The text to add or the replacement. Not used by set_metadata.
- **section** (string | null, optional, default: None) — The heading for section commands: "Decisions", "Auth/Decisions" for one under "Auth", or "Decisions<span>

1

</span>

" for the second of two.
- **find_text** (string | null, optional, default: None) — For find_replace: the exact, case-sensitive text to replace.
- **expected_replacements** (integer, optional, default: 1) — For find_replace: how many times find_text must occur.
- **replace_subsections** (boolean, optional, default: True) — For replace_section: true replaces the section's subsections too; false stops at the first subsection heading.
- **metadata** (object | null, optional, default: None) — Frontmatter fields to set; other fields are kept.
- **expected_checksum** (string | null, optional, default: None) — Edit only while the note is still this revision (from bm_read).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_edit(identifier="plans/launch-plan.md", command="find_replace",
        find_text="one PR per tool group", content="one PR per tool", project="demo")
# → Edited plans/launch-plan.md (find_replace)

bm_edit(identifier="plans/launch-plan.md", command="set_metadata",
        metadata={"status": "active"}, project="demo")
# → Edited plans/launch-plan.md (set_metadata)

bm_edit(identifier="plans/launch-plan.md", command="append",
        content="- [risk] the docs lag", project="demo")
# → Edited plans/launch-plan.md (append)

bm_edit(identifier="plans/launch-plan.md", command="append", content="x",
        expected_checksum="stale", project="demo")
# → error: REVISION_CONFLICT: 'plans/launch-plan.md' changed since you read it
#   (current checksum 77a457af…). Nothing was changed.
#   Try instead: bm_read(identifier='plans/launch-plan.md'), then retry with
#   expected_checksum='77a457af…'
```

### GOTCHAS

- `section` belongs to the section commands only; `append` with a `section` is
refused. Use `insert_after_section` to add under a heading.
- `set_metadata` refuses `title` and `permalink`: they are the note's identity.
Rename with bm_move.

### SEE ALSO

- [bm_read](#bm_read)
- [bm_write](#bm_write)
- [bm_move](#bm_move)

## `bm_delete`

### NAME

**bm-delete** — delete a note or a directory

### SYNOPSIS

```text
bm_delete(identifier=None, path=None, recursive=False,
          expected_checksum=None, output_format="text", project=None)
```

### DESCRIPTION

Deletes a note (`identifier`) or a directory (`path`). A directory delete
removes every file under it and needs `recursive=true`; without it the error
says how many entries the directory holds. The project root cannot be deleted.

Pass `expected_checksum` (from bm_read) to delete a note only if it is still
the revision you read. A note that does not exist is an error. A delete cannot
be undone.

### PARAMETERS

- **identifier** (string | null, optional, default: None) — The note to delete: a path, permalink, memory:// URL or exact title.
- **path** (string | null, optional, default: None) — The directory to delete, with everything in it.
- **recursive** (boolean, optional, default: False) — Required true to delete a directory and everything under it.
- **expected_checksum** (string | null, optional, default: None) — Delete the note only while it is still this revision (from bm_read).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_delete(path="old", project="demo")
# → error: INVALID_ARGUMENTS: 'old' is a directory with 1 entries at its top level.
#   Deleting it removes everything under it.
#   Try instead: bm_delete(path='old', recursive=true)

bm_delete(path="old", recursive=True, project="demo")
# → Deleted directory old (1 files)

bm_delete(identifier="plans/launch-plan.md", expected_checksum="stale", project="demo")
# → error: REVISION_CONFLICT: 'plans/launch-plan.md' changed since you read it
#   (current checksum 77a457af…). Nothing was deleted.
```

### SEE ALSO

- [bm_move](#bm_move)
- [bm_read](#bm_read)
- [bm_list](#bm_list)

## `bm_move`

### NAME

**bm-move** — move or rename a note or a directory within its project

### SYNOPSIS

```text
bm_move(destination, identifier=None, path=None, expected_checksum=None,
        output_format="text", project=None)
```

### DESCRIPTION

Moves a note (`identifier`) or a directory (`path`). `destination` is the new
project-relative path; ending it with `/` moves into that folder under the
current name (`archive/`). A destination that names only the project
(`demo/`) is its root.

A note keeps its file extension (`.markdown` may become `.md`). A folder that
differs from an existing one only in case or Unicode form lands in the existing
folder, and the result names it.

Pass `expected_checksum` (from bm_read) to move a note only if it is still the
revision you read. Moving into another project is refused: bm_read the note,
bm_write it there, then bm_delete the original.

### PARAMETERS

- **destination** (string, required) — New path, e.g. 'archive/plan.md'. A trailing '/' ('archive/') moves the note or directory into that folder under its current name.
- **identifier** (string | null, optional, default: None) — The note to move: a path, permalink, memory:// URL or exact title.
- **path** (string | null, optional, default: None) — The directory to move, with everything in it, e.g. 'drafts/2025'.
- **expected_checksum** (string | null, optional, default: None) — Move the note only while it is still this revision (from bm_read).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_move(identifier="plans/release-checklist.md", destination="archive/", project="demo")
# → Moved plans/Release Checklist.md to archive/Release Checklist.md

bm_move(path="archive", destination="old", project="demo")
# → Moved directory archive to old (1 files)
```

### GOTCHAS

- A destination with no file extension is refused for a note; end it with `/`
to move into a folder.
- A directory move is file by file. If some files fail, the error lists which
moved and which did not; bm_list the destination before retrying.

### SEE ALSO

- [bm_delete](#bm_delete)
- [bm_write](#bm_write)
- [bm_list](#bm_list)

## `bm_search`

### NAME

**bm-search** — search a project's notes, ranked by relevance

### SYNOPSIS

```text
bm_search(query=None, mode=None, filters=None, note_types=None, items=None,
          categories=None, since=None, lines=None, max_matches=10,
          identifier=None, path=None, compact=False, valid_at=None,
          valid_overlaps=None, time_kind=None, min_similarity=None,
          projects=None, all_projects=False, page=1, page_size=10,
          output_format="text", project=None)
```

### DESCRIPTION

Searches notes, observations and relations. `query` supports quoted phrases,
AND/OR/NOT and `tag:name`. `mode` picks `hybrid` (the default when semantic
search is available), `text`, `semantic`, `title` or `permalink` (globs with
`*`). A `memory://` URL as the query searches that permalink.

Narrow with `filters` (frontmatter), `note_types`, `items` (notes,
observations, relations), `since` and `path` (a folder). `lines` returns each
hit's matching lines with that many lines of context, like grep; `identifier`
searches one note's lines. `projects` or `all_projects` search several projects
at once, and each hit's path names its project.

Give one scope: `identifier`, `path`, a `memory://` query, `projects` and
`all_projects` each say where to search, and more than one is refused. Results
are paged; read full notes with bm_read.

The shell counterpart is grep(1).

### PARAMETERS

- **query** (string | null, optional, default: None) — What to search for. Omit to search by filters alone.
- **mode** (string | null, optional, default: None) — Search mode.
- **filters** (object | null, optional, default: None) — Frontmatter filters as {field: condition}. Nested fields use dot notation ("schema.confidence"). A condition is a value for equality ({"status": "draft"}), null for absent-or-null ({"owner": null}), a list for contains-all ({"tags": <span>

"a", "b"

</span>

}), or an operator object with $gt, $gte, $lt, $lte, $in, $between, $contains or $exists, e.g. {"priority": {"$in": <span>

"high"

</span>

}}. Several operators on one field AND together.
- **note_types** (array | null, optional, default: None) — Frontmatter `type` values, e.g. <span>

'person', 'meeting'

</span>

.
- **items** (array | null, optional, default: None) — What to return: notes, observations, relations, or a mix. Default: notes.
- **categories** (array | null, optional, default: None) — Observation categories to match exactly, e.g. <span>

'decision'

</span>

.
- **since** (string | null, optional, default: None) — How far back to look: '7d', '24h', '2 days ago', 'last week', or a date. Windows shorter than one day are widened to one day.
- **lines** (integer | null, optional, default: None) — Return each hit's matching lines with this many lines of context (0-10), instead of a match excerpt.
- **max_matches** (integer, optional, default: 10) — Matching lines shown per note with `lines`.
- **identifier** (string | null, optional, default: None) — Search only this note; returns its matching lines.
- **path** (string | null, optional, default: None) — Search only this folder and its subfolders.
- **compact** (boolean, optional, default: False) — Omit note bodies and excerpts; keep identifiers and scores.
- **valid_at** (string | null, optional, default: None) — Specialist: only facts whose authored valid time contains this date.
- **valid_overlaps** (string | null, optional, default: None) — Specialist: only facts whose valid range overlaps '[start,end)'.
- **time_kind** (string | null, optional, default: None) — Specialist: effective, valid, occurred, due or mentioned.
- **min_similarity** (number | null, optional, default: None) — Override the semantic similarity floor (0-1).
- **projects** (array | null, optional, default: None) — Search these projects together.
- **all_projects** (boolean, optional, default: False) — Search every project you can access.
- **page** (integer, optional, default: 1) — Page number.
- **page_size** (integer, optional, default: 10) — Results per page (1-100).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_search(query="freeze", lines=0, mode="text", project="demo")
# → Results for 'freeze' in project 'demo':
#   - plans/launch-plan.md | launch-plan
#       9: - [decision] freeze on Friday

bm_search(query="launch", note_types=["plan"], compact=True, project="demo")
# → Results for 'launch' in project 'demo':
#   - plans/launch-plan.md | launch-plan | score 1.212

bm_search(query="release", mode="text", page_size=1, output_format="json", project="demo")
# → {"results": [{"title": "release-checklist", "file_path": "plans/release-checklist.md",
#    ...}], "current_page": 1, "page_size": 1, "total": 2, "has_more": true}
```

### GOTCHAS

- `max_matches` bounds matching lines; without `lines` or `identifier` it is
refused.
- A search across projects reaches at most result 1000 (`page * page_size`).
- `hybrid` mode also returns notes that are only near in meaning. With `lines`,
use `mode="text"` when you want only notes that contain the words.

### SEE ALSO

- see_also [[grep(1)]]
- [bm_find](#bm_find)
- [bm_read](#bm_read)
- [bm_context](#bm_context)

## `bm_context`

### NAME

**bm-context** — follow the knowledge graph from a note

### SYNOPSIS

```text
bm_context(identifier, depth=1, since="7d", max_related=10, compact=False,
           page=1, page_size=10, output_format="json", project=None)
```

### DESCRIPTION

Returns a note, its observations, and the notes its relations reach, up to
`depth` hops (1-5). `identifier` is a path, permalink, `memory://` URL, or a
pattern such as `plans/*`. `since` limits related results to recent changes and
`max_related` caps them.

`compact` returns the graph without note bodies or observations; read chosen
notes with bm_read. Output defaults to JSON because the result is a graph; text
output names each note's path and ends with the next-page call when there is
one.

### PARAMETERS

- **identifier** (string, required) — Where to start: a note path, permalink, memory:// URL, or a pattern such as 'specs/*'.
- **depth** (integer, optional, default: 1) — Relation hops to follow (1-5).
- **since** (string, optional, default: "7d") — How far back to look: '7d', '24h', '2 days ago', 'last week', or a date. Windows shorter than one day are widened to one day.
- **max_related** (integer, optional, default: 10) — Most related results to return (0-100).
- **compact** (boolean, optional, default: False) — Omit note bodies and observations; keep the graph shape.
- **page** (integer, optional, default: 1) — Page number.
- **page_size** (integer, optional, default: 10) — Primary results per page (1-50).
- **output_format** (optional, default: "json")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_context(identifier="plans/launch-plan.md", compact=True, output_format="text",
           project="demo")
# → # Context: launch-plan
#
#   ## launch-plan
#   permalink: demo/plans/launch-plan
#   path: plans/launch-plan.md
#
#   ### Relations
#   - depends_on [[Release Checklist]]
#
#   ### Related
#   - [[Release Checklist]] (plans/Release Checklist.md)
```

### SEE ALSO

- [bm_read](#bm_read)
- [bm_search](#bm_search)

## `bm_list`

### NAME

**bm-list** — list one folder: its subfolders, then its files

### SYNOPSIS

```text
bm_list(path="/", glob=None, sort=None, page=1, page_size=10,
        output_format="text", project=None)
```

### DESCRIPTION

Lists one level of a folder: subfolders first, then files with their title,
date and id. `bm_list(path="/")` with no project lists the projects you can
address; `<project>/path` lists inside another project, and the paths that come
back carry the same prefix.

Filter files with `glob` and order them with `sort`. Results are paged; the text
output ends with the call for the next page. For a recursive search by name or
frontmatter, use bm_find.

The shell counterpart is ls(1).

### PARAMETERS

- **path** (string, optional, default: "/") — A directory in the project, e.g. 'specs' or 'specs/active'. '/' is the project root; '<project>

/path' addresses another project.

</project>
- **glob** (string | null, optional, default: None) — File-name glob, e.g. '*.md' or '*meeting*'.
- **sort** (string | null, optional, default: None) — File order. Folders always come first.
- **page** (integer, optional, default: 1) — Page number.
- **page_size** (integer, optional, default: 10) — Results per page (1-100).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_list(path="/")
# → Projects:
#   📁 demo                           /demo
#   📁 main                           /main

bm_list(path="plans", project="demo")
# → Contents of 'plans':
#   📄 launch-plan.md         plans/launch-plan.md | launch-plan | 2026-10-23 | id: 686e696d…
#   📄 Release Checklist.md   plans/Release Checklist.md | Release Checklist | 2026-10-23 | id: 7bf40fbe…

bm_list(path="demo/plans", sort="title_asc", output_format="json")
# → {"nodes": [{"name": "launch-plan.md", "file_path": "demo/plans/launch-plan.md",
#    "type": "file", "title": "launch-plan", ...}, ...], "page": 1, "has_more": false}
```

### GOTCHAS

- `glob` and `sort` apply to files in a folder; on the project listing at `/`
they are refused.

### SEE ALSO

- see_also [[ls(1)]]
- [bm_find](#bm_find)
- [bm_read](#bm_read)

## `bm_find`

### NAME

**bm-find** — find files by name or notes by frontmatter, recursively

### SYNOPSIS

```text
bm_find(path="/", name=None, filters=None, fields=None, depth=10, page=1,
        page_size=10, output_format="text", project=None)
```

### DESCRIPTION

Searches a folder and everything under it. With `name`, matches file names
against a glob (`*meeting*`). With `filters`, matches notes by frontmatter,
e.g. `{"status": "active"}` or `{"priority": {"$in": ["high"]}}`; `fields`
returns just those frontmatter values per note. `path` limits the search to a
folder, and `<project>/` searches another project.

For ranked text or meaning search, use bm_search.

The shell counterpart is find(1).

### PARAMETERS

- **path** (string, optional, default: "/") — A directory in the project, e.g. 'specs' or 'specs/active'. '/' is the project root; '<project>

/path' addresses another project.

</project>
- **name** (string | null, optional, default: None) — File-name glob, e.g. '*.md'. Not with `filters`.
- **filters** (object | null, optional, default: None) — Frontmatter filters as {field: condition}. Nested fields use dot notation ("schema.confidence"). A condition is a value for equality ({"status": "draft"}), null for absent-or-null ({"owner": null}), a list for contains-all ({"tags": <span>

"a", "b"

</span>

}), or an operator object with $gt, $gte, $lt, $lte, $in, $between, $contains or $exists, e.g. {"priority": {"$in": <span>

"high"

</span>

}}. Several operators on one field AND together.
- **fields** (array | null, optional, default: None) — Frontmatter fields to return per note (dot paths allowed), e.g. <span>

"title", "priority"

</span>

. Needs `filters`.
- **depth** (integer, optional, default: 10) — Folder levels to search (1-10).
- **page** (integer, optional, default: 1) — Page number.
- **page_size** (integer, optional, default: 10) — Results per page (1-100).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_find(path="demo/", name="*plan*")
# → Files under 'demo/' matching '*plan*':
#   📁 plans                          /demo/plans
#   📄 launch-plan.md                 demo/plans/launch-plan.md | launch-plan | …

bm_find(path="demo/", filters={"status": "active"}, fields=["status"])
# → 1 notes under 'demo/' match:
#   - demo/plans/launch-plan.md | launch-plan | status='active'
```

### GOTCHAS

- A `name` glob matches folders as well as files, so `*plan*` also returns the
`plans` folder.

### SEE ALSO

- see_also [[find(1)]]
- [bm_list](#bm_list)
- [bm_search](#bm_search)

## `bm_project`

### NAME

**bm-project** — list, create, and show recent activity in projects

### SYNOPSIS

```text
bm_project(command, name=None, path=None, set_default=False, workspace=None,
           since="7d", items=None, page=1, page_size=10,
           output_format="text", project=None)
```

### DESCRIPTION

`command` picks what to do:

- `list` — the projects you can address, grouped by workspace.
- `create` — add a project: `name`, `path`, optional `set_default` and
`workspace`.
- `recent` — what changed lately: `since`, `items`, paging. `project` picks the
project, else the default project; with no default, every project. JSON
output carries `page`, `page_size` and `has_more`, plus `guidance` for any
project that was never indexed.

bm_project never deletes; bm_project_delete does, and only when the server
enables it.

The shell counterpart for `recent` is tail(1).

### PARAMETERS

- **command** (required) — What to do.
- **name** (string | null, optional, default: None) — For create: the new project's name.
- **path** (string | null, optional, default: None) — For create: the folder that holds the project's notes.
- **set_default** (boolean, optional, default: False) — For create: make the new project the default.
- **workspace** (string | null, optional, default: None) — In Cloud: the workspace slug, name or id that holds the project.
- **since** (string, optional, default: "7d") — How far back to look: '7d', '24h', '2 days ago', 'last week', or a date. Windows shorter than one day are widened to one day.
- **items** (array | null, optional, default: None) — What to return: notes, observations, relations, or a mix. Default: notes.
- **page** (integer, optional, default: 1) — Page number.
- **page_size** (integer, optional, default: 10) — Results per page (1-100).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_project(command="create", name="demo", path="/Users/me/demo", set_default=True)
# → ✓ Project 'demo' added successfully
#   ...
#   Project is now available for use in tool calls; indexing completed.

bm_project(command="list")
# → Available projects:
#   - demo (local) [b2a21302…]
#   - main (local) [9b4d5944…]

bm_project(command="recent", since="1d")
# → ## Recent Activity: demo (1d)
#   **📄 Recent Notes & Documents (1):**
#     • launch-plan (plans) [id: 686e696d…]
```

### SEE ALSO

- see_also [[tail(1)]]
- see_also [[bm-project-delete(3)]]
- [bm_list](#bm_list)

## `bm_schema`

### NAME

**bm-schema** — validate notes against schemas, infer schemas, find drift

### SYNOPSIS

```text
bm_schema(command, note_type=None, identifier=None, threshold=0.25,
          output_format="text", project=None)
```

### DESCRIPTION

Works with Picoschema note schemas. A schema is a note with `type: schema`
whose `entity` field names the note type it describes. `command` picks what to
do:

- `validate` — check notes against their schema: one `note_type`, one note by
`identifier`, or every type that has a schema.
- `infer` — suggest a schema from the notes of a `note_type`; `threshold` is
the share of notes a field must appear in.
- `diff` — compare a `note_type`'s notes with its schema to find drift.

A type with no schema, or no notes, is a `NOT_FOUND` error that says how to
create one.

### PARAMETERS

- **command** (required) — What to do.
- **note_type** (string | null, optional, default: None) — The frontmatter `type:` to check. Required for infer and diff.
- **identifier** (string | null, optional, default: None) — For validate: one note, instead of a whole note type.
- **threshold** (number, optional, default: 0.25) — For infer: the share of notes a field must appear in to be suggested.
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With several projects, pass an explicit scope or a project-qualified target.

### EXAMPLES

```text
bm_schema(command="infer", note_type="plan", project="demo")
# → # Schema Inference: plan
#   Notes analyzed: 1
#   ...
#   - **goal** (observation) — 100% (1/1) [required]

bm_schema(command="validate", note_type="plan", project="demo")
# → # Schema Validation: plan
#   Notes: 1 | Valid: 1 | Warnings: 1 | Errors: 0
#   - **launch-plan** — valid
#     - warning: Missing required field: owner (expected [owner] observation)

bm_schema(command="diff", note_type="plan", project="demo")
# → # Schema Drift: plan
#   ## New Fields (in notes but not in schema)
#   - **decision** (observation) — 100% (1/1)
```

### SEE ALSO

- [bm_find](#bm_find)
- [bm_write](#bm_write)

## `bm_man`

### NAME

**bm-man** — read the Basic Memory manual

### SYNOPSIS

```text
bm_man(page=None, query=None, output_format="text")
```

### DESCRIPTION

Reads the manual bundled with Basic Memory, so it works with no project and no
network. With no arguments, the index of every page. `page` reads one page:
`bm-search(3)` for an MCP tool, `grep(1)` for a shell command; a tool name such
as `bm_search` works too. `query` lists the pages that mention every word given,
like `man -k`; pages whose name or summary match come first.

Section 3 documents the MCP tools, section 1 the `bm` shell commands, and
section 7 the concepts. The index marks pages for tools this server does not
register.

The shell counterparts are `bm man` and apropos(1).

### PARAMETERS

- **page** (string | null, optional, default: None) — The page to read, e.g. 'bm-search(3)', 'grep(1)' or 'bm_search'.
- **query** (string | null, optional, default: None) — Words to find in the manual; lists the pages that mention them all.
- **output_format** (optional, default: "text")

### EXAMPLES

```text
bm_man(page="bm_search")
# → # bm-search(3)
#   ## NAME
#   **bm-search** — search a project's notes, ranked by relevance
#   ...

bm_man(query="frontmatter filters")
# → Manual pages for 'frontmatter filters':
#   - apropos(1) — search the Basic Memory manual
#   - bm-find(3) — find files by name or notes by frontmatter, recursively
#   - bm-man(3) — read the Basic Memory manual
#   - bm-search(3) — search a project's notes, ranked by relevance
#   - search-notes(3) — search the knowledge base by text, similarity, or metadata

bm_man(page="nope(3)")
# → error: NOT_FOUND: No manual page 'nope(3)'.
#   Try instead: bm_man(query='nope(3)'), or bm_man() for the index
```

### GOTCHAS

- `topic`, `name`, `command` and `tool` are accepted as spellings of `page`.

### SEE ALSO

- see_also [[apropos(1)]]

## Resources and hosted extensions

`memory://man` lists the built-in manual; `memory://man/bm-read(3)` reads one page. `bm man bm-read` reads the same page in the terminal. `memory://<project>/<path>` reads a note resource, and `memory://ai_assistant_guide` provides usage guidance.

Cloud-only tools such as `review_note` and `open_basic_memory` are separate extensions. See [Comments and Suggestions](https://docs.basicmemory.com/cloud/comments-and-suggestions) and [Pocketbook](https://docs.basicmemory.com/cloud/mcp-app) for their hosted contracts. Their rollout is independent of the core 1.0 tools. ChatGPT's `search` and `fetch` compatibility tools remain client-specific wrappers.

See also [AI Assistant Guide](https://docs.basicmemory.com/reference/ai-assistant-guide), [CLI Reference](https://docs.basicmemory.com/reference/cli-reference), and [Metadata Search](https://docs.basicmemory.com/concepts/metadata-search).


## Sitemap

See the full [sitemap](https://docs.basicmemory.com/sitemap.md) for all pages.
