> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://documents.pyramid-ai.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://documents.pyramid-ai.com/_mcp/server.

# MCP Server

The Pyramid AI **MCP server** exposes your organization's document corpus over the
[Model Context Protocol](https://modelcontextprotocol.io) — an open standard for connecting
AI assistants to external tools and data. Any MCP-aware client (Claude, Cursor, or a custom
agent built on the MCP SDK) can search your documents and get back grounded answers with
source links, without you writing a line of integration code.

#### Search documents

Run hybrid semantic + keyword search and get back ranked documents with their source URLs.

#### Answer questions

Get a finished, cited answer synthesized from the most relevant passages in your corpus.

#### Stay grounded

Every result carries a source link, so your agent can always cite where an answer came from.

> **Note**
>
> MCP access is gated per organization. If your key returns `403 FEATURE_NOT_ENABLED`, ask your
> Pyramid AI account manager to enable the MCP server for your org.

## Endpoint

```
POST https://api.pyramid-ai.com/api/v2/mcp
```

| Environment | Base URL                                        | Key prefix   |
| ----------- | ----------------------------------------------- | ------------ |
| Production  | `https://api.pyramid-ai.com/api/v2/mcp`         | `pai_live_*` |
| Staging     | `https://api-staging.pyramid-ai.com/api/v2/mcp` | `pai_test_*` |

* **Transport:** Streamable HTTP, **stateless** — one `POST` per JSON-RPC exchange, no session to manage.
* Only `POST` is served; `GET` and `DELETE` return a JSON-RPC `405`.

## Connect in under 5 minutes

#### Get an API key

Create a key in the [Pyramid AI platform](https://platform.pyramid-ai.com), or use one you
already have. Production keys (`pai_live_*`) work against `api.pyramid-ai.com`; staging keys
(`pai_test_*`) against `api-staging.pyramid-ai.com`. See [Authentication](/authentication) for
key formats, environments, and rotation.

> **Warning**
>
> Store your API key securely — it is shown only once at creation time and cannot be retrieved later.

#### Add the server to your client

Point your MCP client at the endpoint and pass your key as a bearer token. See
[Connect your client](#connect-your-client) below for the exact config for Claude, Cursor, and the SDK.

#### Ask a question

Once connected, your agent can call the `search_documents` and `answer_question` tools directly.
Try: *"Search the corpus for bamboo scaffolding safety requirements."*

## Authentication

The MCP endpoint uses the **same bearer API-key auth as every other Pyramid AI API route** — no
OAuth flow. Send your key in the `Authorization` header, and because the transport is Streamable
HTTP, your client must advertise **both** content types in `Accept`:

```
Authorization: Bearer pai_live_YOUR_KEY_HERE
Content-Type: application/json
Accept: application/json, text/event-stream
```

Responses come back as a one-shot SSE frame (`event: message` / `data: <json-rpc>`). Most MCP
clients and SDKs handle this framing for you.

## Connect your client

### Claude Code

```bash
claude mcp add pyramid-corpus --transport http \
  https://api.pyramid-ai.com/api/v2/mcp \
  --header "Authorization: Bearer pai_live_YOUR_KEY_HERE"
```

### Claude Desktop / Cursor

Add the server to your `mcpServers` config (`claude_desktop_config.json` for Claude Desktop,
`.cursor/mcp.json` for Cursor):

```json
{
  "mcpServers": {
    "pyramid-document-corpus": {
      "type": "http",
      "url": "https://api.pyramid-ai.com/api/v2/mcp",
      "headers": { "Authorization": "Bearer pai_live_YOUR_KEY_HERE" }
    }
  }
}
```

### Custom agent (TypeScript SDK)

```ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const transport = new StreamableHTTPClientTransport(
  new URL('https://api.pyramid-ai.com/api/v2/mcp'),
  { requestInit: { headers: { Authorization: 'Bearer pai_live_YOUR_KEY_HERE' } } },
);

const client = new Client({ name: 'my-agent', version: '1.0' });
await client.connect(transport);

const result = await client.callTool({
  name: 'answer_question',
  arguments: { query: 'What are the requirements for road opening works?' },
});
```

### MCP Inspector (visual debugging)

```bash
npx @modelcontextprotocol/inspector
```

Choose transport **Streamable HTTP**, set the URL to the endpoint, and add the
`Authorization: Bearer …` header.

## Tools

The server exposes two tools. Both accept the same arguments — the difference is whether you want
raw hits to reason over yourself, or a finished, cited answer.

| You want                                     | Call               | Typical latency |
| -------------------------------------------- | ------------------ | --------------- |
| Ranked documents your own model reasons over | `search_documents` | \~5s            |
| A finished answer with `[n]` citations       | `answer_question`  | \~20–25s        |

### Shared arguments

| Argument        | Type         | Required | Notes                                                                                                                                                                                                                                                                                                                                         |
| --------------- | ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`         | string       | yes      | Natural-language question or keywords.                                                                                                                                                                                                                                                                                                        |
| `scope`         | enum         | no       | One of `technical_circulars`, `general_circulars`, `standards_and_codes`, `practice_notes`, `safety_health`, `publications`, `press_releases`, `everything`. **Omit it** to search all technical documents and auto-exclude noise — the right default for almost every query. See [Scope & broadening](#scope-and-broadening).                |
| `startDate`     | string (ISO) | no       | Inclusive lower bound, e.g. `2024-01-01T00:00:00Z`. Only set for an explicit time window — date filters drop undated documents, which are common in this corpus.                                                                                                                                                                              |
| `endDate`       | string (ISO) | no       | Inclusive upper bound; set only alongside `startDate`.                                                                                                                                                                                                                                                                                        |
| `versionPolicy` | enum         | no       | `current` (the default) or `all`. Many documents exist in several editions — the Schedule of Rates, the General Specification, codes of practice. By default only the **current edition** of each document is searched; set `all` to include superseded editions, e.g. to compare editions or read an earlier one. See [Editions](#editions). |

### `search_documents`

Hybrid (semantic vector + full-text BM25) search over the corpus, returning ranked documents
with their source links. The tool result is a JSON payload:

```json
{
  "query": "bamboo scaffolding",
  "scope": "technical_circulars",
  "requestedScope": "technical_circulars",
  "broadened": true,
  "hasResults": true,
  "count": 1,
  "results": [
    {
      "title": "DEVB TC(W) No. 1/2025",
      "url": "https://www.devb.gov.hk/tcw-1-2025.pdf",
      "snippet": "Guidance on bamboo scaffolding...",
      "score": 0.91,
      "edition": null,
      "versionStatus": "current",
      "publishedAt": "2025-01-01T00:00:00.000Z"
    }
  ],
  "versionPolicy": "current"
}
```

Always surface `results[].url` to the user — it is the canonical source link. When `hasResults`
is `false`, tell the user nothing relevant was found and ask them to refine; do **not** answer
from prior knowledge.

### Editions

Results come from the current edition of each document unless you pass `versionPolicy: "all"`.
Every result says which edition it is: `edition` (e.g. `"2026 Edition"`, or `null` when the
document has a single edition), `versionStatus` (`"current"` or `"superseded"`) and, for a
superseded edition, `supersededBy` (the title of the current one). When you show a result to a
user, name its edition; never present a `superseded` result as current. A date range
(`startDate`/`endDate`) returns documents published in that range and, for documents with
editions, the edition that was in force then.

### `answer_question`

Runs the same hybrid search, then synthesizes a complete written answer from the top passages,
with bracketed `[n]` citation markers. The answer is atomic — it arrives whole after \~20–25s
(MCP tool results cannot stream).

```json
{
  "query": "what are the requirements for road opening works?",
  "answer": "Road opening works must follow the permit conditions [1]...",
  "hasResults": true,
  "scope": "default",
  "requestedScope": "default",
  "broadened": false,
  "citations": [
    { "ref": 1, "title": "HyD TC No. 5/2024", "url": "https://..." }
  ],
  "sources": [
    { "ref": 1, "title": "HyD TC No. 5/2024", "url": "https://...", "snippet": "...", "score": 0.88 }
  ]
}
```

* `citations` is the subset of `sources` the answer actually references via its `[n]` markers.
* `sources` lists every passage the search retrieved, whether or not it was cited.
* The payload is also returned as `structuredContent` for SDK clients that prefer structured parsing.

## Scope & broadening

A narrow `scope` is a **relevance hint, not a hard filter**. When you pass a narrow technical
scope, the search runs over the broad technical pool so a wrong category guess can't hide the
right document — and the response sets `broadened: true` with your original choice preserved in
`requestedScope`. Results may therefore include other document types.

* `answer_question` will prefer the requested document type and add a caveat in the answer when
  it relies on another.
* `press_releases` and `everything` are exact — they are never broadened.

A `hasResults: false` response means nothing ranked as relevant; it does **not** prove the
document doesn't exist. Refine the query rather than concluding the corpus lacks coverage.

## Progress updates

MCP tool results don't stream, but `answer_question` emits coarse `notifications/progress` while
it works — **if** your call opts in with a `progressToken` in the request `_meta`:

```json
"params": {
  "name": "answer_question",
  "arguments": { "query": "..." },
  "_meta": { "progressToken": "t1" }
}
```

Stages: *Searching the document corpus…* (10/100) → *Retrieved N documents — generating answer…*
(40/100) → *Answer ready.* (100/100). SDK clients receive these via the `onprogress` callback on
`callTool`. Budget a 60-second client-side timeout for `answer_question`.

## Raw JSON-RPC (curl)

For testing or non-SDK clients, call the JSON-RPC API directly. The `Accept` header must advertise
both content types:

```bash
curl -sS https://api.pyramid-ai.com/api/v2/mcp \
  -H "Authorization: Bearer pai_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "search_documents",
      "arguments": { "query": "bamboo scaffolding safety requirements" }
    }
  }'
```

List the available tools with `"method": "tools/list"`.

## Troubleshooting

| Symptom                          | Cause & fix                                                                                                                   |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `401 UNAUTHORIZED`               | Missing/invalid bearer key, or a `pai_test_*` key against production (and vice versa). Check the key matches the environment. |
| `403 FEATURE_NOT_ENABLED`        | Your org doesn't have MCP enabled. Contact your account manager.                                                              |
| `406` / transport error          | The `Accept` header is missing `text/event-stream`. Add `application/json, text/event-stream`.                                |
| `answer_question` times out      | Client timeout is below \~30s. Raise it — generation takes \~20–25s.                                                          |
| Empty results with a `scope` set | Set `scope: "everything"` to include all document types, or drop the date filters.                                            |

## Next steps

#### [Authentication](/authentication)

API key format, environments, and rotation.

#### [Knowledge & Search](/concepts/knowledge-search)

How Pyramid AI retrieves and grounds answers in your documents.

#### [Query Your Knowledge Base](/howto/agent)

The REST equivalent of the MCP tools — single, chat, and batch queries.

#### [Error Handling](/error-handling)

Error codes, envelopes, and retry strategies.