# wiki-viewer-mcp

MCP filesystem adapter for [wiki-viewer](https://github.com/anh-chu/wiki-viewer).

Maps standard MCP filesystem tools onto the wiki-viewer agent HTTP API so Claude Code, Cursor, and Codex get native-feeling file tools against a **remote** wiki-viewer instance.

## First-time setup (register)

Before connecting an agent, register it with your wiki-viewer instance.
The human operator approves the request in the wiki-viewer AI Panel.

**Using the built-in CLI (recommended):**

```bash
# Once published to npm:
npx wiki-viewer-mcp register \
  --url https://notes.example.com \
  --id ai:myagent \
  --name "My Agent" \
  --scope-paths "**/*" \
  --ops read,mutate,delete \
  --timeout 300

# Or from a local build (after cd packages/wiki-viewer-mcp && npm run build):
node ./dist/index.js register \
  --url http://localhost:3000 \
  --id ai:myagent \
  --name "My Agent"
```

The CLI will:

1. POST the registration to wiki-viewer and print the registration ID.
2. Print `⏳ Waiting for approval. Open the wiki-viewer AI Panel and approve agent "ai:myagent".`
3. Poll every 3 s until the human approves (or timeout).
4. On approval, print your `WIKI_VIEWER_TOKEN` and `WIKI_VIEWER_AGENT_ID`, then print a ready-to-paste `mcp.json` snippet.

```
✅ Approved!

Agent ID : ai:myagent
Token    : wv_tok_…

Paste this into your mcp.json:

{
  "servers": {
    "wiki-viewer": {
      "command": "npx",
      "args": ["wiki-viewer-mcp"],
      "env": {
        "WIKI_VIEWER_URL": "https://notes.example.com",
        "WIKI_VIEWER_TOKEN": "wv_tok_…",
        "WIKI_VIEWER_AGENT_ID": "ai:myagent"
      }
    }
  }
}
```

**CLI options:**

| Flag            | Required | Default       | Description                                               |
| --------------- | -------- | ------------- | --------------------------------------------------------- |
| `--url`         | yes      | —             | Base URL of wiki-viewer, e.g. `https://notes.example.com` |
| `--id`          | yes      | —             | Agent ID, must match `ai:[a-z][a-z0-9-]{0,30}`            |
| `--name`        | yes      | —             | Human-readable display name                               |
| `--scope-paths` | no       | `**/*`        | Comma-separated path globs the agent can access           |
| `--ops`         | no       | `read,mutate` | Comma-separated: `read`, `mutate`, `delete`               |
| `--timeout`     | no       | `300`         | Seconds to wait for approval before giving up             |

---

## Installation

```bash
# Once published to npm:
npx wiki-viewer-mcp
# or install globally
npm i -g wiki-viewer-mcp

# From a local build:
cd packages/wiki-viewer-mcp
npm run build
node dist/index.js   # start MCP server
```

## Configuration

Set three environment variables before starting the MCP server
(the `register` command prints them for you after approval):

| Var                     | Description                                                                                                                                                                                       |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WIKI_VIEWER_URL`       | Base URL of your wiki-viewer instance, e.g. `https://notes.example.com`                                                                                                                           |
| `WIKI_VIEWER_TOKEN`     | Bearer token obtained via `wiki-viewer-mcp register`                                                                                                                                              |
| `WIKI_VIEWER_AGENT_ID`  | Your agent ID (e.g. `ai:myagent`), sent as `X-Agent-Id` on every request                                                                                                                          |
| `WIKI_VIEWER_WORKSPACE` | _Optional._ Workspace id, sent as `X-Workspace` on every request. Targets one root directory when the instance serves several. Omit for single-workspace instances (the server uses its default). |

## Usage in Claude Code / Cursor / Codex

Add to your `mcp.json` (or equivalent):

```json
{
  "servers": {
    "wiki-viewer": {
      "command": "npx",
      "args": ["wiki-viewer-mcp"],
      "env": {
        "WIKI_VIEWER_URL": "https://notes.example.com",
        "WIKI_VIEWER_TOKEN": "<your-token>",
        "WIKI_VIEWER_AGENT_ID": "<your-agent-id>"
      }
    }
  }
}
```

## Available tools

| Tool             | Maps to                            | Description                                     |
| ---------------- | ---------------------------------- | ----------------------------------------------- |
| `read_file`      | `GET /api/agent/fs/file/<path>`    | Read file bytes; captures sha256 + collab state |
| `write_file`     | `PUT /api/agent/fs/file/<path>`    | Atomic whole-file write with If-Match           |
| `edit_file`      | read → str-replace → PUT           | Client-side exact-string replacement            |
| `list_directory` | `GET /api/agent/fs/ls/<path>`      | Scope-filtered directory listing                |
| `search`         | `POST /api/agent/fs/search`        | Server-side grep or glob                        |
| `move_file`      | `POST /api/agent/fs/move`          | Rename/move; sidecar handled by server          |
| `delete_file`    | `DELETE /api/agent/fs/file/<path>` | Delete; requires `delete` scope                 |

## Working mode vs Collaborating mode (important)

wiki-viewer has two tiers for `.md` files:

- **Tier 1 (raw fs)** — fast, all file types, light audit. Use for code, config, non-prose, or whole-file rewrites.
- **Tier 2 (collab)** — block-ops + proof-spans, review/accept/revert by humans. Use for prose that a human is co-editing.

The shim enforces this automatically in three layers:

### Layer 1 — discoverable (headers)

Every `read_file` response includes:

```
X-Collab-State: active | tracked | untracked | not-markdown
X-Collab-Revision: <n>
X-Collab-Snapshot: /api/agent/files/<path>.md
```

| State          | Meaning                                                      | You should                             |
| -------------- | ------------------------------------------------------------ | -------------------------------------- |
| `active`       | Human has the doc open OR there are pending review artifacts | **Use Tier-2 block-ops**               |
| `tracked`      | Sidecar exists, no active review                             | Prefer Tier-2 for prose/semantic edits |
| `untracked`    | Plain `.md`, no collaboration history                        | Raw ok                                 |
| `not-markdown` | Not a `.md` file                                             | Raw only                               |

### Layer 2 — client-side guard

If the shim's last-known `X-Collab-State` for a `.md` is `active`, `write_file` and `edit_file` are **blocked** before any HTTP request is made. The tool returns a clear error with the Tier-2 snapshot URL.

### Layer 3 — server-side enforcement (409)

Even if the cache is stale, the server re-checks collab state **atomically inside the write mutex**. A raw `PUT` to an `active` `.md` is rejected with `409 COLLAB_ACTIVE` and the Tier-2 snapshot URL. The shim surfaces this as a clear error message.

### Rule

> Before editing a `.md`, run `read_file` and check `X-Collab-State`.  
> If `active`, use wiki-viewer block-ops (Tier 2).  
> For everything else, use raw fs tools.

## If-Match concurrency

The shim caches the sha256 from every `read_file` response (ETag header). `write_file`, `edit_file`, and `delete_file` automatically send this as `If-Match`. If the file changed since your read, you get a `412` error with instructions to re-read.

`edit_file` always reads fresh before writing, so it's naturally atomic per-op.

Use `force: true` to bypass `If-Match` (audited by the server).

## Scope

Your token's `scope.paths` (set during registration) governs what paths you can touch. Paths outside scope, and internal paths (`.proof/`, `.locks/`, the app db), are rejected by the server. Scope supports `**`, `*`, `?` glob patterns.

The `delete` op is a separate scope permission from `mutate` — an agent can be granted "edit but never delete."

## Development

```bash
cd packages/wiki-viewer-mcp
pnpm install
pnpm test      # runs unit tests with tsx --test
pnpm build     # compiles to dist/
```

Tests use a mock fetch — no real wiki-viewer instance needed.
