# robot-md-mcp — v0.1 design

**Status:** Approved design, pre-implementation
**Scope:** First shippable version of the Claude Desktop MCP server for ROBOT.md
**Complements:** `spec/v0.2-design.md` (signing / registry ingestion)
**Last updated:** 2026-04-17

---

## 0. Preamble

`robot-md-mcp` is the **Model Context Protocol** server that exposes a
`ROBOT.md` to Claude Desktop (and any other MCP-speaking client). This
document defines v0.1 — the first release. The shape is deliberately
*scoped down* from the ambitious sketch in
`integrations/claude-desktop/README.md` so we can ship without
pre-committing to API contracts that v0.2 signing will re-shape.

**What ships here does not write to the robot.** It reads the manifest
and exposes it, plus two local-only tools (`validate`, `render`). Any
dispatch to a running robot gateway — `invoke_skill`, `query_status`,
anything with a signed-command story — is deferred to the post-§13
release and will be designed in a companion doc once the crypto
decisions in `spec/v0.2-design.md` §13 are finalized.

## 1. Goals and non-goals

### Goals

1. A Claude Desktop user whose robot ships a `ROBOT.md` can add one
   MCP config entry and have Claude understand what the robot *is*.
2. The same server works with any MCP client (Zed, Cline, Continue,
   custom).
3. Zero coupling to the v0.2 signing design — no resource or tool in
   this release makes a claim that signing semantics will invalidate.
4. Parity with the Python CLI's `validate` and `render` verbs without
   requiring Python on the user's machine.

### Non-goals

- Robot-dispatch tools (`invoke_skill`, `query_status`, etc.) — v0.2.
- Signature verification — v0.2.
- Multi-manifest servers (fleet view) — post-v0.2.
- Hot-reload via filesystem watcher — manual re-read per call is fine
  for small files.
- Auto-discovery of `ROBOT.md` in CWD / home — the MCP config passes
  the path explicitly.

## 2. Decisions, recorded

| # | Decision | Alternatives considered | Why |
|---|---|---|---|
| 1 | Scope: resources + local tools (no dispatch) | (A) resources-only, (C) full v0.2 vision | (C) pre-commits API shape that §13 crypto may require us to change; (A) leaves the server without any tools, which is a valid but weaker story |
| 2 | Language/runtime: **TypeScript**, new repo | (1) integrate into Python CLI, (2) separate Python package | Matches MCP ecosystem norm; uses tsup/vitest tooling we already run for rcan-ts; no Python runtime burden on Claude Desktop users |
| 3 | Parser: **in-house** yaml + ajv | port Python parser, shell out to Python CLI | JSON schema is the single source of truth for "valid"; duplicated parsing logic is not load-bearing |
| 4 | Distribution: npm + GitHub release tarballs | GitHub only, npm only | npm is idiomatic; GH release tarball is the bridge while NPM_TOKEN is unresolved |

## 3. Repo + package

- **Repo:** `github.com/RobotRegistryFoundation/robot-md-mcp`
  (sibling to `robot-md`).
- **License:** Apache-2.0 (matches `robot-md`).
- **npm name:** `robot-md-mcp`, unscoped. (Rationale: matches
  `rcan-ts`; `@RobotRegistryFoundation` org on npm isn't claimed and
  we explicitly don't want to take on that operational surface yet.)
- **Toolchain:**
  - `tsup` for build (CJS + ESM outputs, plus a bin entrypoint)
  - `vitest` for tests
  - `strict: true` TypeScript, Node 18+
  - `ajv` for JSON-schema validation (draft 2020-12)
  - `yaml` for frontmatter parsing
  - `@modelcontextprotocol/sdk` for MCP wire protocol
- **Repo layout:**

  ```
  robot-md-mcp/
  ├─ src/
  │  ├─ index.ts                 # library exports (parseRobotMd, etc.)
  │  ├─ parser.ts                # parseRobotMd(text) → ParsedRobotMd
  │  ├─ validate.ts              # validateParsed(parsed) → ValidateResult
  │  ├─ render.ts                # renderYaml(parsed) → string
  │  ├─ server.ts                # MCP server wiring (resources + tools)
  │  ├─ bin.ts                   # CLI entrypoint — parse argv, start server
  │  └─ schema/
  │     └─ robot.schema.json     # bundled copy, kept in sync via script
  ├─ tests/
  │  ├─ parser.test.ts
  │  ├─ validate.test.ts
  │  ├─ render.test.ts
  │  ├─ server.test.ts           # in-process MCP round-trip
  │  └─ fixtures/                # copy of key fixtures from robot-md
  ├─ scripts/
  │  └─ sync-schema.mjs          # pulls schema from ../robot-md or URL
  ├─ .github/workflows/
  │  ├─ ci.yml                   # build + test + schema-sync-check
  │  └─ release.yml              # tag → GH release; dispatch → npm
  ├─ package.json
  ├─ tsconfig.json
  └─ README.md
  ```

## 4. Parser + validation

`parseRobotMd(text: string): ParsedRobotMd` — single entry point.

```ts
interface ParsedRobotMd {
  frontmatter: Record<string, unknown>;  // parsed YAML object
  body: string;                          // raw markdown after ---
  rawText: string;                       // the full input
}
```

- Splits on the first pair of `---` delimiters (same rule the Python
  parser uses). Throws `ParseError` if no frontmatter found.
- `yaml.parse` with default options (which handle comments — important
  because `autodetect`-emitted drafts carry YAML comments).
- Body is kept verbatim — the MCP server surfaces it as raw markdown.

`validateParsed(parsed): ValidateResult` returns:

```ts
interface ValidateResult {
  ok: boolean;
  summary: string;        // e.g. "bob (arm+camera, 6 DoF, 5 capabilities)"
  errors: string[];       // human-readable messages
}
```

Under the hood: ajv against the bundled schema + body checks (H1
matches `metadata.robot_name`, required sections present). Mirrors
the Python validator's behavior.

Schema is a **byte-for-byte copy** of
`robot-md/schema/v1/robot.schema.json`. `scripts/sync-schema.mjs`
refreshes it; a CI step (`schema-sync-check`) fails the build if the
copy has drifted. Follows the same pattern `robot-md` already uses
for its Python bundle and the site-served copy.

## 5. MCP server wiring

- Entrypoint: `robot-md-mcp <path>`. `<path>` is a required positional
  argument. No env var, no auto-discovery. Claude Desktop's MCP
  config line is the single source of truth for which file is served.

- Example `~/Library/Application Support/Claude/claude_desktop_config.json`:

  ```json
  {
    "mcpServers": {
      "robot-md": {
        "command": "npx",
        "args": ["-y", "robot-md-mcp", "/path/to/ROBOT.md"]
      }
    }
  }
  ```

- On startup: read + parse the file once to determine
  `metadata.robot_name` (used in resource URIs). Fail fast with a
  clear stderr message if the file is missing or unparseable. **Do
  not** refuse to start on schema-violation — that would hide a
  broken manifest; instead, serve it and let the `validate` tool
  surface the errors.

- On each resource/tool call: re-read and re-parse the file. Files
  are tiny (<10 KB in practice); the simplicity is worth the extra
  disk read. No FS watcher, no cache-invalidation logic.

## 6. Resources

Resource URIs use `robot-md://<robot_name>/<kind>` where `<robot_name>`
is `metadata.robot_name` from startup.

| URI | `mimeType` | Payload |
|---|---|---|
| `robot-md://<name>/frontmatter` | `application/json` | entire parsed frontmatter object |
| `robot-md://<name>/capabilities` | `application/json` | `frontmatter.capabilities` array (JSON-stringified) |
| `robot-md://<name>/safety` | `application/json` | `frontmatter.safety` object (JSON-stringified) |
| `robot-md://<name>/body` | `text/markdown` | prose body verbatim |

`resources/list` returns all four. `resources/read` returns the
current contents (post-reload).

Design choice: the resources are **flat and typed**, not nested. A
consumer can fetch `/capabilities` without paying for the whole
manifest. Keeps MCP traffic small.

## 7. Tools

| Name | Args | Returns |
|---|---|---|
| `validate` | `{}` | `{ ok: boolean, summary: string, errors: string[] }` |
| `render` | `{}` | `{ yaml: string }` — canonical YAML of the frontmatter |

Both tools are **pure**, read-only, and operate on the already-known
file path. No argument means no chance of misuse against a different
file than the operator configured.

Explicitly **not** exposed in v0.1:

- `invoke_skill(skill_name, params)` — deferred to post-§13.
- `query_status()` — same.
- `refresh()` — unnecessary since every call re-reads.
- `set_path(path)` — security footgun; operator owns the MCP config.

## 8. Error surfaces

| Scenario | Behavior |
|---|---|
| File path missing at startup | stderr message + non-zero exit; MCP client shows server as failed |
| File unreadable (permissions) | same as above |
| File has no frontmatter | same — startup fails; `robot_name` is required to namespace resources |
| File has invalid frontmatter YAML | same |
| File has valid YAML but schema violations | server starts; `validate` tool surfaces the errors; resources still serve the parsed (possibly-invalid) content |
| File is deleted or modified after startup | next resource/tool call re-reads and either serves fresh content or errors (with a clear "file no longer readable" message) |

Principle: **fail at startup for structural problems, surface
content problems through the `validate` tool.** Operators can fix
the file and re-run; consumers (Claude) can read the validation
errors and explain them.

## 9. Testing

- Unit tests (`vitest`) covering:
  - `parseRobotMd` on valid fixtures (minimal, bob, so-arm101,
    turtlebot4 — copied from the Python test fixtures)
  - `parseRobotMd` on malformed inputs (no frontmatter, bad YAML,
    mismatched H1)
  - `validateParsed` on valid + each category of invalid fixture
  - `renderYaml` round-trip behavior
- One integration test that:
  - Spawns `server.ts` with an in-process MCP transport from the SDK
  - Calls `resources/list`, `resources/read`, `tools/call validate`,
    `tools/call render`
  - Asserts the returned payloads match expected shapes
- CI matrix: Node 18, 20, 22.
- `schema-sync-check` CI step: MD5 compare the bundled schema against
  a freshly-fetched canonical copy; fail if they differ.

## 10. Distribution

- **Release workflow follows the `rcan-ts` pattern we established:**
  - Tag push (`v*.*.*`) → `build-and-test` job → `github-release` job
    that attaches the `.tgz` to the GitHub release.
  - `workflow_dispatch` with `publish_npm=true` → `npm-publish` job
    that uses `NPM_TOKEN`.
- v0.1.0 ships as a **GitHub release with tarball attached**; npm
  publish runs once `NPM_TOKEN` is configured (tracked separately —
  not this design's concern).
- README install sections document both paths:

  ```bash
  # once on npm
  npx robot-md-mcp /path/to/ROBOT.md

  # during the npm-blocked window
  npm i github:RobotRegistryFoundation/robot-md-mcp#v0.1.0
  ```

## 11. Coupling with the v0.2 signing design

This release is **strictly a reader**. It makes zero claims about
authenticity, so it takes no dependency on `spec/v0.2-design.md` §13.

Post-§13 changes the server will grow:

- A new resource: `robot-md://<name>/signature` (or `/envelope`) —
  once detached `.sig` semantics land.
- A new tool: `verify` — returns signature-validity + key-fingerprint
  + manifest_version.
- New tools: `invoke_skill`, `query_status` — but only once the
  signed-command wire shape is frozen. The design of those tools is
  explicitly out of scope for this document.

The v0.1 resource and tool URIs are stable and will not change when
those additions arrive.

## 12. Open items (tracked for implementation, not blocking)

- **Package-signing / provenance**: npm supports `--provenance`
  publish which records a signed attestation tied to the GH Actions
  workflow. Low-cost to add; do it in v0.1 release if the npm
  publish step lands cleanly.
- **README example using autodetect**: once a v0.1 release is live,
  a two-line README recipe — `robot-md autodetect --write ROBOT.md`
  then `npx robot-md-mcp ROBOT.md` — closes the Tier 0 60-second loop
  in a way a new operator can follow end-to-end.
- **Schema fetching fallback**: if `scripts/sync-schema.mjs` should
  fetch from `https://robotmd.dev/schema/v1/robot.schema.json` as a
  fallback when the repo-relative path is unavailable. Minor.

## 13. Implementation plan (deferred to writing-plans)

This design is the input to a separate implementation plan
(writing-plans skill). The plan will sequence:

1. Repo scaffold + toolchain
2. Parser + validator + render (pure functions, tests first)
3. MCP server wiring + tools
4. Fixtures + integration test
5. CI workflows + schema-sync check
6. README + release v0.1.0 (GH release only)
7. Post-release: enable npm publish once `NPM_TOKEN` is sorted

Each step is an independent PR with its own review gate.
