# MCP Server

NotarAI includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that serves pre-filtered diffs and change data to the reconciliation engine. This keeps context usage proportional to what actually changed rather than the full repository.

## Setup

`notarai init` writes an `.mcp.json` file to the project root that registers the MCP server:

```json
{
  "mcpServers": {
    "notarai": {
      "type": "stdio",
      "command": "notarai",
      "args": ["mcp"]
    }
  }
}
```

Claude Code reads this file and starts the server automatically. No manual configuration needed.

## Protocol

- **Transport:** stdio (stdin/stdout)
- **Format:** JSON-RPC 2.0, one message per line
- **Execution:** synchronous (no async runtime)
- **Protocol version:** `2024-11-05`

## Initialize response

The `initialize` response includes standard MCP fields (`protocolVersion`, `capabilities`, `serverInfo`, `tools`). When the local schema (`.notarai/notarai.spec.json`) is out of date relative to the bundled schema, the response includes an additional `schemaNote` field:

```json
{
  "schemaNote": "Schema is out of date (local: .../0.5/..., bundled: .../0.6/...). Run `notarai init` to update."
}
```

This surfaces schema staleness to Claude at session start without requiring a separate check.

When the project's NotarAI configs are behind the running CLI version (detected via the version in `.notarai/README.md`), the response includes an additional `projectNote` field:

```json
{
  "projectNote": "hint: project was initialized with notarai v0.3.1. Run `notarai init` to update project configs to v0.3.2."
}
```

This surfaces project config staleness to Claude at session start so reconciliation uses up-to-date slash commands and schema.

## Tools

### list_affected_specs

Identify which specs govern files that changed on the current branch relative to a base branch.

**Parameters:**

| Parameter     | Type   | Required | Description                             |
| ------------- | ------ | -------- | --------------------------------------- |
| `base_branch` | string | Yes      | Branch to diff against (e.g., `"main"`) |

**Returns:**

```json
{
  "changed_files": ["src/auth.rs", "src/main.rs"],
  "affected_specs": [
    {
      "spec_path": ".notarai/cli.spec.yaml",
      "behaviors": [],
      "constraints": [],
      "invariants": []
    }
  ]
}
```

Each affected spec includes its `behaviors`, `constraints`, and `invariants` so the reconciliation engine has the context to evaluate drift without additional file reads.

---

### get_spec_diff

Get the git diff filtered to files governed by a specific spec. Uses the hash cache to skip files that haven't changed since the last reconciliation.

**Parameters:**

| Parameter          | Type     | Required | Description                                                                                |
| ------------------ | -------- | -------- | ------------------------------------------------------------------------------------------ |
| `spec_path`        | string   | Yes      | Relative path to the spec file                                                             |
| `base_branch`      | string   | Yes      | Branch to diff against                                                                     |
| `exclude_patterns` | string[] | No       | Glob patterns to exclude via git `:(exclude)` pathspecs (e.g., `["Cargo.lock", "*.lock"]`) |
| `bypass_cache`     | boolean  | No       | If `true`, diff all governed files regardless of cache state. Defaults to `false`          |

**Returns:**

```json
{
  "diff": "unified diff of non-spec governed files...",
  "files": ["src/auth.rs"],
  "skipped": ["src/utils.rs"],
  "excluded": ["Cargo.lock"],
  "spec_changes": [
    {
      "path": ".notarai/cli.spec.yaml",
      "content": "full file content..."
    }
  ],
  "system_spec": {
    "path": ".notarai/system.spec.yaml",
    "content": "full file content..."
  },
  "binary_changes": ["assets/logo.png", "slides/deck.pptx"],
  "file_categories": {
    "src/auth.rs": "code",
    "docs/auth.md": "docs",
    "assets/logo.png": "assets"
  }
}
```

| Field             | Description                                                                                                           |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| `diff`            | Unified diff output for non-spec, non-binary artifact files only                                                      |
| `files`           | Non-spec files included in the diff (includes binary files by path, but their content is in `binary_changes`)         |
| `skipped`         | Non-spec files whose BLAKE3 hash matched the cache (already reconciled)                                               |
| `excluded`        | Patterns passed via `exclude_patterns`                                                                                |
| `spec_changes`    | Array of `{path, content}` for each governed `.notarai/**/*.spec.yaml` file that changed                              |
| `system_spec`     | The system spec (the spec with a `subsystems` key) -- included whenever `spec_changes` is non-empty; `null` otherwise |
| `binary_changes`  | File paths of binary files (images, PPTX, PDF, etc.) whose content cannot be usefully diffed                          |
| `file_categories` | Object mapping each changed file path to its artifact category from the spec (e.g., `"code"`, `"docs"`, `"assets"`)   |

**Why full content for spec files?**

Spec files express intent, not implementation. The reconciliation engine needs the complete spec to evaluate drift -- diff hunks showing only changed lines lack the context to determine whether behavior is still satisfied. Returning full content also avoids the ambiguity of partial context when the spec is the source of truth.

**Spec deduplication:** If the system spec itself changed, it appears in `spec_changes` with full content and `system_spec` contains only `{path}` (a reference) to avoid duplicating the content.

**Cache behavior:**

- Files whose on-disk BLAKE3 hash matches the cached hash are listed in `skipped` (for artifact files) or omitted from `spec_changes` (for spec files).
- A cold or absent cache causes all governed files to be included. This is a safe fallback that ensures nothing is missed.
- `bypass_cache: true` forces a full diff without destroying the cache (useful for re-checking everything).

---

### get_changed_artifacts

Get artifact files governed by a spec that have changed since the last cache update. Useful for identifying which docs or other artifacts need review during reconciliation.

**Parameters:**

| Parameter       | Type   | Required | Description                                                     |
| --------------- | ------ | -------- | --------------------------------------------------------------- |
| `spec_path`     | string | Yes      | Relative path to the spec file                                  |
| `artifact_type` | string | No       | Filter by artifact type (e.g., `"docs"`, `"code"`, `"configs"`) |

**Returns:**

```json
{
  "changed_artifacts": ["docs/auth.md", "docs/api-reference.md"]
}
```

Only files whose content differs from the cached hash are included. If no `artifact_type` is specified, all artifact types are checked.

---

### mark_reconciled

Update the hash cache after reconciliation is complete. Call this at the end of a reconciliation pass so that subsequent runs skip files that haven't changed.

**Parameters:**

| Parameter | Type     | Required | Description                  |
| --------- | -------- | -------- | ---------------------------- |
| `files`   | string[] | Yes      | Relative file paths to cache |

**Returns:**

```json
{
  "updated": 5
}
```

Files are hashed with BLAKE3 and stored with their relative paths as cache keys. Non-existent files are silently skipped.

---

### clear_cache

Delete the reconciliation cache database, forcing the next `get_spec_diff` call to diff all governed files.

**Parameters:** None.

**Returns:**

```json
{
  "cleared": true
}
```

Returns `true` if the database was deleted, `false` if it didn't exist.

---

### snapshot_state

Persist the current reconciliation cache as a state snapshot at `.notarai/reconciliation_state.json`. Call this at the end of a successful reconciliation pass.

**Parameters:** None.

**Returns:**

```json
{
  "state_path": ".notarai/reconciliation_state.json",
  "files": 42,
  "specs": 5,
  "git_hash": "a1b2c3d..."
}
```

| Field        | Description                                               |
| ------------ | --------------------------------------------------------- |
| `state_path` | Absolute path where the state file was written            |
| `files`      | Number of non-spec file fingerprints stored               |
| `specs`      | Number of spec fingerprints stored                        |
| `git_hash`   | git HEAD at snapshot time (empty string if not in a repo) |

The state file is pretty-printed JSON and safe to commit. It gives collaborators a baseline so subsequent `get_spec_diff` calls can skip files that haven't changed since the last reconciliation. Use `notarai state show` / `notarai state reset` to inspect or clear state from the CLI.

## Cache semantics

The cache is a SQLite database at `.notarai/.cache/notarai.db` with a single table:

```sql
file_cache(path TEXT PRIMARY KEY, blake3_hash TEXT, updated_at INTEGER)
```

**Key details:**

- **Hash algorithm:** [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) -- fast cryptographic hash.
- **Path format:** MCP tools use **relative paths** as cache keys. Seed the MCP cache via `mark_reconciled`, not `notarai cache update`.
- **Cold cache:** When the cache is empty or absent, `get_spec_diff` diffs all governed files. This is the safe default.
- **Cache location:** `.notarai/.cache/` is gitignored by `notarai init` so the cache is never committed.

## Error codes

| Code     | Meaning                                                   |
| -------- | --------------------------------------------------------- |
| `-32700` | Parse error (malformed JSON)                              |
| `-32601` | Method not found                                          |
| `-32602` | Invalid params (missing required parameter)               |
| `-32603` | Internal error (git failure, file I/O, cache unavailable) |
