Aiqbee Hive
Sign in
Help

Connecting via MCP

Service accounts, client credentials or API keys, Azure AI Foundry, and your first tool call.

Machine integrations authenticate as a service account using the OAuth client_credentials grant, or — for clients that can only send a fixed header — a static API key. 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 and the API key are shown exactly once; copy them immediately. If they are lost, use Reissue secret — neither can 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.

Refresh tokens are tied to the account's current secret. After the secret is reissued, or once it expires, a refresh returns invalid_grant — request a new token with grant_type=client_credentials using the current secret. Keep the client secret available to your integration; a refresh token alone is not enough to recover.

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.

API keys

Some agent platforms cannot run a token exchange and can only attach a fixed header to every request. For those, a service account also carries an API key, issued alongside the client secret and sharing its expiry. Prefer client credentials (above) wherever your client can run them.

Send the key on X-Api-Key against any MCP endpoint, https://<hive>/brain/<brain-id>/mcp. No token request is needed:

X-Api-Key: <api-key>
curl -X POST https://<hive>/brain/<brain-id>/mcp \
  -H "X-Api-Key: $HIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
  • The key grants exactly what the service account grants — the same brain access, the same permission layers, the same allow_mcp_editing requirement.
  • The key must be sent on X-Api-Key. Sending it as an Authorization: Bearer token is refused, with a message saying so.
  • A 401 means the key is wrong, revoked or expired — replace it. A 503 with Retry-After means the Hive could not verify the key just then — retry; do not reissue anything.
  • The key shares the client secret's expiry and rotates whenever the secret is reissued.
  • Service accounts created before API keys existed get a key at their next Reissue secret.

Optionally, exchange the key for an access token: POST /brain/<brain-id>/token with grant_type=api_key and api_key=<api-key> returns a 1-hour access token and no refresh token.

A token obtained with grant_type=api_key stays valid until it expires (up to 1 hour) even after the key is reissued, whereas a key sent directly in X-Api-Key stops working on the next request.

VS Code (SSH remote / headless)

Where the OAuth sign-in cannot open a browser, put the key in mcp.json as a header and keep it out of the file with a masked prompt:

{
  "inputs": [
    { "id": "hive-api-key", "type": "promptString",
      "description": "Hive API key", "password": true }
  ],
  "servers": {
    "aiqbee-hive": {
      "type": "http",
      "url": "https://<hive>/brain/<brain-id>/mcp",
      "headers": { "X-Api-Key": "${input:hive-api-key}" }
    }
  }
}

Connecting from Azure AI Foundry

Foundry offers several authentication methods for MCP tools. Only one suits a service account.

Foundry optionWorks with Hive?
Key-basedYes — use this. Send the service account API key.
OAuth identity passthroughAuthenticates the signed-in Foundry user, not a service account. Works if you want each user to act as themselves and they each have a Hive account.
Microsoft Entra ID (agent or project identity)Not supported.
UnauthenticatedNot supported. Every MCP endpoint requires a credential.
  1. In Hive, create a service account under Admin → Service Accounts and copy the API key shown at creation. Use one service account per integration so it can be revoked without affecting anything else.
  2. Grant it access to the brain you want the agent to use (step 2 above). Service accounts get no access implicitly.
  3. In the Foundry portal, open your project and go to Build → Tools, then Add tool → Custom → Model Context Protocol.
  4. Fill in the connection: endpoint https://<hive>/brain/<brain-id>/mcp, Authentication Key-based, Credential name X-Api-Key, Credential value your API key. Then select Connect.

Worth knowing

  • Anyone with access to the Foundry project can read a credential stored in a project connection. Use one least-privilege service account per integration.
  • The key stops working on the secret's expiry date. Set a reminder before then — Foundry stores the value statically, so someone has to paste a new one in.
  • Require approval defaults to always. A blanket never auto-approves writes and deletes too, so name the read-only tools instead and leave anything that writes on always.
  • Use the brain-specific endpoint, so the brain_id parameter disappears from the tool schemas and the model has less to get wrong.

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.
invalid_grant (400) when refreshing a token The secret was reissued or has expired, the account is deactivated or deleted, or the refresh token itself expired. Request a new token with grant_type=client_credentials and the current secret (reactivate the account first if it was deactivated).
401 on the MCP endpoint with X-Api-Key The key is wrong, revoked or expired. Reissue the secret from the admin page and use the new key.
503 with Retry-After The Hive could not verify the key just then. Retry after the delay; do not reissue.
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.