Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Read the relevant reference doc when working in that area:

- `docs/dev/SPECIFICATION.md` — system boundaries, design constraints, safety gates, hybrid read/write rationale. Read before architectural changes.
- `docs/dev/SECURITY.md` — trust model and defenses.
- @docs/dev/MCP_STANDARDS.md — tool description vs schema separation, mutation response metadata rule, examples. Read when adding or modifying MCP tools.
- @docs/dev/MCP_STANDARDS.md — this project's standards for designing our MCP server's tool surface (tool description vs schema separation, mutation response conventions, examples). Follow when adding or modifying MCP tools.
- @docs/dev/CODE_STYLE.md — style choices not auto-enforced; the WHY-not-WHAT comments rule.

## Core Workflows
Expand Down
31 changes: 18 additions & 13 deletions docs/dev/MCP_STANDARDS.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
# MCP Standards

Conventions for the MCP server's tool surface — how to write tool descriptions, schemas, and mutation responses so they work well with LLM clients. Read this when adding a new tool or modifying an existing one.
This project's conventions for designing our MCP server's tool surface — how we write tool descriptions, schemas, and mutation responses so they work well with LLM clients. Project-wide rules; they apply to every tool we add or modify. Read when adding a new tool or touching an existing one.

Specific instantiation details (which fields a particular mutation response carries, which underlying system tokens it threads, which exceptions Bear's URL-API quirks force) live in `SPECIFICATION.md`.

This doc is focused on the conventions themselves, so they remain easy to apply consistently across new and existing tools; it deliberately doesn't name specific tools, DB columns, or helper functions, because those belong with the implementations they describe.

## Separation of Concerns

Tool descriptions help with tool **selection** and understanding; schema (`describe()`) text guides correct **invocation**. Don't conflate the two — a description full of parameter syntax is hard for the LLM to scan when choosing among tools, and a schema description missing constraint details forces the LLM to guess.

## LLM-First Design

Tools are first discovered via descriptions, then invoked via schemas. Optimize both for the consumer (an LLM), not for human readability. The reference implementations are `src/tools/note-tools.ts` and `src/tools/tag-tools.ts` — mirror their patterns when adding new tools.

## Mutation Response Metadata

Every note-level mutation tool — `bear-create-note`, `bear-add-text`, `bear-replace-text`, `bear-add-file`, `bear-add-tag`, `bear-archive-note` — must return **note ID + note title + what changed** in its response. Both values are always available without post-write database reads:

- For modifications: ID comes from the input parameter, title from the pre-flight `getNoteContent()` validation
- For creation: title comes from the input parameter, ID from post-create polling

Global tag mutations (`bear-rename-tag`, `bear-delete-tag`) are not note-level and intentionally omit note metadata.

Never fetch tags or other metadata from the database after a write — Bear's fire-and-forget architecture means post-write reads return pre-mutation state, which would mislead the LLM into thinking the operation failed.
Tools are first discovered via descriptions, then invoked via schemas. Optimize both for the AI agent and the LLM, not for humans.

## Tool Description

Expand Down Expand Up @@ -60,3 +53,15 @@ const schema = z.object({
.describe("Array of file paths to read. Each path must be a valid absolute or relative file path.")
});
```

## Mutation Response Conventions

A mutation tool's response should give the LLM enough metadata to:

1. **Confirm the write landed** — a "what-changed" summary or a version token the LLM can compare against its prior view.
2. **Address the affected resource in follow-up calls** — a stable identifier (note ID, row ID, document path, etc.) the LLM can pass back without round-tripping through search.
3. **Reason about freshness for subsequent writes** — a version token where the underlying system exposes one (HTTP `ETag`, Core Data `Z_OPT`, document revision, etc.). This is what enables eventual *enforce*-style optimistic concurrency (HTTP `If-Match` / `412 Precondition Failed`).

**Field-sourcing discipline matters more than the field list.** Prefer values already available pre-write — input parameters, pre-flight validation reads, helpers that bundle id+version in a single SELECT — over post-write reads that may reflect pre-mutation state if the underlying write path is asynchronous and has no completion handle. When a post-write read is genuinely required to capture a version token, design it to wait for a *change* (write-confirmation) rather than to sample current state, and surface a clearly-labelled sentinel on timeout instead of a value that could be stale.

For this server's instantiation — which fields the response carries, which underlying token is the version, how it's sourced safely, and the narrow exceptions to the general "never read after write" rule — see `SPECIFICATION.md`.