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
pathis absolute, and everyfiles[].pathis 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
rangesarray.
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_STOREdiagnostic.
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.