Skip to main content
Glama
SettleTop-Inc

CodeRoot-Authoring-MCP

Official
README.md
# CodeRoot-Authoring-MCP

An MCP server that captures an agentic asset's **foundational record** while
the asset is being built. It writes and maintains a single file,
`asset-record.json`, at the root of the repository being built — creation
facts (language, framework, runtime, direct dependencies, repository
identity) plus three facts only the author can assert (`created_by`,
`maintained_by`, `model_access.mode`). The file is committed alongside the
code and read downstream (by CodeRoot) as **declared provenance**. It never
affects how the asset is classified.

This server has no configuration, no network access, and no secrets. It
reads and writes one JSON file in a directory the caller names.

## The record contract

```json
{
  "record_version": 1,
  "created_by": "settletop-niles",
  "created_at": "2026-08-09T00:00:00Z",
  "source_repo": {"host": "github.com", "owner": "SettleTop-Inc", "name": "example"},
  "maintained_by": "SettleTop-Inc",
  "technologies": {
    "language": "python",
    "framework": "mcp",
    "runtime": "python>=3.11",
    "dependencies": ["mcp", "httpx"]
  },
  "model_access": {"mode": "byo", "provider": null, "model": null},
  "confirmation": {
    "mode": "elicitation",
    "confirmed": ["created_by", "maintained_by", "model_access.mode"],
    "complete": true
  }
}
```

- `record_version` is required and always `1`.
- `model_access.mode` is `"pinned"` or `"byo"`; `pinned` requires a non-null
  `provider` and `model`, `byo` forces both null.
- `technologies.dependencies` is DIRECT dependencies only (not the resolved
  tree) — at most 50 entries, 100 chars each.
- Every string field must be non-blank and at most 200 chars.
- Unknown top-level keys are ignored (forward compatible).

This is a summary. The machine-readable contract — the one the server itself
validates against — is served live via the `record://schema` resource (and
identically by the `get_record_schema` tool), so a client can always fetch
the current shape instead of trusting a copy in this file.

## The creation workflow

The `new_asset` prompt is a twelve-step workflow for building a new MCP server
or agent: decide what it does, whether it is really an agent, what it must
never do, how you will know it worked, what it can use and when it stops —
*then* make the repo, build, test on real work, containerise, prove the same
tests pass inside the image, and ship a tag. The first six get worse if asked
after a scaffold exists, which is why the order is enforced rather than
suggested.

Two tools hold your place, so you are not tracking twelve steps by hand:

| Tool | Arguments | Returns |
| --- | --- | --- |
| `next_step` | `directory: str = "."` | `{"step": <int>, "title": ..., "asks": ..., "done": [...], "remaining": [...]}`, or `{"complete": true}` |
| `complete_step` | `step: int`, `answer: str`, `directory: str = "."` | `{"recorded": <int>, "next": {...}}`, or `{"error": "<code>", ...}` |

They read and write **`WORKFLOW.md`** at the repo root — the checklist *is* the
state, so the place you are up to survives a session ending or someone picking
the work up a week later. `complete_step` refuses a step whose predecessors are
unanswered (`E_OUT_OF_ORDER`), refuses a blank answer, and does not count a
ticked box with nothing under it.

`WORKFLOW.md` is committed with the code and is the account of **why** the
asset is the way it is. `asset-record.json` remains the facts of record — the
file anything downstream reads. The workflow calls `record_facts` at steps 7
and 8 and `finalize_record` at step 12, so following it produces both.

## Tools

| Tool | Arguments | Returns |
| --- | --- | --- |
| `get_record_schema` | — | `RECORD_SCHEMA` (the JSON-Schema-shaped contract) directly |
| `record_facts` | `patch: dict`, `directory: str = "."` | `{"record": ..., "missing": [...]}` on success, `{"error": "<code>", ...}` on rejection |
| `read_record` | `directory: str = "."` | `{"record": ..., "missing": [...]}` on success (an empty record if no file exists yet), `{"error": "<code>", ...}` on rejection |
| `finalize_record` | `confirmations: dict`, `directory: str = "."`, `mode: str = "conversation"` | `{"record": ..., "missing": []}` on success, `{"error": "<code>", ...}` on rejection — rejection writes nothing |

`record_facts` deep-merges its patch into the existing record and is meant to
be called repeatedly, as each fact is decided during the build.
`finalize_record` is the only tool that marks a record complete: it requires
the author's own confirmation of `created_by`, `maintained_by`, and
`model_access.mode`, and refuses to write anything if the resulting record
would be invalid or a confirmation is missing.

There is also a `record://schema` resource (identical to `get_record_schema`),
the `new_asset` prompt above, and a `create_asset_record` prompt that walks the
capture → review → confirm sequence on its own for an asset built without the
full workflow.

## Install

Two ways to run it: a prebuilt container from GHCR, or straight from a local
checkout with `uv`. Both speak MCP over stdio and both write
`asset-record.json` into a directory you name. Nothing else — no environment
variables, no tokens, no network.

### Run with Docker (GHCR)

The image is published to GitHub Container Registry as
`ghcr.io/settletop-inc/coderoot-authoring-mcp`, but the package is **private**,
so authenticate once on this machine before pulling — otherwise `docker pull` /
`docker run` returns `403`:

```bash
# One-time. Use a GitHub Personal Access Token (classic) with the
# read:packages scope as the password.
docker login ghcr.io -u <github-username>
# Or, with the gh CLI:
#   gh auth refresh -s read:packages && gh auth token | docker login ghcr.io -u <github-username> --password-stdin
```

Then run the server against the repo you are authoring. Because it writes into
a directory, bind-mount that repo and attach stdin:

```bash
docker run --rm -i -w /work -v "$PWD:/work" ghcr.io/settletop-inc/coderoot-authoring-mcp
```

- `-i` attaches stdin — **required** for a stdio MCP server; without it the
  server has no channel to speak on and exits immediately.
- `-v "$PWD:/work"` mounts the repo being authored into the container, so the
  `asset-record.json` the server writes lands on your host and survives the
  container exiting.
- `-w /work` makes `/work` the container's working directory, so the tools'
  default `directory="."` resolves to your mounted repo. The image's own
  working directory is `/app`, which is **not** mounted; without `-w /work` a
  tool called with the default `.` writes inside the container and the file is
  lost on exit. So either pass `-w /work` as shown, or call the tools with an
  explicit `directory="/work"`. (Every tool also accepts an arbitrary
  `directory` argument, so the agent can target any repo path directly.)

On Windows, use `${PWD}` in PowerShell or `%CD%` in `cmd.exe` in place of
`$PWD`.

Tags: `:latest` and `:sha-<short>` track `main`; a release is tagged
`:vX.Y.Z`. No `-e` flags are needed — this server reads no environment.

### Run locally (uv)

Straight from a checkout, no container:

```bash
uv run --directory <path-to-this-repo> python -m authoring.server
```

Replace `<path-to-this-repo>` with wherever you've cloned
`CodeRoot-Authoring-MCP`. Installing the package also exposes a
`coderoot-authoring-mcp` console script (`authoring.server:main`), an
equivalent entry point to `python -m authoring.server` for clients that prefer
to invoke it directly. The server talks stdio and needs no environment
variables, tokens, or network access.

### Use with Claude Code

Register the server with `claude mcp add`. Claude Code launches it as a
subprocess and speaks MCP over its stdin/stdout, so the whole launch command
goes after the `--`.

Docker (private GHCR image — run `docker login ghcr.io` first, see above):

```bash
claude mcp add coderoot-authoring -- docker run --rm -i -w /work -v "$PWD:/work" ghcr.io/settletop-inc/coderoot-authoring-mcp
```

On **Windows PowerShell**, brace the variable — bare `$PWD:` is a PowerShell
parser error (it reads `:` as a drive/scope qualifier) — so use `${PWD}`:

```powershell
claude mcp add coderoot-authoring -- docker run --rm -i -w /work -v "${PWD}:/work" ghcr.io/settletop-inc/coderoot-authoring-mcp
```

`${PWD}` is captured when you run `claude mcp add`, so run it from the repo you
want to author (or replace it with an explicit path, e.g. `"C:\path\to\repo:/work"`).

Local checkout (uv):

```bash
claude mcp add coderoot-authoring -- uv run --directory <path-to-this-repo> python -m authoring.server
```

**Make it global, and reload.** `claude mcp add` defaults to **local** scope —
the server is available only in the directory you ran it in, so it won't appear
in a session for a different project. Add `--scope user` to register it for
every project. MCP servers connect when a session starts, so **restart Claude
Code (or open a new chat)** after adding — a mid-session add won't show until
then.

**Confirm it connected.** Run the **`/mcp`** command inside Claude Code (there
is no MCP menu or button — `/mcp` lists each server, its connection status, and
its tools), or `claude mcp list` in a terminal:

```bash
claude mcp list
```

`coderoot-authoring` should show as connected, and inside a Claude Code session
its six tools — `next_step`, `complete_step`, `record_facts`, `read_record`,
`finalize_record`, `get_record_schema` — plus the `create_asset_record` prompt become available.

### Configuration

There is nothing to configure: `authoring/server.py` constructs the server at
import with no settings to read, no config file, and no secrets. The only
things that decide where the record is written are the bind mount and the
`directory` argument the tools already take:

| Name | Required? | Meaning |
| --- | --- | --- |
| _(environment variables)_ | — | None. The server reads no environment variables, tokens, or credentials, and makes no network calls. |
| `-v "<host-repo>:/work"` (docker) | Docker only | Bind-mounts the repo being authored into the container so writes survive the container exiting. |
| `-w /work` (docker) | Recommended | Makes the mount the container's working directory, so the tools' default `directory="."` lands in your repo. Otherwise pass `directory="/work"`. |
| `directory` (tool argument) | No (default `.`) | Every tool takes it; the record is always written to `<directory>/asset-record.json`. Local (uv) runs resolve `.` against the server process's working directory. |

### Skill

`skills/creating-agentic-assets/SKILL.md` teaches an agent to use this server
proactively while building a new asset — capture facts as they're decided,
finalize before the first push, and never assert the author-only fields on
the author's behalf. Install it by copying or symlinking the skill directory
into `~/.claude/skills/`:

```bash
ln -s "$(pwd)/skills/creating-agentic-assets" ~/.claude/skills/creating-agentic-assets
```

(On Windows, copy the directory instead of symlinking, or use `mklink /D` from
an elevated shell.)

## Confirmation modes

`finalize_record` supports two ways to get the author's confirmation of the
three author-only fields:

- **`mode="conversation"`** (the default, and the floor) — the calling agent
  asks the author in the conversation, in its own words, and passes their
  answers in `confirmations`. This works with any MCP client, since it needs
  no special capability, and is what the skill instructs agents to use unless
  the client is known to support interactive prompting (see "Client support"
  below).
- **`mode="elicitation"`** — the server asks the client to prompt the author
  directly, via a typed elicitation request (`AuthorConfirmation`). Prefer it
  only for a client you know supports interactive prompting: over MCP, a
  client that hasn't declared form-elicitation capability gets a
  protocol-level error (JSON-RPC `-32021`) before the tool body even runs,
  and as of this writing the interactive accept path has not been observed
  live for any client (see "Client support" below — only the cancel path has,
  over Claude Code headless). The `{"error": "confirmation_unavailable", ...}`
  shape only occurs for in-process/direct invocation, not over MCP.

Either way, the confirmation is checked against what was actually passed to
`finalize_record` in that call — a value written earlier via `record_facts`
is never treated as a confirmation of itself.

### Client support

Live findings, Claude Code headless (`claude -p --mcp-config`, 2026-08-09):

- **Instructions: injected.** A fresh instance quoted the server's
  `instructions` first sentence verbatim, unprompted, and listed all four
  tools — the ambient contract reaches the agent on this client.
- **Elicitation: capability declared.** `finalize_record(mode="elicitation")`
  did not hit the JSON-RPC `-32021` capability error; the elicit request went
  through, the non-interactive harness cancelled it, and the server returned
  `{"error": "confirmation_cancelled"}` with nothing written — the cancel
  path verified over the real wire.
- **Interactive accept path: not yet observed live.** The SDK-level accept
  flow is covered end-to-end by this repo's tests (a real in-memory client
  answering the elicitation); whether interactive Claude Code renders the
  form to a human author remains to be confirmed the first time this server
  is used in a live interactive session. Until then, `mode="conversation"`
  stays the default and the floor — see "Confirmation modes" above.

## Development

Requires Python >= 3.11.

```bash
uv sync --extra dev
uv run pytest -q
```

152 tests, all green.

## License

GPL-3.0-or-later. See `LICENSE`.

## Security

This server handles no secrets: no tokens, no credentials, no network calls.
Its filesystem authority is broader than a single fixed file, though: the
`directory` argument every tool takes is unrestricted — absolute paths and
`..` segments are honored as given, a missing directory is created rather
than rejected, and an existing `asset-record.json` at the target is merged
into rather than refused. The server runs with exactly the privileges of the
client that launched it — the MCP client is the trust boundary, not this
server — and what bounds what can be written is that the filename is never
caller-controlled: every write lands at `<directory>/asset-record.json`,
always that exact basename.

TDQS

A4.6/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct role: schema retrieval, fact recording, record reading, and finalization. There is no functional overlap, and the descriptions explicitly delineate boundaries (e.g., record_facts vs. finalize_record).

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (get_, record_, read_, finalize_). The naming is predictable and unambiguous, with no mix of conventions.

Tool Count5/5

Four tools is an appropriate size for a focused authoring workflow. Each tool serves a necessary, non-redundant function, and the count falls well within the ideal 3-15 range.

Completeness5/5

The tool set covers the complete lifecycle of an asset record: reading the schema, recording facts, reading the current state, and finalizing with author confirmation. No obvious dead ends or missing operations for the stated purpose.

Maintenance

ActivitySlowing
ResponsivenessNo issues