Skip to content

Connect agents

View .md

Your users can bring their own AI agents to the documents they keep in your app. An agent reads, suggests, edits and comments through Proof’s MCP server, and everything it writes is credited to it on the record and in the seal.

You build two things: a page that tells people how to connect an agent, and a consent page where they approve one. Proof does the rest. It runs the MCP server and the OAuth sign-in, issues the agent’s tokens, and checks what the agent may do on every call. Your server never holds an agent’s token, which is why an agent’s words can’t be passed off as a person’s, or a person’s as an agent’s.

Each platform has its own MCP address. For Acme Notes it is:

https://mcp.proof.exalto.ai/acme

In test mode it is https://mcp.proof.exalto.ai/acme/test, with your test consent URL. Your server can read the address for the key’s mode as mcp_url from GET /v1/platform.

When someone adds that address to their agent:

  1. The agent calls Proof, learns it needs to sign in, and registers itself with Proof’s OAuth server. Proof checks where the agent comes from: its host, such as claude.ai, which the agent can’t claim falsely.
  2. The agent opens the person’s browser at Proof, which sends it straight on to your consent URL with a request id: https://acme.com/proof/connect?request=areq_5tNc81.
  3. Your consent page signs the person in, as any page of your app would, and shows which agent is asking.
  4. When they click Allow, your server approves the request with your secret key, naming the signed-in user. Proof answers with a URL, and you send the browser there.
  5. Proof finishes the sign-in and gives the agent its tokens. From then on the agent talks only to Proof, as your user’s agent.

Tokens for one platform’s address don’t work on another’s. Someone who uses two platforms built on Proof connects their agent to each separately, and the agent keeps the two apart.

In the console, under Agents, or with PATCH /v1/platform, enter the URL of your consent page, such as https://acme.com/proof/connect, for each mode. Until you set one, Proof answers every connection attempt with an error telling the person that this platform doesn’t accept agents yet.

Give your settings an Agents section that shows your MCP address and how to add it to the agents people use most. The address is the same for all your users; who they are is settled on the consent page.

Terminal window
claude mcp add --transport http acme https://mcp.proof.exalto.ai/acme

Then run /mcp inside Claude Code and choose acme to sign in.

In every case, signing in opens the person’s browser on your consent page.

Name the server after your product (acme above). Agents show tools grouped by server, so your users see acme rather than a name they don’t recognise.

The consent page is a normal page in your app, behind your normal sign-in.

  1. Look up the request. Proof tells you which agent is asking. client.host is verified; client.name is what the agent calls itself.

    connect.ts
    app.get("/proof/connect", requireSignIn, async (req, res) => {
    const request = await proof.agentRequests.retrieve(String(req.query.request));
    const documents = await proof.users.listDocuments(req.user.proofUserId);
    res.render("connect-agent", {
    requestId: request.id,
    agentName: request.client.name, // "Claude Code"
    agentHost: request.client.host, // "claude.ai"
    documents: documents.data,
    });
    });

    If the person isn’t signed in, requireSignIn should send them through your sign-in and back to this URL, query string included. Requests last 10 minutes; an expired one returns 409 request_expired, and the person starts again from their agent.

  2. Ask the person. Show the agent’s name and host together, such as “Claude Code (claude.ai) wants to work in your Acme Notes documents”, and let them choose what it reaches:

    • All documents: every document they can open, editing at most. This is what most people want.
    • Chosen documents: only the ones they pick, each as an editor, suggester or viewer.

    Keep it to one screen with Allow and Deny.

  3. Approve or deny on your server. Always name the signed-in user from your session, never from the form.

    connect.ts
    app.post("/proof/connect", requireSignIn, verifyCsrf, async (req, res) => {
    const { requestId, decision, access, documents } = req.body;
    if (decision !== "allow") {
    const { redirect_url } = await proof.agentRequests.deny(requestId);
    return res.redirect(303, redirect_url);
    }
    const { redirect_url } = await proof.agentRequests.approve(requestId, {
    user: req.user.proofUserId,
    access, // "all" or "chosen"
    documents, // with "chosen": { "doc_8Hq2Lm4R": "suggester", ... }
    });
    res.redirect(303, redirect_url);
    });
  4. Send the browser on. The redirect_url returns the person to Proof, which finishes the sign-in and hands them back to their agent. The page they land on tells them they can close it.

List a person’s agents in the same Agents section, with what each reaches and a way to remove it.

const agents = await proof.users.listAgents("usr_5Kq2Lm8R");
// [{ id: "agt_Qe71", name: "Claude Code", host: "claude.ai", access: "all",
// connected_at: "2026-10-29T16:02:11Z", last_used_at: "2026-10-30T09:14:52Z" }]

Show the host beside the name: two agents can share a name, but not a host.

Remove an agent. Its tokens stop working at once, mid-task too.

await proof.agents.revoke("agt_Qe71");

Narrow what it reaches. Switch it to chosen documents, then give it a role in each:

await proof.agents.update("agt_Qe71", { access: "chosen" });
await proof.agents.putDocument("agt_Qe71", "doc_8Hq2Lm4R", { role: "suggester" });
await proof.agents.deleteDocument("agt_Qe71", "doc_3pXw9KdA");

A good place for per-document choices is your Share dialog: a row for each of the person’s agents, with the same roles you offer people. Whatever you set, the agent never gets more than its person’s own role in that document. See Share documents.

Two webhook events keep your Agents section current without polling:

  • agent.connected when someone approves an agent they hadn’t connected before.
  • agent.revoked when an agent is removed, by your call or because its user was deleted.

See Send notifications for receiving events, and the event reference for their fields.

Agents get Proof’s document tools: listing, searching, reading, writing in Markdown, suggesting, and commenting. They’re told to suggest unless their person asked them to edit directly. Each edit is credited with the agent’s name and its person’s, such as “Claude Code (Ada Lovelace’s agent)”, in the margin, in the record and in the seal.

Folders, tags and anything else your app keeps itself aren’t in Proof’s tools. To give agents those, register tools of your own, which Proof’s server lists beside its own and forwards to you; see Add your own agent tools.