> Markdown version of https://agledger.ai/docs/guides/mcp-server/
> Full index of this site for AI assistants: https://agledger.ai/llms.txt

# MCP Server

The AGLedger MCP server connects any MCP-compatible agent runtime (Claude Desktop, Cursor, Windsurf, ChatGPT MCP, Gemini) to your AGLedger Server. It is a thin pass-through: the agent calls a small set of MCP tools, and those tools relay HTTP calls to the API and forward the responses verbatim. Nothing about the record lifecycle lives in the server - it carries the agent's calls and the API's guidance back and forth.

Use this guide when you want an agent to notarize through MCP rather than calling the HTTP API directly, or rather than embedding the SDK. For endpoint and field-level detail, the [API reference](/api/) is canonical - this page covers only the MCP setup and the tool surface.

The package is `@agledger/mcp-server`, pinned here at **v2.10.0**. It requires Node.js 24 or newer and speaks MCP over stdio.

## 1. Install the server

```bash
npm install -g @agledger/mcp-server@2.10.0
```

On Linux hosts where the global npm prefix is root-owned (the NodeSource default), `npm install -g`
fails with `EACCES` - run it with `sudo`, or point npm at a user-writable prefix first
(`npm config set prefix ~/.npm-global`).

This installs the `agledger-mcp` binary. Confirm it runs and points at the version you expect:

```bash
agledger-mcp --help
```

```
AGLedger MCP Server v2.10.0

Usage: agledger-mcp --api-key <key> --api-url <url>

Options:
  --api-key, -k     AGLedger API key (or AGLEDGER_API_KEY env var). Required.
  --api-url, -u     Base URL of your AGLedger instance (or AGLEDGER_API_URL env
                    var). Required: AGLedger is self-hosted, so there is no
                    default server to call.
  --help, -h        Show this help message

Tools:
  agledger_discover   Returns API health, your identity, and available scopes
  agledger_api        Make any AGLedger API call; the API guides you via nextSteps
  agledger_verify     Verify an audit export offline (hash chain + signatures)

Exit codes: 0 clean, 1 runtime failure, 2 usage or configuration error.
```

The server needs two things: an API key and the base URL of your Server. Both can come from flags or environment variables - `AGLEDGER_API_KEY` and `AGLEDGER_API_URL` - and both are required. The key is a Bearer credential the server attaches to every call; mint one as described in [Authentication](/docs/guides/authentication/). If either is missing the server prints an error and exits with code 2 rather than guessing - AGLedger is self-hosted, so there is no default Server to fall back to.

## 2. Wire it into your MCP client

Add the server to your client's MCP configuration. For Claude Desktop this is `claude_desktop_config.json`; other clients use the same `mcpServers` shape.

```json
{
  "mcpServers": {
    "agledger": {
      "command": "agledger-mcp",
      "args": ["--api-url", "https://agledger.example.com"],
      "env": {
        "AGLEDGER_API_KEY": "agl_agt_…"
      }
    }
  }
}
```

Keep the API key in the `env` block, not in `args` - process arguments are visible in process listings, environment variables are not. Restart the client so it spawns the server. On connect the client lists three tools (`agledger_discover`, `agledger_api`, `agledger_verify`) and two resources (`agledger://openapi` and `agledger://llms.txt`).

## 3. The tools

The server exposes exactly three tools. The agent calls `agledger_discover` once to orient, drives the record lifecycle through `agledger_api`, and verifies an export with `agledger_verify`.

### `agledger_discover`

No arguments. Returns your Server's health, the calling identity (`GET /v1/auth/me`), the available scope profiles, and a four-step quickstart. The agent calls this first - the response tells it who it is, what scopes it holds, and what to do next. It is read-only and makes no changes.

### `agledger_api`

The workhorse. Makes any AGLedger API call and forwards the response unmodified. Four arguments:

| Argument | Type | Notes |
|---|---|---|
| `method` | `GET` / `POST` / `PUT` / `PATCH` / `DELETE` | required |
| `path` | string | must start with `/`; all routes use the `/v1/` prefix |
| `params` | JSON-encoded string (or native object) | optional. For `GET`/`DELETE` it becomes query parameters; for `POST`/`PUT`/`PATCH` it becomes the JSON body |
| `idempotencyKey` | string | optional. Dedup key for `POST` calls, max 256 characters; one is generated per call when omitted, so pass your own only when retrying a call that may have already reached the Server |

The agent notarizes through this one tool. The tool's own description hands the agent the lifecycle, and every API response carries `nextSteps` and (on errors) a `suggestion` field, so the agent self-guides:

1. `GET /v1/schemas` - list the available record types (contract types).
2. `GET /v1/schemas/{type}` - read the required fields and examples for the type.
3. `POST /v1/records` - notarize a record. For a notarize-only type this terminalizes the record at `RECORDED` in that one signed call.
4. `POST /v1/records/{id}/completions` - submit a Completion (the performer's evidence) when the work is done, for types that run the gated lifecycle.

A bad path returns a structured `PATH_INVALID` error; malformed `params` returns `INVALID_JSON`; a slow Server returns `TIMEOUT`; a connectivity failure returns `NETWORK_ERROR`. API-origin errors (a `403` for a missing scope, a `422` for a schema violation) are forwarded byte-for-byte - the API owns that guidance, and the agent reads the `suggestion` field to recover.

### `agledger_verify`

Verifies a record audit export entirely offline. The agent first pulls the export through `agledger_api` (`method=GET`, `path=/v1/records/{id}/audit-export`), then passes the response body as the `export` argument.

| Argument | Type | Notes |
|---|---|---|
| `export` | JSON-encoded string (or native object) | required. The body from `GET /v1/records/{id}/audit-export` |
| `publicKeys` | JSON-encoded string (or native object) | optional. Map of `signingKeyId` → SPKI-DER base64 public key, merged over any keys embedded in the export |
| `requireKeyId` | string | optional. Reject any entry not signed by this key, even if otherwise valid |
| `requireOutOfBandKeys` | boolean | optional. Refuse keys embedded in the export; an entry whose only key is export-embedded fails `CHAIN_KEY_POLICY_VIOLATION`, forcing verification against keys supplied via `publicKeys` |

The verifier decodes each entry's tagged COSE_Sign1 envelope (RFC 9052) over its in-toto v1 Statement payload, recomputes the SHA-256 over the envelope bytes, walks the hash chain, cross-checks the protected-header chain claim against the row columns, and verifies every envelope signature (Ed25519 or ES256, dispatched from the trusted key material; anything else fails closed). It makes **no network calls** - trust is rooted in the signing keys embedded in the export (or supplied via `publicKeys`). On a clean export it returns `valid: true` with a `signatureCoverage` breakdown; on a tampered or broken export it returns `valid: false` with `brokenAt`, which pinpoints the first failing entry and a machine-readable `code` from the SCREAMING_SNAKE `FailureCode` taxonomy (`CHAIN_HASH_MISMATCH`, `CHAIN_LINK_BROKEN`, `CHAIN_COSE_DECODE_FAILED`, `CHAIN_SIGNATURE_INVALID`, and so on).

Do not read `valid: true` alone as "every entry was cryptographically signed" - surface `signatureCoverage` (`{ signed, unsigned, skipped, total }`) so an unsigned-but-chain-intact entry is not mistaken for a signed one.

### The `agledger://openapi` resource

Alongside the tools, the server exposes the live OpenAPI spec as an MCP resource. Clients surface it in their resource picker, so the agent (or operator) can pull the full route catalog into context without constructing a call. The resource proxies `GET /openapi.json` at read time - it is always current with the running Server, never a stale local copy. A second resource, `agledger://llms.txt`, proxies the Server's `GET /llms.txt` the same way: the prose orientation the API writes for agents, worth reading before the spec.

## Verify it worked

After wiring the config, have the agent call `agledger_discover`. A healthy connection returns your identity and scopes; the `identity` block mirrors `GET /v1/auth/me` and should show your role and `authType`. If `health` carries an `error` instead, the server reached your runtime but could not reach the API - check `--api-url` and that the key is valid.

{/* TESTBED: validate the agledger_discover result shape end-to-end against a live Server (health / identity / scopeProfiles / quickstart / docs blocks). */}

## Failure modes

| Symptom | Cause | Fix |
|---|---|---|
| Server exits immediately on start (code 2) | No API key or no API URL supplied | Set `AGLEDGER_API_KEY` in the client's `env` block (or pass `--api-key`), and pass `--api-url` or set `AGLEDGER_API_URL` |
| `agledger_discover` returns `health.error` | API URL wrong or Server unreachable | Confirm `--api-url` resolves from where the client runs; try `GET /health` directly |
| `agledger_api` returns `NETWORK_ERROR` | DNS or transport failure to the API | Check the URL; the suggestion points you at `GET /health` |
| `agledger_api` returns `TIMEOUT` | Server slow to respond (default 30s) | Retry; the request is forwarded as-is, so retries are safe for idempotent calls |
| `agledger_api` returns a `403` body | Key lacks the scope for the call | Mint a key with the right scope profile (see [Authentication](/docs/guides/authentication/)) |
| `agledger_api` returns `INVALID_JSON` | `params` was not a JSON object | Pass `params` as a JSON-encoded object string, e.g. `{"type":"notarize-generic-v1"}` |
| `agledger_verify` returns `INVALID_EXPORT` | Input was not an audit export | Fetch it first with `GET /v1/records/{id}/audit-export`, then pass the response body |
| `agledger_verify` returns `UNSUPPORTED_FORMAT` | Pre-2.0 export format | Re-export the chain from a v0.23.0+ Server |

## Air-gapped operation

The MCP server has no runtime dependency on the AGLedger website, Docker Hub, or npm once it is installed. Every flow above works against a Server on your own network with no outbound internet.

**Install without npm at runtime.** On a connected staging host, fetch the package tarball once and carry it across the boundary:

```bash
npm pack @agledger/mcp-server@2.10.0
```

This produces `agledger-mcp-server-<version>.tgz`. Transfer it into the air-gapped environment and install from the file - no registry contact:

```bash
npm install -g ./agledger-mcp-server-<version>.tgz
```

**Point it at your internal Server.** Set `--api-url` (or `AGLEDGER_API_URL`) to your in-network Server. All three tools then operate without leaving the network: `agledger_discover` and `agledger_api` call only that Server, and `agledger_verify` is offline by construction.

**Verification needs no Server at all.** Because `agledger_verify` makes no network calls and roots trust in the keys embedded in the export, an auditor can verify a chain on a fully disconnected machine. Carry the audit-export JSON across the boundary, supply the expected signing keys via `publicKeys` (and `requireKeyId` to pin a key), and the verdict is self-contained. The same offline verification is also available as a standalone step - see [Verify offline](/docs/sdk/).

---

*Tool surface (`agledger_discover`, `agledger_api`, `agledger_verify`), the `agledger://openapi` and `agledger://llms.txt` resources, the CLI flags, and the offline-verifier failure classes are drawn from `@agledger/mcp-server` v2.10.0. Endpoint and schema detail is canonical in the [API reference](/api/); confirm response shapes against your own Server.*
