# MCP server

Give Claude Code, Claude Desktop or any MCP client a render_pdf tool backed by your account.

Page: https://legivel.com/docs/mcp

The MCP server lives at `https://api.legivel.com/mcp`. It speaks the Streamable HTTP transport, protocol version `2025-06-18`: POST-only, one JSON response per request, no SSE stream, no server-initiated messages, no sessions. Authentication is the same Bearer API key the REST API takes, and every tool call runs through the same handlers, so the same account, rate limits and documents per period apply.

## Connect a client

### Claude Code

Register the server once with the CLI. The `--header` value is the Authorization header sent on every request.

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

### Claude Desktop and other clients

Clients that read an `mcpServers` map take an HTTP entry with the URL and a static header:

```json
{
  "mcpServers": {
    "legivel": {
      "type": "http",
      "url": "https://api.legivel.com/mcp",
      "headers": {
        "Authorization": "Bearer lgv_your_key_here"
      }
    }
  }
}
```

Note

Any client that speaks Streamable HTTP with a static Authorization header works. The server does not implement OAuth, sessions or SSE streaming, so clients that require an SSE stream or server-initiated messages are not supported.

## Tools

`tools/list` returns two tools. Both take a JSON object as input.

### render_pdf

Render markdown to a PDF. Returns the PDF as a base64 resource, without a watermark.

| Name | Type | Description |
| --- | --- | --- |
| `markdown`required | string | The markdown source to render. |
| `language` | string | Optional document language, such as `en-GB`. Automatic hyphenation is enabled by default when a supported language is specified. Overrides the document's front-matter language. |
| `features.hyphenation` | boolean | Set `false` to disable automatic hyphenation. An omitted value uses front matter, then defaults to enabled. No language means no automatic hyphenation. See [language settings](https://legivel.com/docs/markdown#hyphenation). |
| `sign` | object \| boolean | Request a [customer seal](https://legivel.com/docs/sealing) with` {certificate: name}`,` {p12: base64, password}` or` {key, cert, chain?, password?}` as PEM text or Base64 DER. Optional reason, location and contact use the REST contract. `true` is refused with `invalid_request`; `false` or omission leaves the PDF unsealed. |
| `idempotency_key` | string | The equivalent of REST's `Idempotency-Key`. Follow the same[retry rules](https://legivel.com/docs/api/render#idempotency); sealed retries require the prepared-resource mode and valid credentials on each attempt. |

The result has one content item of type `resource`. Its `uri` is `legivel://render/latest.pdf`, its `mimeType` is `application/pdf`, and `blob` holds the PDF bytes as base64. Decode the blob to get the file. Each successful call costs one of the account's documents for the current period, except a successful idempotent replay.

A customer seal applies your certificate on your instruction, with the same plan, ownership, audit and failure rules as REST. The result includes` _meta["legivel/signature"] = "signed"` when the returned PDF carries the requested seal. Sealing failure never returns an unsealed PDF. The [customer seal guidance](https://legivel.com/docs/api/render#sealing) explains certificate trust and the limits of the feature's legal claims.

### get_usage

The current period's boundaries, the documents the plan includes, the documents granted, the plan limits and the documents used, split by API key; editor downloads are the remainder of `credits_used`. One optional argument, `period`: one of the account's usage period IDs as a decimal string, for an earlier period. The result has one content item of type `text` whose `text` is the JSON usage object, the same object [`GET /v1/usage`](https://legivel.com/docs/api/usage) returns:

```json
{
  "plan": "free",
  "period_start": "2026-09-09T00:00:00.000Z",
  "period_end": "2026-10-09T00:00:00.000Z",
  "credits_included": 100,
  "credits_granted": 0,
  "credits_used": 3,
  "limits": {
    "markdown_kb": 512,
    "resources_mb": 1.5,
    "pages_max": 20,
    "timeout_s": 30,
    "concurrency": 1,
    "requests_per_min": 5,
    "previews_per_hour": 120
  },
  "keys": [
    { "id": "<apikey.id>", "name": "CI", "credits": 2 }
  ]
}
```

## Raw JSON-RPC

You do not need an SDK. Each request is one JSON-RPC 2.0 message posted with the key in the Authorization header. The three calls below initialize, list the tools and render a small document.

```bash
# 1. initialize
curl https://api.legivel.com/mcp \
  -H "Authorization: Bearer $LEGIVEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "curl", "version": "0" }
    }
  }'

# 2. list tools
curl https://api.legivel.com/mcp \
  -H "Authorization: Bearer $LEGIVEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'

# 3. render a PDF
curl https://api.legivel.com/mcp \
  -H "Authorization: Bearer $LEGIVEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "render_pdf",
      "arguments": { "markdown": "# Hello\n\nRendered over MCP." }
    }
  }'
```

The third call answers with the PDF as a base64 resource:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "resource",
        "resource": {
          "uri": "legivel://render/latest.pdf",
          "mimeType": "application/pdf",
          "blob": "JVBERi0xLj…"
        }
      }
    ]
  }
}
```

The server also accepts `ping`, which returns an empty result. Batches work: post a JSON array of requests and you get an array of responses back. A request with no `id` is a notification; a body that contains only notifications is acknowledged with HTTP 202 and no body.

## Errors

Failures inside a tool call are tool results, not protocol errors, so the calling agent can read them and decide what to do. Everything else follows JSON-RPC and HTTP.

| Where | Shape | Cases |
| --- | --- | --- |
| Tool result | `isError: true`, one text item reading `<code>: <message>` | `quota_exhausted`, `rate_limited`, `queue_full`, `render_timeout`, `too_many_pages`, `render_unavailable`, `quota_unavailable`, `render_failed`, `empty_document`; also a missing or non-string `markdown` argument |
| JSON-RPC error | `error.code` on a 200 response | `-32602` unknown tool, `-32601` unknown method |
| JSON-RPC error | `-32700` with HTTP 400 | Body is not valid JSON |
| HTTP 401 | `{"code":"unauthorized",…}` | Missing or invalid Bearer token |
| HTTP 405 | `{"code":"method_not_allowed",…}`, `Allow: POST` | Any method other than POST, including GET and DELETE |

[Errors and quota](https://legivel.com/docs/errors) explains what each code means, and [Limits](https://legivel.com/docs/limits) carries the numbers behind each plan.

Tip

Usage from MCP calls is attributed to the API key the client was configured with, so give each agent its own key. `get_usage` then shows each agent's share under `keys`.
