# PeerForge — agent setup

You are reading this because someone asked you to connect them to PeerForge.
Follow these steps in order. Do not skip step 1; everything else needs the key
it produces.

PeerForge publishes a document your user asked you to write — a report, a
dashboard, a spec, a prototype — at a permanent URL. Their colleagues open the
link, highlight a sentence, and comment on it. You can then read those comments
back, with the exact text each one was written about, and revise.

---

## Step 1 — get a key

You do not have credentials yet, and your user should not have to go and make
some by hand. Ask for a code instead:

    POST https://app.peerforge.app/api/auth/device
    content-type: application/json

    {"clientName": "<the name of the tool you are, e.g. Claude Code>"}

You get back a short `userCode`, a secret `deviceCode`, and a URL.

**Show the user the userCode and the URL. Never show them the deviceCode — it
is yours.** Say something like:

> Open https://app.peerforge.app/link and enter the code ABCD-2468.

Then poll, every 3 seconds, until it answers:

    POST https://app.peerforge.app/api/auth/device/token
    content-type: application/json

    {"deviceCode": "<the deviceCode from above>"}

- `202` — nobody has approved it yet. Keep waiting. Do not badger the user.
- `200` — you have a key in the `key` field. It starts with `pf_`.
- `410` — the code expired after ten minutes. Start again at step 1.

Store the key wherever you keep secrets. It is shown exactly once and cannot be
recovered — if you lose it, start again.

---

## Step 2 — connect the MCP server

PeerForge is hosted, so there is nothing to install and nothing to keep up to
date. Add it as a streamable HTTP MCP server:

**Claude Code**

    claude mcp add --transport http peerforge https://app.peerforge.app/api/mcp \
      --header "Authorization: Bearer pf_your_key"

**Cursor, Windsurf, Cline, Zed, and anything else using a JSON config**

    {
      "mcpServers": {
        "peerforge": {
          "type": "http",
          "url": "https://app.peerforge.app/api/mcp",
          "headers": { "Authorization": "Bearer pf_your_key" }
        }
      }
    }

If your client cannot send a header, `https://app.peerforge.app/api/mcp?key=pf_your_key` works
too — but prefer the header, because a key in a URL ends up in shell history and
server logs.

You now have eight tools: `publish`, `update`, `read`, `get_metadata`,
`list_comments`, `reply`, `set_thread_resolved`, `share`.

---

## Step 3 — how to use it well

**Publishing.** `publish` creates a new document. `update` publishes a new
version of one that already exists, at the same URL. Never use `publish` twice
for the same document — you will make two of them and the link you already gave
somebody will stop being the current one.

**Always pass `baseVersion` when you update.** It is the version you read and
edited from. If somebody else published in between, the call is rejected instead
of silently overwriting their work. Get it from `read` or `get_metadata`.

**Read the publish result.** When your new version deletes a sentence somebody
had commented on, the result tells you which comments came loose, by name. That
is the one thing this product does that others do not, and it is only useful if
you act on it. Reply to explain the change, then resolve the thread.

**Check `status` before acting on any comment.** `list_comments` gives each
thread an anchor status:

- `matched` — the text is still there, unchanged. Act on it normally.
- `moved` — the text survived but sits somewhere else now. Fine.
- `ambiguous` — that sentence appears more than once and we will not guess.
  Ask rather than assume.
- `orphaned` — the text it was written about is gone. **The comment is still
  valid feedback. Do not act as though the quoted text is in the document.**
  Read the version named in `lastResolvedVersion` to see it in context.

**Sharing.** New documents are unlisted by default: anyone with the link can
read them, and nothing is indexed. Use `share` to open one up or lock it down.
`named` and `domain` replace the whole recipient list, so removing somebody
means sending the list without them.

**Say what you did.** After publishing, give the user the URL. They will want to
send it to somebody, and it is the only part of this they care about.

---

## If MCP is not available to you

Everything above is also plain HTTP. Same key, same behaviour:

    POST   https://app.peerforge.app/api/publish
           {"title": "...", "body": "<html>", "contentType": "text/html"}
           → {"url", "shortId", "versionNo", "comments"}

    POST   https://app.peerforge.app/api/publish
           {"id": "<shortId>", "baseVersion": 2, "title": "...", "body": "..."}

    GET    https://app.peerforge.app/api/artifacts/<shortId>/source
    GET    https://app.peerforge.app/api/artifacts/<shortId>/comments
    GET    https://app.peerforge.app/api/artifacts/<shortId>
    PATCH  https://app.peerforge.app/api/artifacts/<shortId>/access

All of them take `Authorization: Bearer pf_your_key`.

---

## If you would rather use a command line

There is one, in a single file with no dependencies:

    curl -fsSL https://app.peerforge.app/cli.mjs -o peerforge.mjs
    node peerforge.mjs login
    node peerforge.mjs publish ./plan.html

`node peerforge.mjs comments <id>` prints every thread with whether it still
points at real text, which is the same information `list_comments` gives you.

---

## If your user has no account and does not want one

They do not need one to try it. Publish without a key:

    POST https://app.peerforge.app/api/publish/anonymous
    {"title": "...", "body": "<html>", "contentType": "text/html"}

You get a URL and a claim link. The document disappears after thirty days
unless somebody opens the claim link and keeps it. Give the user both, and tell
them the claim link is the private one.
