MCP server

Verbatim AI is also a Model Context Protocol server, so an AI assistant (Claude, Cursor, an agent built on an MCP SDK…) can query your knowledge bases directly.

Verbatim AI is also a Model Context Protocol server, so an AI
assistant (Claude, Cursor, an agent built on an MCP SDK…) can query your knowledge bases directly.

EndpointPOST https://<api-host>/mcp
TransportStreamable HTTP, stateless — no MCP session, no Mcp-Session-Id, no server-sent stream
Authenticationan access token, as Authorization: Bearer <token> or X-Access-Token: <token>
Toolslist_corpora, rag_query, get_document_markdown

Authentication

Only access tokens open /mcp. On this path the Authorization: Bearer header carries an
access token — an RSA or Firebase JWT sent there is rejected. Everywhere else in the API,
Authorization: Bearer still means a JWT.

A missing, unknown, expired or revoked token is answered 401 Unauthorized (the rest of the API
answers 403).

Each tool requires the scope of the REST call it mirrors. A token without it can connect and list
the tools, but calling the tool returns a tool error (isError: true) and does nothing.

ToolMirrorsScope
list_corporaGET /v1/corpus/corpus:read
rag_queryGET /v1/post/qpost:read
get_document_markdownGET /v1/doc/{id}/mddoc:read

Mint a dedicated token, scoped to what the assistant needs:

POST /v1/auth/access-token/
Authorization: Bearer <your RSA JWT>
Content-Type: application/json

{"scope": ["corpus:read", "post:read", "doc:read"], "ttl": 86400}

The token expires like any other access token (ttl, capped by the platform); the client has to be
given a new one then. Revoke it with DELETE /v1/auth/access-token/{token}.

Tools

list_corpora

List the knowledge bases (corpora) of your organization — deleted ones excluded — so the assistant
can pick the corpusIds it passes to rag_query.

ArgumentRequiredDescription
pageSizenoCorpora per page, 1 to 100 (default 25)
pageIndexnoPage index, from 0 (default 0)
{
  "pageIndex": 0,
  "pageSize": 25,
  "items": [
    {"id": "…", "name": "Support knowledge base", "description": "…", "metadata": {}}
  ]
}

A full page means there may be more: ask for the next pageIndex. The tool declares an outputSchema describing every field and returns the result both as structuredContent and as JSON text content.

rag_query

Ask a question to your organization's knowledge bases. The answer is generated from the documents
of the corpora searched, exactly as GET /v1/post/q does.

Each call is independent: it opens a new thread over the corpora searched and records the
exchange (question + answer) in it, so it shows up in GET /v1/thread/ and GET /v1/post/ with
metadata.source = "mcp". Opening that thread needs no scope of its own — post:read is enough.

ArgumentRequiredDescription
questionyesThe question, in natural language
corpusIdsnoUUIDs of the corpora to search. Default: every corpus of the organization (deleted ones excluded)
langnoISO 639 language code of the answer (default en)
agentIdnoUUID of the agent to run the query on (default: the platform's default agent)

The tool declares an outputSchema (every field described, for the assistant to know what to do with it) and returns the result both as structuredContent and as JSON text content:

{
  "threadId": "550e8400-e29b-41d4-a716-446655440000",
  "corpusIds": ["…", "…"],
  "questionId": "…",
  "answerId": "…",
  "agentId": "…",
  "lang": "en",
  "answer": "The corpus mainly covers customer support runbooks…",
  "sources": [
    {"docId": "…", "filename": "runbook.pdf", "pages": [3, 4], "summary": "…"}
  ]
}

A corpus of another organization, an unknown corpus or agent, an organization without any corpus,
or a missing question returns a tool error, and nothing is written — not even the thread.

get_document_markdown

Read one whole document as Markdown — the text ingestion extracted from the original file, headings, lists and
tables included. Where rag_query answers from a few excerpts, this gives the assistant the full document: typically
one of the sources a rag_query answer cites, by its docId.

ArgumentRequiredDescription
docIdyesUUID of the document

Where GET /v1/doc/{id}/md hands out a presigned URL, the tool returns the content itself — an assistant cannot follow
a URL into storage. The result holds a single embedded resource whose mimeType is text/markdown:

{
  "content": [
    {
      "type": "resource",
      "resource": {
        "uri": "verbatim://doc/550e8400-e29b-41d4-a716-446655440000/md",
        "mimeType": "text/markdown",
        "text": "# Annual report 2025\n\nRevenue grew **12%** over the year…"
      }
    }
  ],
  "isError": false
}

The uri identifies the document; it is not fetchable — the server exposes no MCP resources. The whole conversion is
returned, with no truncation: a long document is a long result, which counts against the assistant's context.

A document that has not been converted yet — still PENDING or PROCESSING, or FAILED before conversion — returns a
tool error whose message carries its status: try again once it is READY. A document of another organization, an
unknown one or a missing docId returns a tool error too.

Connecting a client

Claude Code

claude mcp add --transport http verbatim https://<api-host>/mcp \
  --header "Authorization: Bearer <access-token>"

Any client with a JSON configuration (.mcp.json, Cursor, …)

{
  "mcpServers": {
    "verbatim": {
      "type": "http",
      "url": "https://<api-host>/mcp",
      "headers": {"Authorization": "Bearer <access-token>"}
    }
  }
}

Raw JSON-RPC, to check a token:

curl -s https://<api-host>/mcp \
  -H "Authorization: Bearer <access-token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Connectors that require OAuth (claude.ai custom connectors, for instance) are not supported yet:
the server takes a token you mint, it does not run an OAuth authorization flow.


Did this page help you?