# Connect AI tools with MCP

> Use the BatteryPass integration API from Claude Code, Cursor, VS Code and other MCP clients with an organisation API key.

BatteryPass runs a **Model Context Protocol (MCP) server** at `https://<your BatteryPass host>/mcp`. An AI tool that
speaks MCP connects to it with an API key of your organisation and gets one tool for each operation of the
integration API: it can look up battery models, units, values and their completeness, read passports and their
versions, follow the change feed, export BatteryPass JSON, PDFs and QR codes, and, with the right scope, report
condition data and lifecycle events or map and validate spreadsheet imports.

Use it when you want an assistant to answer questions about your data ("Which units of model X still have open
fields?") or to prepare and send condition reports on your behalf. For scheduled, unattended integrations call the
REST API directly; the tools are the same operations.

> [!NOTE]
> The MCP server is part of every BatteryPass installation and needs no extra service. It speaks the Streamable HTTP
> transport (stateless, protocol version 2026-07-28; older clients that use the `initialize` handshake also work).

## Before you start

1. **Create an API key**

   An owner of your organisation opens **Settings → API keys** and creates a key for this one tool. Choose the
   scopes the tool needs: `read` to read data, `condition` to report condition data and lifecycle events, `import`
   to map, validate and read spreadsheet imports. The page also shows the setup snippets below with your server's
   address.

2. **Provide the key as an environment variable**

   The key is shown once: keep it in your password manager. The configurations below read it from the environment
   variable `BATTERYPASS_API_KEY` when the tool starts, so set the variable in the environment your tool starts
   from, for example in the current shell:

   ```bash title="macOS and Linux"
   export BATTERYPASS_API_KEY="bp_live_…"
   ```

   ```powershell title="Windows (PowerShell)"
   $env:BATTERYPASS_API_KEY = "bp_live_…"
   ```

3. **Add the server to your tool**

   Follow the tab for your tool below, then ask it to list the BatteryPass tools.

## Set up your tool

**Claude Code**

Add the server to your project's `.mcp.json`. Claude Code replaces `${BATTERYPASS_API_KEY}` from the environment
each time it starts, so neither this file nor Claude Code's configuration holds the key, and you can share the
file with your team:

```json title=".mcp.json"
{
  "mcpServers": {
    "batterypass": {
      "type": "http",
      "url": "https://passport.example.com/mcp",
      "headers": { "Authorization": "Bearer ${BATTERYPASS_API_KEY}" }
    }
  }
}
```

To add the server for your user in all projects instead, use the CLI with `--scope user` (without it, the server
is added for the current project on your machine only):

```bash title="Terminal"
claude mcp add --transport http --scope user batterypass https://passport.example.com/mcp \
  --header "Authorization: Bearer bp_live_…"
```

> [!WARNING]
> The CLI saves the header, key included, in plain text in Claude Code's configuration (`~/.claude.json`), and
> your shell may keep the command in its history. Writing `$BATTERYPASS_API_KEY` in the command does not avoid
> this: the shell replaces it before Claude Code sees it (and PowerShell needs `$env:BATTERYPASS_API_KEY`). Use
> `.mcp.json` to keep the key out of files.

Check the connection with `claude mcp list` or `/mcp` inside Claude Code.

**Cursor**

```json title=".cursor/mcp.json"
{
  "mcpServers": {
    "batterypass": {
      "url": "https://passport.example.com/mcp",
      "headers": { "Authorization": "Bearer ${env:BATTERYPASS_API_KEY}" }
    }
  }
}
```

Restart Cursor after you set the environment variable; the server then appears under **Settings → MCP**.

**VS Code**

VS Code asks for the key the first time it starts the server and keeps it in its secret storage:

```json title=".vscode/mcp.json"
{
  "inputs": [
    {
      "type": "promptString",
      "id": "batterypass-api-key",
      "description": "BatteryPass API key",
      "password": true
    }
  ],
  "servers": {
    "batterypass": {
      "type": "http",
      "url": "https://passport.example.com/mcp",
      "headers": { "Authorization": "Bearer ${input:batterypass-api-key}" }
    }
  }
}
```

**Other clients**

A client that only starts local (stdio) servers can reach BatteryPass through the `mcp-remote` bridge. Put the
whole header into an environment variable, because some clients split arguments at spaces:

```json title="Client configuration"
{
  "mcpServers": {
    "batterypass": {
      "command": "npx",
      "args": ["-y", "mcp-remote@0.15.2", "https://passport.example.com/mcp", "--header", "Authorization:${AUTH_HEADER}"],
      "env": { "AUTH_HEADER": "Bearer bp_live_…" }
    }
  }
}
```

> [!NOTE]
> Connectors in claude.ai, Claude Desktop and ChatGPT that can only sign in with OAuth cannot connect yet: the MCP
> server accepts API keys only. Use Claude Code, Cursor, VS Code or `mcp-remote` instead.

## The tools

Every operation of the published integration API is one tool, except the file upload `createImport`. Its name is the
operation's `operationId` in snake_case, so `listBatteryModels` in the API reference is the tool
`list_battery_models`. The tool's arguments are the operation's path and query parameters under their names and the
JSON request body under `body`; the descriptions come from the API reference and name the scope a tool needs.

| Scope         | Tools                                                                                                                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `read`        | Catalogue: `list_battery_models`, `get_battery_model`, `list_model_versions`, `get_model_version`, `list_batteries`, `get_battery`, `export_units`, `list_facilities`                                        |
| `read`        | Values and completeness: `list_model_version_values`, `list_model_version_value_history`, `get_model_version_completeness`, `list_battery_values`, `list_battery_value_history`, `get_battery_completeness`  |
| `read`        | Passports: `get_passport`, `list_passport_versions`, `get_passport_version`, `get_passport_version_diff`, `export_passport_json`, `export_passport_pdf`, `get_passport_qr`, `get_registry_submission_status` |
| `read`        | Lifecycle, documents and registry: `list_lifecycle_events`, `get_condition`, `list_evidence`, `get_evidence`, `download_evidence`, `get_registry_info`, `get_registry_schema`, `get_registry_status`         |
| `read`        | Change feed: `list_changes`                                                                                                                                                                                  |
| `condition`   | `update_condition` (report measured condition data), `create_lifecycle_event`                                                                                                                                |
| `import`      | `list_imports`, `get_import`, `update_import_mapping`, `validate_import`                                                                                                                                     |
| none (public) | `get_public_passport`, `get_public_passport_version`, `get_public_passport_by_product_id`, `verify_passport`, `download_public_passport_document`                                                            |

A tool only sees the data of the organisation that owns the key, exactly like a REST request with that key. Your
tool lists only the tools of the key's scopes; public passport reads are available to every key.

A tool cannot carry a file, so a spreadsheet is uploaded over REST with
[`createImport`](https://passport.kabin.sh/docs/en/api-reference/imports/create-import) and the same key; the assistant then maps and
validates the import with the tools ([Units from production](https://passport.kabin.sh/docs/en/integrations/workflows/units-from-production)).
Committing the import and approving its values stay with members in the portal.

How results come back:

- JSON responses are returned as text and as structured content.
- Lists return one page. The tool descriptions ask the assistant to pass a small `limit` and to call again with
  `cursor` set to `next_cursor` until it is null.
- Files (PDF, QR codes as PNG or SVG, spreadsheets, documents) are returned as embedded resources up to 1 MiB.
  Larger files fail with `result_too_large`; download them over REST with the key instead.
- An error of the API is a tool error with the Problem's `code`, `title`, `detail`, the field `errors` and the
  `request_id`, for example `not_found` or `validation_error`. Arguments that do not match the tool's schema fail with
  `invalid_arguments` before anything is sent.

> [!TIP]
> Writes are only the two `condition` tools and `update_import_mapping` and `validate_import`. They are recorded in
> your audit log with the key as actor and the note `channel: mcp`. After a condition report a published passport may
> be re-published right away, depending on your organisation's setting. Let your tool ask before it calls them.

## Security

- **One key per tool and person.** You can then see in the audit log who acted, and revoke one tool without
  touching the others.
- **Least scopes.** Give `condition` only to tools that must report measurements and `import` only to tools that
  prepare imports; a `read` key cannot change anything.
- **Revoke** a key on the API keys page as soon as a tool or laptop is no longer used. Revoking works at once.
- **Results are data, not instructions.** Values, notes and documents in BatteryPass come from your team, your
  suppliers and service partners. The server tells the assistant to treat them as data; still review what an
  assistant proposes to write.
- Never put a key into a repository, a URL or a chat message. The configuration files above read it from the
  environment; only the `claude mcp add` command and the `mcp-remote` configuration hold it in plain text.

## Troubleshooting

| Answer                                          | Cause and fix                                                                                                                                                                                                                                                                                                   |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401 unauthenticated`                           | No key, a mistyped key or a revoked key: the answer is the same for all three. Check that the environment variable is set in the environment the tool starts from, and that the header reads `Authorization: Bearer bp_live_…`.                                                                                 |
| `403 forbidden` on connecting                   | A browser-based client on another web origin sent an `Origin` header. Browser clients on other origins are not supported; use a desktop client or the `mcp-remote` bridge.                                                                                                                                      |
| `400` with the JSON-RPC error `-32600`          | The client sent a batch (several messages in one request). Send one message per request; current clients do.                                                                                                                                                                                                    |
| Tool error `403 forbidden`                      | The tool needs a scope your key does not have, for example `condition`. Create a key with that scope.                                                                                                                                                                                                           |
| `405 method not allowed`                        | Something sent a `GET` (or `DELETE`) to `/mcp`. The server answers `POST` only; update the client or choose the Streamable HTTP transport (`--transport http`).                                                                                                                                                 |
| `404 not found`                                 | The address is wrong, or the server's administrator turned MCP off (`MCP_ENABLED=false`).                                                                                                                                                                                                                       |
| `429 rate_limited` or tool error `rate_limited` | All keys of your organisation together may make 600 requests per minute, and each tool call counts twice (the MCP request and the API request it makes). Wait the `Retry-After` seconds (`retry_after_seconds` in tool errors). Repeated wrong keys from one address are limited to 30 attempts per 15 minutes. |

[**API reference**](https://passport.kabin.sh/docs/en/api-reference): Every operation behind the tools, with parameters, responses and error codes.

[**API keys**](https://passport.kabin.sh/settings/api-keys): Create and revoke keys in the BatteryPass portal (owners only).
