Connecting via MCP

Service accounts, client credentials, and your first tool call.


Machine integrations authenticate as a service account using the OAuth client_credentials grant. No browser, no user session, no interactive consent.

1. Create a service account

An administrator does this from Admin → Service Accounts:

  1. Enter a display name that identifies the integration — "Jira Sync", not "api". It shows up in audit history.
  2. Press create. The client secret is shown exactly once; copy it immediately. If it is lost, use Reissue secret — it cannot be recovered.

The service account's id is the client_id, and it is safe to store in config. The secret is not — treat it like a password.

2. Grant it access to a brain

A new service account can authenticate but can see nothing. Access is granted per brain by an administrator, from Admin → Brains → the brain → its service-account access section. Note this is the admin brain page: the user-facing brain page manages user and group access, not service accounts.

Pick the service account and choose a level:

LevelCan do
readSearch and read. The right default for a retrieval agent.
read_writeAlso create, update and delete neurons and synapses.
ownerAlso manage access and brain settings.

Grant the least you need. An agent that only answers questions should be read — that way a prompt-injection attempt in retrieved content cannot lead to writes.

3. Get an access token

POST /brain/{brain_id}/token. Form-encoded or JSON both work. The access token is a bearer JWT valid for 1 hour; the refresh token lasts 30 days.

curl -X POST https://hive.example.com/brain/$BRAIN_ID/token \
  -d grant_type=client_credentials \
  -d client_id=$HIVE_CLIENT_ID \
  -d client_secret=$HIVE_CLIENT_SECRET
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOi..."
}

To rotate without re-sending the secret, post grant_type=refresh_token with the refresh_token. Refresh tokens rotate on use — store the new one.

4. Call a tool

The MCP endpoint is POST /brain/{brain_id}/mcp, JSON-RPC 2.0 over the streamable-HTTP transport. Send the access token as a bearer header and accept both content types:

curl -X POST https://hive.example.com/brain/$BRAIN_ID/mcp \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "aiqbee_search",
      "arguments": {"query": "what did we decide about rate limiting?"}
    }
  }'

tools/list returns the full tool set with schemas — useful for building your tool definitions programmatically rather than hard-coding them.

A minimal Python client

Most MCP SDKs will handle this for you. If you are wiring it by hand, the whole surface is this small:

import os, httpx

BASE     = os.environ["HIVE_BASE_URL"]
BRAIN_ID = os.environ["HIVE_BRAIN_ID"]


class Hive:
    """Thin Hive MCP client. Fetches a token lazily and retries once on 401."""

    def __init__(self) -> None:
        self._client = httpx.Client(timeout=30)
        self._token: str | None = None

    def _get_token(self) -> str:
        r = self._client.post(
            f"{BASE}/brain/{BRAIN_ID}/token",
            data={
                "grant_type": "client_credentials",
                "client_id": os.environ["HIVE_CLIENT_ID"],
                "client_secret": os.environ["HIVE_CLIENT_SECRET"],
            },
        )
        r.raise_for_status()
        return r.json()["access_token"]

    def call(self, tool: str, **arguments: object) -> dict:
        if self._token is None:
            self._token = self._get_token()

        def _post() -> httpx.Response:
            return self._client.post(
                f"{BASE}/brain/{BRAIN_ID}/mcp",
                headers={
                    "Authorization": f"Bearer {self._token}",
                    "Accept": "application/json, text/event-stream",
                },
                json={
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/call",
                    "params": {"name": tool, "arguments": arguments},
                },
            )

        response = _post()
        if response.status_code == 401:      # token expired mid-session
            self._token = self._get_token()
            response = _post()
        response.raise_for_status()
        return response.json()


hive = Hive()
print(hive.call("aiqbee_search", query="rate limiting decision"))
You now have a working connection. Before you wire it into your app, read the integration pattern — how you expose this to your model matters more than the transport details above.

Troubleshooting

SymptomCause
invalid_client (401) at the token endpoint Wrong client_id/secret, the account is deactivated, or its secret has expired. Reissue from the admin page.
Token works, every tool reports no access Step 2 was skipped — the service account has no grant on that brain, or you are calling a different brain's id.
Reads fine, writes refused Access level is read, or the brain has allow_mcp_editing switched off.
Audit history tools refuse Audit is off for that brain. Enabling it needs a paid licence (Licensing & tiers), but reading existing history does not — a brain that already has audit on keeps serving its history.
Audit history refuses for a service account Audit history is owner-only. A service account with read or read_write cannot read it even though its own writes are recorded there.
406 Not Acceptable Missing Accept: application/json, text/event-stream. The streamable-HTTP transport requires both.