MCP tools

The two tools an agent sees — their inputs, the structured success result, the structured error, the server instructions, and the stdio transport.

The server registers exactly two tools. There is no third tool, no prompt, and no resource.

Tool Inputs Success result
notes_pull path (optional) OK
notes_commit message, path (optional) OK

The normal workflow is one call, then ordinary file editing, then the other call:

notes_pull()
        |
        v
edit UTF-8 text files in the notebook directory
        |
        v
notes_commit(message)

Inputs

Tool inputs are strict JSON objects. notes_pull requires no field; notes_commit requires only message. Both accept the optional path. Unknown fields and explicit null values are invalid, and the advertised schemas set additionalProperties: false.

Field Tool Rules
path both Optional UTF-8 host path of the notebook directory, 1 to 4,096 bytes. Omitted or empty means the server’s own notebook directory. May begin with ~/, which expands to the current user’s home directory before the absolute-path and byte checks. The result must be at or below the workspace root.
message notes_commit Non-blank UTF-8 without U+0000, at most 16,384 bytes. A message of only Unicode white space is rejected; any other message is preserved byte for byte. Retained in recent internal history only.

The byte bounds are enforced by the strict decode, not by the schema, because JSON Schema maxLength counts code points rather than bytes.

A supplied path addresses one directory at or below the workspace root, which is how one server can serve several notebooks. With no --workspace-root, the server takes its own session directory and the agent should simply omit path.

Tool descriptions

Each tool carries a description the host shows to the model:

  • notes_pull — “Write the current notebook into the notebook directory and record the accepted state. Omit path or pass an empty string to use the server’s notebook directory, which the result reports. Edit UTF-8 text files (without U+0000) there between notes_pull and notes_commit; notes_commit publishes the changes and incorporates concurrent non-conflicting changes.”
  • notes_commit — “Publish the caller’s changes in the notebook directory and incorporate concurrent non-conflicting changes. Omit path or pass an empty string to use the server’s notebook directory, which the result reports. message must be non-blank UTF-8 without U+0000, at most 16,384 bytes; it is retained in recent internal history only.”

When the process is configured with a writable or read-only set, one sentence per configured set is appended to both descriptions, naming the entries and the consequence. With both sets configured, the read-only sentence ends with the rule that reconciles them rather than “write elsewhere”.

Server instructions

The server also sends instructions for the session:

The notebook directory is <path>. Call notes_pull, edit UTF-8 text files
(without U+0000) there, then call notes_commit to publish. Both tools
default to that directory; pass path only to address a subdirectory of it.

A configured writable set appends: “Writable paths: <entries>. notes_commit refuses any change elsewhere, resets those files, and reports READ_ONLY_PATH; write only under them.” A configured read-only set appends the matching sentence for its own entries, ending in “write elsewhere.” on its own, or in “where the two sets nest, the longest matching entry decides.” when a writable set is also configured.

The set is advertised on four surfaces — the instructions, both tool descriptions, and the readOnly and writable arrays of every result — so a host that drops one still leaves the others. A process with neither set configured is byte-for-byte unchanged on every surface except the always-present empty arrays.

The success result

A successful call returns one MCP text item and one structured object. The text item carries the resolved notebook directory, because many clients forward only the text item to the model:

/tmp/slivingdoc-7f3a1c/notebook

With path sets configured it becomes <path> (writable: <entries>), <path> (read-only: <entries>), or, with both, <path> (writable: <entries>; read-only: <entries>; longest match decides).

The structured object is:

{
  "code": "OK",
  "path": "/tmp/slivingdoc-7f3a1c/notebook",
  "generation": 18,
  "filesChanged": 3,
  "insertions": 3,
  "deletions": 4,
  "files": [
    { "path": "notes/a.md", "insertions": 1, "deletions": 1 },
    { "path": "notes/c.md", "insertions": 2, "deletions": 0 },
    { "path": "archive/old.md", "insertions": 0, "deletions": 3 }
  ],
  "readOnly": ["docs", "faq.md"],
  "writable": []
}
Field Meaning
code Always OK.
path The resolved notebook directory the operation ran against — the caller’s own visible directory, never private state.
generation The accepted remote generation after the operation.
filesChanged The number of entries in files.
insertions Total inserted lines.
deletions Total deleted lines.
files Per-file path, insertions, deletions. Always present, empty for a no-op synchronisation.
readOnly The normalised, sorted read-only set this server enforces. Always present, empty when none is configured.
writable The normalised, sorted writable set. Always present, empty when none is configured. It never appears in readOnly.

The pull diffstat is the on-disk delta between the visible state before the pull and the materialised result; the commit diffstat is the increment the publication added over the observed remote parent tree. Paths use the same normalised internal slash form as error files. No success data contains credentials, S3 keys, private paths, or Git object IDs.

The structured error

A domain error returns a tool result with isError=true, one candid text item, and this structured object:

{
  "code": "CONTENT_CONFLICT",
  "reason": "MERGE_CONFLICT",
  "action": "EDIT_FILES",
  "diagnosticId": "0011223344556677",
  "retryable": false,
  "message": "Resolve the conflict blocks before notes_commit.",
  "files": [
    {
      "path": "notes/today.md",
      "reason": "TEXT_CONFLICT",
      "ranges": [{ "start": 12, "end": 18 }]
    }
  ],
  "readOnly": [],
  "writable": []
}
Field Presence Meaning
code always The stable error category.
reason always One stable token classifying the error one level below code.
action always One stable token naming the caller’s next step.
diagnosticId always A fresh 16-character lowercase hexadecimal ID correlating the result with the two server log records for the call.
retryable always Whether a retry of the same call can help.
message always Human-readable text. It can change between releases.
detail conditional For ENGINE_FAILED only, and only when the engine classified the cause itself: a fixed description from a closed set. An unrecognised cause carries no detail, and the operator reads the full cause from the log record with the same diagnosticId.
files always The affected files. Empty array when the error is not about files.
recovery conditional For RECOVERY_FAILURE only: stage (string), remoteAccepted (yes, no, or unknown), and resynchronized (boolean).
readOnly always The normalised read-only set, as on success.
writable always The normalised writable set, as on success.

Each files entry carries path, its own reason, and ranges:

  • The request path is absolute, and every files[].path is relative to it, in the normalised internal slash form.
  • Ranges are one-based, inclusive, ordered, and non-overlapping. A file with no marker range carries an empty ranges array.

code, reason, action, and every files[].reason are stable across releases; message text is not. Every domain error carries a non-empty reason and action, and every file entry carries a non-empty reason, so an agent can branch on the tokens instead of parsing prose and is never left without a next step. Errors is the complete table.

The candid text item repeats the code, reason, message, optional detail, affected files, action, retryable verdict, diagnostic ID, recovery report, and both path sets, so a client that discards structured content still keeps the whole safe diagnostic:

CONTENT_CONFLICT · MERGE_CONFLICT
Resolve the conflict blocks before notes_commit.
file: notes/today.md · TEXT_CONFLICT · lines 12-18
action: EDIT_FILES
retryable: false
diagnosticId: 0011223344556677

Note: A cancelled request is not a domain error. It stays a protocol error and never appears inside this envelope, and neither does the startup-only INCOMPATIBLE_STORE diagnostic.

No error text or data contains credentials, S3 keys, private paths, or Git object IDs. Pack keys, probe keys, 40- and 64-character hexadecimal identifiers, AWS access key IDs, and URL user information are all scrubbed from diagnostic text as defence in depth.

Transport

An MCP host starts the server as a local child process and speaks MCP JSON-RPC over stdio. Stdout carries only protocol messages; logs go to stderr. The host and the server share the visible directory, so agents and humans edit files there and the server scans them at each call. The npm package is the primary stdio installation path:

{
  "command": "npx",
  "args": ["-y", "slivingdoc", "serve"]
}

The server advertises the implementation name slivingdoc and the release version, and it logs one correlated pair of records per tool call — a start record and a completion record, both carrying the mcpReqID that becomes the result’s diagnosticId.

Next

  • Connect an MCP host — registering the server in a specific host.
  • Errors — every code, reason, file reason, and action.
  • CLI — the same operations as subcommands.

Last updated September 21, 2026

Type to search the documentation.