# MCP Integration for AI Agents (/en/docs/integration/mcp)



Herald ships an official MCP (Model Context Protocol) server, so your AI agent can query your realm's data with structured tool semantics instead of reading REST docs and hand-writing calls. Authorization is OAuth: you sign in once with your Herald account in the browser, and the agent holds user credentials — no API key, no client id, no secret to paste anywhere.

The endpoint is a standard MCP Streamable HTTP endpoint (protocol version 2026-07-28) served by your Herald instance — the same origin as the REST API, no separate deployment. Herald's MCP server does **not** accept API keys.

## 1. Get Your Tenant URL [#1-get-your-tenant-url]

Each realm has one canonical MCP address:

```text
https://<origin>/mcp/<realmId>
```

* `<origin>` — your Herald deployment origin (or the realm's configured custom domain)
* `<realmId>` — your realm id

The realm id is part of the address: a token issued for one realm never works against another realm's URL.

## 2. Connect Your Agent Client (URL Only) [#2-connect-your-agent-client-url-only]

Your client needs the tenant URL and nothing else. Through RFC 9728 / RFC 8414 discovery and a controlled RFC 7591 registration endpoint, the client obtains the realm's preset public MCP client (`herald-mcp`) by itself.

> **Enable the realm's MCP access first**: the built-in Herald MCP client ships disabled. A realm admin must enable it once in the admin console's Client App list before an agent can register and authorize (see the per-realm switch in section 7).

**Claude Code** — verified end-to-end with Claude Code 2.1.269: registration, browser authorization, tool discovery, and tool calls all work with the URL alone.

```bash
claude mcp add --transport http herald https://your-herald-host/mcp/your-realm-id
```

**VS Code** (`.vscode/mcp.json`, or via MCP: Add Server → HTTP) — consumes the same discovery and registration contracts verified with Claude Code (checked against VS Code 1.140); the interactive OAuth handshake in VS Code itself is still pending end-to-end verification.

```json
{
  "servers": {
    "herald": {
      "type": "http",
      "url": "https://your-herald-host/mcp/your-realm-id"
    }
  }
}
```

Other MCP clients that implement the MCP authorization specification (protected-resource discovery, browser authorization with PKCE, dynamic client registration) should work the same way — pass them the tenant URL only.

## 3. Sign In Once in the Browser [#3-sign-in-once-in-the-browser]

On first connect, the client opens Herald's login page in your browser. Sign in as usual — including any consent prompt or second factor your realm enforces. After the browser redirects back, the tools are available.

Closing the browser before completing sign-in simply issues no credentials; reconnect from the client and try again. If your authorization later expires or is revoked, the client asks you to re-authorize the same way.

## 4. The Nine Read-Only Tools [#4-the-nine-read-only-tools]

### Admin tools [#admin-tools]

These check the **authorizing user's RBAC permissions** — grant the user a role with the permission, not a key:

| Tool                       | What it does                                     | Required permission |
| -------------------------- | ------------------------------------------------ | ------------------- |
| `query_users`              | List or look up users in the realm               | `users.view`        |
| `get_points_balance`       | A user's realm-wide points balance               | `points.view`       |
| `list_points_transactions` | A user's points history, newest first            | `points.view`       |
| `list_audit_logs`          | The realm's audit events                         | `audit.view`        |
| `get_realm_config_status`  | Which configs exist and whether they are enabled | `settings.view`     |

The admin balance tool returns the target user's balance **across the whole realm**, not per client app. Config status never returns configuration values.

### Self tools [#self-tools]

These operate on **the authorizing user only**. They take no `userId` — asking one to query someone else is rejected:

| Tool                          | What it does                              | Required scope           |
| ----------------------------- | ----------------------------------------- | ------------------------ |
| `get_my_profile`              | Your profile                              | `mcp:profile:read`       |
| `get_my_points_balance`       | Your points balance                       | `mcp:points:read`        |
| `list_my_points_transactions` | Your points history                       | `mcp:transactions:read`  |
| `list_my_subscriptions`       | Your subscriptions and entitlement status | `mcp:subscriptions:read` |

Any Herald user granted the scopes can call the self tools — no admin role needed.

### Shared conventions [#shared-conventions]

* Pagination is 1-based, default `page=1` / `pageSize=20`, max 100.
* Time filters accept RFC 3339 timestamps or `YYYY-MM-DD` (interpreted as UTC midnight).
* Outputs are deliberately narrow because agents may carry results into third-party models: no IP addresses, user agents, trace ids, config values, or payment metadata.
* Read-only is deliberate. Write operations are excluded and only arrive tool-by-tool after a per-tool security review. For writes or the full field surface, call the REST API with a client credential instead — see the [third-party backend integration guide](/docs/integration/third-party-backend) and the [OpenAPI reference](/docs/api).

## 5. Connectivity Self-Check [#5-connectivity-self-check]

After connecting, ask the agent:

> Use the herald `get_my_profile` tool.

It takes no arguments, reads only your own profile, and needs nothing beyond the profile scope — no admin permissions. A successful result echoing your id and email proves the whole chain works: discovery, client registration, browser authorization, and data access.

## 6. Troubleshooting [#6-troubleshooting]

| Symptom                                                                         | Cause                                                                                                 | Recovery                                                                                                                            |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Tool returns `permission_denied` — "Your user does not have the 'X' permission" | Admin tools check the authorizing user's RBAC                                                         | Ask your realm administrator to grant you a role with that permission; re-login does not help                                       |
| HTTP 403 with an `insufficient_scope` challenge                                 | A self tool's scope was not granted to your authorization                                             | Re-authorize in the agent client and accept the requested scopes                                                                    |
| HTTP 401 authorization challenge on every call                                  | Credentials missing, expired, or revoked — or the realm's MCP access was disabled                     | Re-authorize; if the realm disabled MCP, ask your realm administrator to re-enable it, then re-authorize (old grants do not revive) |
| HTTP 404 on connect                                                             | Wrong tenant URL                                                                                      | Check `https://<origin>/mcp/<realmId>` — realm id included, no trailing slash                                                       |
| `invalid_target` / `invalid_scope` errors                                       | The authorization request was tampered with or the client is misconfigured                            | Remove and re-add the server with the tenant URL only                                                                               |
| Tool returns `not_found`                                                        | The target id does not exist — or belongs to another realm (cross-realm ids always read as not found) | Check the id; an agent cannot query outside the authorized realm                                                                    |
| Tool returns `invalid_argument`                                                 | A malformed argument — the message names the field and the expected format                            | Follow the message and re-send with a corrected value (e.g. a valid UUID, `startTime` not after `endTime`)                          |
| Tool returns `internal_error`                                                   | Server-side failure                                                                                   | Retry; if it persists, check server logs                                                                                            |

## 7. Credentials, Isolation, and Limits [#7-credentials-isolation-and-limits]

* **Bidirectional credential isolation:** MCP credentials represent you but are separate from your browser and admin-console sessions — neither side accepts the other's tokens.
* **Long sessions:** clients renew through the standard refresh token grant; refresh tokens rotate and have an absolute lifetime.
* **Immediate revocation:** your MCP credentials stop working the moment your account is disabled, you are forced offline, or the realm's built-in MCP client app is disabled.
* **Realm on/off switch:** each realm has a built-in "Herald MCP" client app in the admin console's Client Apps list. Disabling it closes the realm's MCP access — existing and new authorizations are rejected. Re-enabling does not revive old grants; agents must re-authorize. The built-in client cannot be deleted.
* **Rate limit:** each authorized user is limited to 60 MCP requests per 60 seconds.
