MAQPNADocs

Connect to the gateway

How an agent gets its session identity and token, the environment variables and endpoints it is given, how long a token lives and how it is refreshed.

Your agent never holds an API key for a tool or a model. It holds one thing: a session token, a short-lived signed identity for this one session. It sends that token as a bearer token to the MAQPNA gateway, and the gateway does everything else. This page explains where the token comes from, which variables tell your code where the gateway is, and what to do when the token expires.

How the token reaches your code#

sequenceDiagram
    autonumber
    participant OP as Operator
    participant IB as Identity broker
    participant K as Secret SESSION-maqpna-token
    participant AG as Your agent (sandbox)
    participant GW as Gateway
    OP->>IB: POST /v1/token (namespace, agent, session, user, scopes, tier, ttl)
    IB-->>OP: signed EdDSA token, SPIFFE ID, expiry
    OP->>K: write token
    K-->>AG: mounted at /var/run/maqpna/token (read-only)
    Note over OP: env MAQPNA_GATEWAY_URL, MAQPNA_TOKEN_FILE,<br/>MAQPNA_TOOL_NAME_URL, ...
    AG->>AG: SDK reads the file on every request
    AG->>GW: POST /mcp/github tools/list<br/>Authorization Bearer session token
    GW->>GW: verify signature with the broker's public keys (JWKS),<br/>audience maqpna-gateway, expiry, revocations
    GW-->>AG: tools this session may use
    Note over OP,K: at 80% of the lifetime the operator<br/>mints a new token into the same Secret
  1. You (or a controller) create an AgentSession for your Agent.
  2. The operator checks that the requested scopes are a subset of the agent's, then asks the identity broker for a token.
  3. The token is written to the Secret <session>-maqpna-token and mounted read-only into the sandbox at /var/run/maqpna/token. On a tier-2 (confidential VM) session it is written to an in-memory volume after attestation instead, and never stored in the cluster.
  4. The SDK reads the file on every request, so a refreshed token is picked up without a restart.

Under maqpna dev run there is no operator and no file: the CLI asks the local broker for a token and passes it in MAQPNA_TOKEN.

What is in the token#

Decode a token (no signature check) with maqpna token inspect:

maqpna dev run --session dev-tok --ttl 20m --scope tools:echo -- sh -c 'maqpna token inspect "$MAQPNA_TOKEN"'
{
  "claims": {
    "iss": "maqpna-identity",
    "sub": "spiffe://maqpna.local/ns/dev/agent/coder/session/dev-tok",
    "aud": "maqpna-gateway",
    "iat": 1792359480,
    "nbf": 1792359480,
    "exp": 1792360680,
    "jti": "e0b1a9a07eb330c49d75a22dbccd29e3",
    "act": {
      "sub": "user:you@localhost"
    },
    "scope": "tools:echo",
    "maqpna_tier": "dev",
    "maqpna_session": "dev-tok",
    "maqpna_ns": "dev",
    "maqpna_agent": "coder"
  },
  "expiresAt": "2026-10-18T21:58:00Z",
  "kid": "o15cBqYfnnnmTdFXvY5NdtVU5ZQlEHNrh6V3WKNuGDk"
}
Claim Meaning Used for
sub The session's workload identity (SPIFFE ID): trust domain, namespace, agent, session. Policy, audit, kill switch
act.sub The user the agent acts for. Policy principals, approvals (an approver cannot approve their own calls)
scope tools:<server>, models:<route>, agents:<name>, space-separated. The gateway refuses a server, route or agent the token has no scope for (missing_scope).
exp Expiry. token_expired after it
jti Token ID. Revoking one leaked token with maqpna kill --jti

Check a signature against the broker's public keys with maqpna token verify TOKEN --jwks http://127.0.0.1:8081/.well-known/jwks.json (exit status 3 when invalid).

Environment variables#

The operator sets these in every sandbox (internal/controller/podspec.go); maqpna dev run sets the local equivalents.

Variable In a cluster Under maqpna dev run
MAQPNA_GATEWAY_URL The gateway's in-cluster URL http://127.0.0.1:8080
MAQPNA_TOKEN_FILE /var/run/maqpna/token not set
MAQPNA_TOKEN not set the session token
MAQPNA_SESSION, MAQPNA_AGENT session and agent names dev-<random> (or --session) and coder (or --agent)
MAQPNA_TOOL_<NAME>_URL {gateway}/mcp/<name> for each spec.tools entry one per MCP server of the local stack
MAQPNA_MODEL_ENDPOINT, MAQPNA_MODEL_ROUTE {gateway}/llm/<ns>.<agent>/v1 and the route, when spec.model.endpoint is set {gateway}/llm/stub/v1 with --stub-llm (route variable not set)
MAQPNA_MODEL_NAME, MAQPNA_MODEL_PROVIDER from spec.model.name and .provider stub and openai with --stub-llm
MAQPNA_ANTHROPIC_BASE_URL {gateway}/llm/<route>/anthropic, when spec.model.adapter is anthropic or bedrock not set
OPENAI_BASE_URL, OPENAI_API_KEY not set (set them from the variables above) the model route and the session token, with --stub-llm
MAQPNA_TASK, MAQPNA_TASK_FILE the session's task (inline when at most 4 KiB; file at /var/run/maqpna/input/task) not set
MAQPNA_WORKSPACE, MAQPNA_PARENT_SESSION workspace mount; parent of a forked session not set

<NAME> is the server name in upper case, with every character that is not a letter or digit replaced by _: server jira-cloud is MAQPNA_TOOL_JIRA_CLOUD_URL. Variables you set in Agent.spec.env cannot override the MAQPNA_* ones, and they cannot reference the session's token Secret.

Endpoints your agent calls#

All on MAQPNA_GATEWAY_URL, all with Authorization: Bearer <session token>:

Endpoint For Page
POST /mcp/{server} MCP JSON-RPC: initialize, tools/list, tools/call Call tools over MCP
POST /llm/{route}/v1/chat/completions, /completions, /embeddings, /responses; GET /llm/{route}/v1/models OpenAI-compatible model calls Call models
POST /llm/{route}/anthropic/v1/messages Native Anthropic Messages (token also accepted as x-api-key) Call models
POST /a2a/{namespace}/{agent} Agent-to-agent (A2A) JSON-RPC Call another agent
POST /v1/sessions/self/result Report how the run ended Handle decisions

Connect from code#

from maqpna import MaqpnaClient

client = MaqpnaClient.from_env()   # MAQPNA_GATEWAY_URL, MAQPNA_TOKEN_FILE or MAQPNA_TOKEN, ...
print(client.session, client.available_tool_servers())
token = client.token()             # the current token, re-read from the file
import { MaqpnaClient } from "@kosa8/maqpna";

const client = MaqpnaClient.fromEnv();
console.log(client.availableToolServers());
const token = await client.token();
client, err := maqpna.NewFromEnv(ctx)
if err != nil {
    return err
}
fmt.Println(client.Session(), client.AvailableToolServers())
token, err := client.Token(ctx)
TOKEN="${MAQPNA_TOKEN:-$(cat "${MAQPNA_TOKEN_FILE:-/var/run/maqpna/token}")}"
curl -sS "$MAQPNA_TOOL_ECHO_URL" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Read the file again before each request. Do not cache the token for the life of the process.

Without MAQPNA_GATEWAY_URL, from_env() / fromEnv() / NewFromEnv fail with ConfigError. They never fall back to a public endpoint.

Token lifetime and refresh#

Where Default lifetime Refresh
Cluster At most 15 minutes (operator flag --token-ttl), and never past the session's own TTL The operator mints a new token at 80% of the lifetime into the same Secret; the kubelet updates the mounted file.
Warm-pool sandbox Handed over by the broker when the sandbox is claimed (MAQPNA_BOOTSTRAP_URL) The SDK refreshes at 80% of the lifetime.
maqpna dev run 1 hour (--ttl) None: start a new maqpna dev run.

The SDKs re-read the token file on every request, and after a 401 they re-read it and retry once. If you write your own client, do the same. An expired token gets JSON-RPC -32003 with token_expired; a missing one gets HTTP 401 with missing_token.

Mint a token by hand#

For a quick test against a shared installation, mint a token with the broker's admin token, or read a running session's token from its Secret:

# local stack: MAQPNA_BROKER_URL and MAQPNA_BROKER_TOKEN come from maqpna dev env
eval "$(maqpna dev env)"
maqpna token mint --broker "$MAQPNA_BROKER_URL" --namespace dev --agent coder --session s1 --scope tools:echo --ttl 15m

# cluster: an existing session's token
kubectl get secret fix-4821-maqpna-token -n team-a -o jsonpath='{.data.token}' | base64 -d > tok
maqpna call --gateway https://gateway.example.eu --token-file tok --server github --list

What can go wrong#

Symptom Reason Fix
ConfigError: MAQPNA_GATEWAY_URL The agent runs outside a sandbox or maqpna dev run. Run it as maqpna dev run -- CMD.
-32003 token_expired The token lived past exp. Locally, raise --ttl; in a cluster, re-read the file instead of caching the token.
-32003 missing_scope:tools:github The token has no scope for that server. Add the server to Agent.spec.tools, or --scope tools:github locally.
HTTP 404 unknown_server No MCP server of that name is visible to this namespace. maqpna mcp list -n NS.
token_audience_mismatch The token was minted for another audience. Mint for maqpna-gateway.

Next steps#