# MCP server for AI agents

Source: https://versodocs.com/docs/mcp

Let Claude Code, Cursor, VS Code / Copilot, Claude Desktop, claude.ai or any MCP client read your organization's **approved** documents. Ops publishes v14 of the checkout spec; the developer asks *“pull the approved Checkout spec and implement section 3”* and the agent reads v14, never a draft.

> Endpoint: `https://versodocs.com/api/mcp`: remote, Streamable HTTP. The organization comes from the credential, so the URL carries none.

## Connect with one click (OAuth)

Recommended for people. Add the URL to your client; on first use it opens VersoDocs in your browser, you sign in, pick the organization and click **Allow**. No key to copy. Implements the MCP Authorization spec (2025-06-18).

### Claude Code

```bash
claude mcp add --transport http versodocs https://versodocs.com/api/mcp
```

1. Run the command in your terminal (add --scope project to share it with your repo).
2. In Claude Code, type /mcp, pick versodocs and choose Authenticate.
3. Your browser opens VersoDocs. Sign in and click Allow.

### Cursor

1. Settings → MCP → Add new MCP server → type "Streamable HTTP" (or "url"), URL https://versodocs.com/api/mcp.
2. Click Connect (or Needs login) next to versodocs.
3. Sign in to VersoDocs in the browser window and click Allow.

### VS Code / Copilot

1. Command Palette → "MCP: Add Server…" → HTTP → paste https://versodocs.com/api/mcp → name it versodocs.
2. Start the server; VS Code asks to authenticate. Click Allow.
3. Sign in to VersoDocs in the browser and click Allow.

### Claude Desktop / claude.ai

1. Settings → Connectors → Add custom connector.
2. Name it VersoDocs and paste https://versodocs.com/api/mcp.
3. Click Connect, sign in to VersoDocs and click Allow.

The consent screen shows which app is asking, an organization picker if you belong to several, what the app can and can't do, your plan's limits, the [scopes](https://versodocs.com/docs/agents.md#scopes) to grant (on Team: **review proposals** and **propose changes**, the latter only if you can edit, 25 agent proposals a month per organization; on Pro also **manage docs & folders** and **publish approved proposals**, unchecked by default) and, on Pro, an optional single-workspace limit.

- The app acts **as you**: it sees only workspaces you can open, and stops working if you leave the org.
- Each connected app (per app, member and org) counts as one key toward the plan's limit (Free 1, Team 5, Pro 20). Reconnecting the same app replaces the old connection.
- Admins see and disconnect apps under **Settings → Developer → Connected AI apps**.

## Connect with an API key

For CI, scripts and clients without OAuth. Use a key from **Organization settings → Developer**, the same keys as the [REST API](https://versodocs.com/docs/api.md). The config-file snippets read the key from an environment variable or a secure prompt, so it never lands in a committed file.

### Claude Code

Run in your terminal (add --scope project to share with your repo, minus the key)

```bash
claude mcp add --transport http versodocs https://versodocs.com/api/mcp --header "Authorization: Bearer vsd_YOUR_KEY"
```

### Cursor

.cursor/mcp.json (set VERSODOCS_API_KEY in your environment)

```json
{
  "mcpServers": {
    "versodocs": {
      "url": "https://versodocs.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${env:VERSODOCS_API_KEY}"
      }
    }
  }
}
```

### VS Code / Copilot

.vscode/mcp.json (VS Code prompts for the key once and stores it securely)

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "versodocs-key",
      "description": "VersoDocs API key",
      "password": true
    }
  ],
  "servers": {
    "versodocs": {
      "type": "http",
      "url": "https://versodocs.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${input:versodocs-key}"
      }
    }
  }
}
```

Claude Desktop and claude.ai connect with OAuth only (custom connector), with no `mcp-remote` bridge or key needed.

## Tools

30 tools. `tools/list` shows a tool only when the credential **holds its scope**, so an agent never sees write tools it wasn't granted. A held tool outside the org's plan (e.g. Team reads on Free, or after a downgrade) stays listed with a plan note in its description; calling it returns an `isError` result naming the plan that unlocks it and the upgrade URL.

| Tool | What it does | Plan |
| --- | --- | --- |
| `search_docs` `{query, limit?}` | Find published (approved) VersoDocs documents (specs, runbooks, policies) matching a query. Returns titles, paths, current version and ids; read one with get_doc. Full-text search on Team/Pro; on Free it matches titles and paths only. | All plans |
| `list_docs` `{folder?, updated_since?, cursor?, limit?}` | List published (approved) documents, most recently updated first. Filter by folder path (`<workspace>` or `<workspace>/<folder>`, includes subfolders) and/or updated_since to see what changed recently. Paginate with the returned cursor. | All plans |
| `get_doc` `{path_or_id, version?, format?, section?}` | Read a document’s approved content: the Current published version by default (never drafts). Returns Markdown with a header (title, version, published date, URL, citable reference). Pass `section` (a heading, e.g. "3" or "Payment flow") to read just that part of a long spec; `version` reads an older published version (Team/Pro). | All plans |
| `get_doc_tree` `{workspace?}` | The workspace → folder → document hierarchy of published docs, with paths you can pass to get_doc. Optionally limit to one workspace (its slug). | Team+ |
| `list_versions` `{path_or_id}` | Version history of a document: every published (approved) version, newest first, with date, author and citable reference. Use diff_versions to see what changed between two. | Team+ |
| `diff_versions` `{path_or_id, from?, to?}` | What changed in a document between two published versions: a summary plus a unified Markdown diff. `from` defaults to the version before `to`; `to` defaults to Current. Ideal for "what changed in the spec since v12?" (from: 12). | Team+ |
| `check_reference` `{reference}` | Check whether a cited spec version is still the Current approved one. Pass a reference `versodocs:<org>/<id>@vN` or a VersoDocs doc URL with `?v=N` (or a block of text containing several, e.g. a PR description). Reports current / outdated / ahead per reference. | All plans |
| `list_attachments` `{path_or_id}` | Files attached to the Current version of a document (PDFs, images, exports…) with their ids, names and sizes. Fetch one with get_attachment_url. | Team+ |
| `get_attachment_url` `{path_or_id, attachment_id}` | A short-lived (5 minute) signed download URL for one attachment of a document’s Current version. Download it right away; do not store or share the link. | Team+ |
| `propose_change` `{path_or_id, title, description?, content_markdown?, edits?, reviewers?}` | Open a proposal to change a document. It goes to human review in VersoDocs and is NEVER published by this tool; reviewers approve and publish it. Send EITHER content_markdown (the COMPLETE new document) OR edits: a list of { find, replace } applied in order to the Current Markdown (each find must match exactly once; read the doc with get_doc first and copy text exactly). Prefer edits for small changes to long docs. Images: `![alt](https://…)` embeds an image by URL; to add a new image or file, call upload_attachment first and paste the Markdown it returns. Existing images and attachments (as get_doc shows them) are kept when you keep their Markdown. Optionally request reviewers by email. | Team+ · docs:propose |
| `upload_attachment` `{path_or_id, filename, content_type, data_base64, alt?}` | Upload an image (PNG, JPEG, GIF, WebP) or file (PDF, Word, Excel, PowerPoint, CSV, text, Markdown, JSON, ZIP) of up to 5.0 MB, base64-encoded, for a document you will propose a change to. Returns a Markdown snippet: put it in content_markdown or an edit's replace of propose_change / update_proposal to embed it. Nothing is published by this tool; an upload no proposal uses is deleted after a day. Larger files: use the REST API's presigned upload. | Team+ · docs:propose or docs:write |
| `get_context_pack` `{workspace?, folder?, max_chars?}` | Every approved (Current) document in scope concatenated as Markdown, with an index and a header per doc (path, version, approvers, publish date, link). Use it to ground a task in the whole spec set at once; narrow with `workspace` or `folder` and cap size with `max_chars` (default 200000). | Team+ |
| `list_links` `{proposal_id?, path_or_id?}` | Jira / Linear issues linked to a document or a proposal: key, provider, cached title and status, URL. | Team+ |
| `link_issue` `{proposal_id?, path_or_id?, url}` | Link a Jira Cloud or Linear issue (paste its URL) to a proposal or a document, so reviewers see what the change implements. Proposals need the proposals:review scope, documents docs:write. | Team+ · proposals:review or docs:write |
| `list_proposals` `{status?, document?, limit?}` | Proposals (suggested changes under review) on documents you can see, newest first. Default: open ones (in review, changes requested, approved). Filter by status or by document. Read one with get_proposal. | Team+ · proposals:review |
| `get_proposal` `{proposal_id, include_content?}` | One proposal: status, the diff against the version it forked from, reviewers and their verdicts, advisory AI reviews and the comment threads (with comment ids for replies). Set include_content to also get the full proposed Markdown (needed before update_proposal edits). | Team+ · proposals:review |
| `comment_on_proposal` `{proposal_id, body, quote?, reply_to?}` | Leave a review comment on a proposal, posted as your user and labeled as coming from this agent. `quote` (exact text from one paragraph of the proposal) anchors it inline; `reply_to` (a comment_id from get_proposal) answers a thread; neither posts to the conversation. @mention people by email. | Team+ · proposals:review |
| `resolve_comment` `{proposal_id, comment_id, resolved?}` | Mark a comment thread on a proposal resolved (or reopen it with resolved: false). Any comment_id of the thread works. | Team+ · proposals:review |
| `submit_review` `{proposal_id, verdict, body?}` | Give your verdict on a proposal: approve, request_changes or comment (body required for the last two). Your verdict is ADVISORY: shown to reviewers as an AI review; it never counts as a required human approval and never publishes anything. | Team+ · proposals:review |
| `update_proposal` `{proposal_id, title?, description?, content_markdown?, edits?}` | Revise a proposal THIS agent opened (e.g. after review feedback): new title / description, and the content as either content_markdown (whole document) or edits ([{ find, replace }] applied to the proposal’s current text; read it with get_proposal include_content first). New images or files: upload_attachment, then paste its Markdown. Editing after approval dismisses the approvals (reviewers must look again). | Team+ · docs:propose |
| `close_proposal` `{proposal_id, reason?}` | Close (withdraw) a proposal THIS agent opened, without publishing. Participants are notified. | Team+ · docs:propose |
| `publish_proposal` `{proposal_id}` | Publish a proposal that human reviewers APPROVED: its text becomes the new Current version. Refused when it is not approved, is stale, breaks a workspace publish rule, or the workspace requires an interactive e-signature (a person must publish then). | Pro · proposals:publish |
| `create_doc` `{workspace, title, folder_path?, content_markdown?}` | Create a new document in a workspace (optionally inside a folder). Nothing is published: content_markdown, if given, opens a first proposal ("Initial version") for human review. | Pro · docs:write |
| `create_folder` `{workspace, name, parent_path?}` | Create a folder in a workspace, at the root or inside parent_path. | Pro · docs:write |
| `rename_doc` `{path_or_id, title}` | Change a document’s title (its URL slug follows). Content and versions are untouched. | Pro · docs:write |
| `move_doc` `{path_or_id, folder_path}` | Move a document to another folder of the SAME workspace ("" = workspace root). | Pro · docs:write |
| `delete_doc` `{path_or_id, confirm_title}` | PERMANENTLY delete a document and all its drafts and proposals. This cannot be undone. Only documents with NO published version can be deleted by an agent; approved documents must be deleted by a person in VersoDocs. confirm_title must repeat the exact title. Ask the user before calling this. | Pro · docs:write |
| `list_document_comments` `{path_or_id, version?, include_resolved?}` | Comment threads on a published document: the Current version by default, or `version`. Includes threads written on an older version and carried forward (marked with the version they came from). Open threads only unless include_resolved. Comment ids are for comment_on_document reply_to / resolve_document_comment. | Team+ |
| `comment_on_document` `{path_or_id, body, quote?, reply_to?, version?}` | Comment on a published document, posted as your user and labeled as coming from this agent. Start a thread with `quote` (exact text from one paragraph of the version, default Current; read it with get_doc), or answer one with `reply_to` (a comment_id from list_document_comments). @mention people by email. To suggest an edit, use propose_change instead. | Team+ · proposals:review |
| `resolve_document_comment` `{path_or_id, comment_id, resolved?}` | Mark a comment thread on a document resolved (or reopen it with resolved: false). Any comment_id of the thread works. | Team+ · proposals:review |

- `path_or_id` accepts a document id, the `path` shown by `list_docs`, a VersoDocs URL, or a reference `versodocs:<org>/<id>@vN`. A URL or reference that pins a version (`?v=12`, `@v12`) reads that version on Team+; on Free the agent gets Current and a note.
- `search_docs` is full-text on Team and Pro; on Free it matches titles and paths and says so. Explicit `get_doc` `version` needs Team.
- `propose_change` and `update_proposal` take the content as `content_markdown` (the whole document) **or** `edits`: `[{ find, replace }]` applied in order, each `find` matching exactly once (all or nothing). Prefer `edits` for small changes to long documents.
- Images and files in proposals: `upload_attachment` takes the bytes base64-encoded (up to 5.0 MB) and returns a Markdown snippet to paste into `content_markdown` or an edit. `![alt](https://…)` embeds an image by URL; images and attachments already in the document survive as long as their Markdown is kept. Larger files: the REST [presigned upload](https://versodocs.com/docs/api.md#upload-attachment).
- `proposal_id` accepts the proposal id or its VersoDocs URL. `get_proposal` returns the diff, reviewers, advisory AI reviews and comment ids (for `reply_to` / `resolve_comment`); `include_content` adds the full text.
- `list_document_comments` reads the comment threads on a published document (Current, or `version`; with `docs:read`), including threads carried forward from older versions with the version each was written on. `comment_on_document` (a `quote` from the version, or `reply_to`) and `resolve_document_comment` need `proposals:review`.
- Read tools carry `readOnlyHint: true`; `delete_doc` carries `destructiveHint: true` (every other tool `false`); every tool has `openWorldHint: false`.
- What agents may do, the safety rules (advisory verdicts, own proposals only, no e-signatures, guarded deletes) and the REST twin of each tool: [Agent workflows](https://versodocs.com/docs/agents.md).

## Example prompts

- “Pull the approved Checkout spec from VersoDocs and implement section 3.”
- “What changed in the Payments API spec since v12? Update the client to match.”
- “Is the spec referenced in this PR description still Current?”
- “Search VersoDocs for our retry policy and follow it in this change.”
- “Cite the spec version you implemented in a code comment.” Agents cite the `reference`; `check_reference` and the GitHub spec check later tell you when it moved on.
- Team / Pro: “Draft the API changes we just made back into the Payments spec and propose it for review by lead@acme.com.”
- Team / Pro: “Review the open proposals on the Payments spec against our API style guide; comment inline and leave a verdict.”
- Pro: “Address the review comments on my proposal, update it, and publish it once it’s approved.”
- Pro: “Create an `apis/v2` folder in eng and move the v2 specs into it.”

## Limits & protocol

- Rate limit: the key's or connected app's plan budget (30 / 120 / 600 req/min), **shared with the REST API**. A batch is charged one request per `tools/call`.
- Large documents: tool text is capped at about 60,000 characters. A longer document is truncated with a note and its heading outline, and the agent re-asks with `section`. Diffs and trees truncate the same way.
- Protocol: MCP `2025-06-18` (also negotiates `2025-03-26` and `2024-11-05`). Stateless, JSON responses only (no SSE, no session id). `GET` → 405. POST one JSON-RPC message or a batch (max 20); notifications → 202. Methods: `initialize`, `ping`, `tools/list`, `tools/call`.
- CORS is open (`*`) because auth is a bearer header, never a cookie.

## OAuth flow (for client authors)

Standard MCP Authorization: you only need this if you are building an MCP client.

1. `POST /api/mcp` without a token → `401` with `WWW-Authenticate: Bearer resource_metadata="https://versodocs.com/.well-known/oauth-protected-resource"`.
2. `GET /.well-known/oauth-protected-resource` (RFC 9728) → `resource` = `https://versodocs.com/api/mcp`, `authorization_servers` = `["https://versodocs.com"]`.
3. `GET /.well-known/oauth-authorization-server` (RFC 8414) → authorize `/oauth/authorize`, token `/api/oauth/token`, registration `/api/oauth/register`, revocation `/api/oauth/revoke`. `S256` only, public clients only.
4. `POST /api/oauth/register` (RFC 7591 dynamic registration) → `client_id`. Public clients (`token_endpoint_auth_method: none`); redirect URIs must be https, loopback http (`localhost`, `127.0.0.1`, `[::1]`, any port) or an app scheme such as `cursor://`. Throttled per IP.
5. Browser → `/oauth/authorize` with `response_type=code`, `client_id`, `redirect_uri`, `code_challenge`, `code_challenge_method=S256`, `state`, `resource=https://versodocs.com/api/mcp` and optional `scope` → sign in → consent → redirect with `code`, `state`, `iss` (deny → `error=access_denied`).
6. `POST /api/oauth/token` (form-encoded; JSON accepted) with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`, `code_verifier`.
7. Call `/api/mcp` with `Authorization: Bearer vsd_at_…`. Refresh with `grant_type=refresh_token`.

POST /api/oauth/token → 200:

```json
{
  "access_token": "vsd_at_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "vsd_rt_…",
  "scope": "docs:read"
}
```

| Credential | Lifetime | Notes |
| --- | --- | --- |
| Authorization code | 60 seconds | Single use; a replayed code revokes what it minted |
| `vsd_at_…` | 1 hour | Valid on `/api/mcp` only |
| `vsd_rt_…` | 30 days | Rotates on every use; reusing an old one revokes the whole grant |

Scopes (space-separated in `scope`; omitted = everything the plan allows, narrowed on the consent screen): `docs:read` (always), `proposals:review` (Team and Pro), `docs:propose` (Team and Pro, members with edit rights; on Team capped at 25 agent proposals per organization per calendar month, UTC), `docs:write` and `proposals:publish` (Pro, members with edit rights, opt-in). See [scopes per plan](https://versodocs.com/docs/agents.md#scopes). Revoke with `POST /api/oauth/revoke` (RFC 7009).

## Security

- **Read-only by default.** A credential has `docs:read` unless given more scopes. `docs:propose`: on Team only one-click connections (acting as their member), 25 agent proposals a month per organization; on Pro API keys too, unlimited. `propose_change` opens a proposal your reviewers must approve; it never edits Current.
- **Reviews are advisory.** `submit_review` verdicts are shown as AI reviews and never count toward required approvals or trigger auto-publish.
- **Publishing needs people.** `publish_proposal` (Pro, `proposals:publish`) only publishes proposals humans approved, never with an override, and is refused when the workspace requires an e-signature. Agents revise or close only their own proposals.
- **Guarded deletes.** `delete_doc` (Pro, `docs:write`) only removes documents with no published version, and needs the exact title in `confirm_title`.
- **Published content only.** Drafts and proposals in progress never leave VersoDocs; archived, unpublished and out-of-scope documents read as not found.
- **Connected apps act as their member**: same workspace visibility, and a member demoted to viewer loses `docs:propose`, `docs:write` and `proposals:publish` immediately. Their comments and actions are labeled with the app name.
- **Scope.** Free credentials see the org's active workspace only; a Pro workspace-scoped key sees just its workspace, as on the REST API.
- **OAuth hardening.** PKCE S256 mandatory, `state` required, exact redirect-URI matching (loopback may change port), only SHA-256 hashes of codes and tokens stored, `iss` on responses (RFC 9207), consent page cannot be framed. Tokens are never logged.
- **Revocation is immediate**: revoke a key or disconnect an app in Settings → Developer. Use one key per developer or tool.
- **Auditing**: on Pro, MCP usage is sampled into the audit log (`api.request`, endpoint `POST /api/mcp`), proposals are logged as `api.proposal_created`, and agent writes (verdicts, edits, publishes, docs & folders) carry the credential that made them.
