> ## Documentation Index
> Fetch the complete documentation index at: https://docs.swarmd.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Reference

> Complete API surface for swarmd-sdk, swarmd-google-adk, and swarmd-langchain.

# Reference

Everything the three Python packages export. For the narrative version, see
[Core concepts](/sdks/python/concepts) and the per-framework pages.

***

## Environment variables

### Platform credentials

Read by `SwarmDConfig.from_env()` and by both wrappers' `create_runtime()`.

| Variable | Required | Default |
| - | - | - |
| `SWARMD_AGENT_ID` | Yes | — |
| `SWARMD_CLIENT_SECRET` | Yes | — |
| `SWARMD_WEBHOOK_SECRET` | Recommended | — |
| `SWARMD_BASE_URL` | See note | `https://api.dev.swarmd.ai` |
| `SWARMD_TOKEN_URL` | See note | `https://auth.dev.swarmd.ai/realms/swarmd/protocol/openid-connect/token` |
| `SWARMD_TIMEOUT` | No | `30` (seconds) |
| `SWARMD_MAX_RETRIES` | No | `3` |

<Warning>
  **`BASE_URL` and `TOKEN_URL` are optional for `swarmd-sdk` and effectively
  required for the wrappers.** `SwarmDClient.from_env()` falls back to the
  production defaults; `create_runtime()` only configures the runtime when all
  four of `AGENT_ID`, `CLIENT_SECRET`, `BASE_URL` and `TOKEN_URL` are present,
  and otherwise leaves it in standalone mode with no discovery.
</Warning>

<Note>
  `SWARMD_WEBHOOK_SECRET` is read by `swarmd-google-adk`'s `create_runtime()` but
  **not** by `swarmd-langchain`'s — see the
  [gap noted on the LangChain page](/sdks/python/langchain#dissected-create_runtime).
</Note>

<Note>
  `SWARMD_TIMEOUT` and `SWARMD_MAX_RETRIES` are read by `SwarmDConfig.from_env()`
  only — so they apply to `SwarmDClient.from_env()` and `McpClient.from_env()`.
  Neither wrapper's `create_runtime()` passes them on: `SwarmDRuntime.configure()`
  takes no timeout or retry arguments, so a wrapper-built runtime always gets the
  `30` / `3` defaults.
</Note>

### Custom prefix

Running several agents in one process? Every `from_env` takes a prefix:

```python theme={null}
weather = SwarmDClient.from_env("WEATHER_AGENT")   # WEATHER_AGENT_AGENT_ID, ...
calendar = SwarmDClient.from_env("CALENDAR_AGENT")
```

### LLM and server

| Variable | Used by | Default |
| - | - | - |
| `SWARMD_LLM_GATEWAY_ID` | `swarmd-google-adk` only | — |
| `OPENAI_API_KEY` | Both wrappers | — |
| `OPENAI_MODEL` | Both wrappers | `gpt-4o` |
| `OPENAI_TEMPERATURE` | `swarmd-google-adk` only | unset |
| `HOST` | `serve()` | `0.0.0.0` |
| `PORT` | `serve()` | `8080` |
| `LOG_LEVEL` | `serve()` — uvicorn; in `swarmd-google-adk` 0.4.0+ also the root logger, so your own `logger.info()` lines are emitted | `info` |
| `AGENT_VERSION` | `swarmd-langchain`'s agent card | `0.1.0` |

### Pointing at another environment

`SWARMD_BASE_URL` and `SWARMD_TOKEN_URL` move together — a base URL and a token
URL from different environments mint tokens the gateway rejects on audience.
Ask your platform team for the pair that matches the estate you've been given
credentials for.

***

## `swarmd-sdk`

```python theme={null}
from swarmd_sdk import (
    SwarmDClient, SwarmDConfig, SwarmDRuntime, TokenManagerProxy,
    AgentDirectory, McpClient, McpServerInfo, mcp_tool_namespace,
    OAuth2TokenManager, AgentResponse, OAuth2Token,
    CorrelationIdMiddleware, correlation_id_var,
    create_admin_app, verify_webhook,
    SwarmDSDKError, AuthenticationError, TokenRefreshError, APIError,
    McpJsonRpcError, McpNotSubscribedError, WebhookVerificationError,
)
```

### `SwarmDClient`

```python theme={null}
SwarmDClient(
    agent_id: str,
    client_secret: str,
    base_url: str = "https://api.dev.swarmd.ai",
    token_url: str = "https://auth.dev.swarmd.ai/realms/swarmd/protocol/openid-connect/token",
    timeout: int = 30,
    max_retries: int = 3,
)
```

| Constructor | |
| - | - |
| `SwarmDClient(...)` | Explicit arguments. |
| `SwarmDClient.from_config(config)` | From a `SwarmDConfig`. |
| `SwarmDClient.from_env(prefix="SWARMD")` | From environment variables. |

| Method | Returns |
| - | - |
| `get_agent_subscriptions(agent_id: UUID)` | `List[AgentResponse]` — agents you may call. Paginated internally at 100/page, capped at 100 pages. |
| `get_agent_llm_gateway_subscriptions(agent_id: UUID)` | `List[dict]` — raw rows carrying `llmGatewayId`, `llmGatewayName`, `tenantId`, `subscribedAt`. |
| `refresh_agent_card(agent_id: UUID)` | `dict` — `{"status": "unchanged"\|"updated", "changedFields": [...]}`. |
| `close()` | Closes the HTTP client. Also available as a context manager. |

<Note>
  `agent_id` must be your **own** id when calling with an agent token. The
  registry compares it against the JWT subject and returns `403` on a mismatch —
  the endpoint is shared with tenant admins, who may query any agent.
</Note>

**Request behaviour:** `401` clears the cached token and retries once; `5xx`
and network errors retry with exponential backoff up to `max_retries`; other
`4xx` fail immediately.

### `SwarmDConfig`

```python theme={null}
SwarmDConfig(
    agent_id: str,
    client_secret: str,
    base_url: str = "https://api.dev.swarmd.ai",
    token_url: str = "https://auth.dev.swarmd.ai/realms/swarmd/protocol/openid-connect/token",
    timeout: int = 30,
    max_retries: int = 3,
    webhook_secret: Optional[str] = None,
)
```

`SwarmDConfig.from_env(env_prefix="SWARMD")` builds one from the environment
and raises `ValueError` naming any missing required variable. Trailing slashes
are stripped from both URLs. Importing the module calls `load_dotenv()`, so a
`.env` in the working directory is picked up automatically.

### `SwarmDRuntime`

Holds credentials and the clients built from them; supports reconfiguration in
place.

| Member | |
| - | - |
| `configure(agent_id, client_secret, base_url=None, token_url=None, webhook_secret=None)` | Builds new clients and swaps them under a lock, closing the old ones. `base_url` and `token_url` are required on the first call only. `webhook_secret` is preserved across reconfigures unless a new value is passed. |
| `configured` | `bool` |
| `config` | `Optional[SwarmDConfig]` |
| `client` | `Optional[SwarmDClient]` |
| `mcp` | `Optional[McpClient]` |
| `token_manager` | `TokenManagerProxy` — `swarmd:api` scope |
| `mcp_token_manager` | `McpTokenManagerProxy` — `mcp:call` scope |

The two token managers are **proxies**: they resolve to the current client's
manager on every call, so a helper that captured one at startup keeps working
after a reconfigure.

### `create_admin_app`

```python theme={null}
create_admin_app(runtime: SwarmDRuntime, on_refresh: Optional[Callable[[], None]] = None)
```

Returns a FastAPI app to mount at `/admin`.

| Endpoint | Behaviour |
| - | - |
| `POST /configure` | Body `{agent_id, client_secret, base_url?, token_url?, webhook_secret?}`. `400` if a first configure omits the URLs. |
| `POST /refresh` | `501` without `on_refresh`; `409` if unconfigured; `502` if the callback raises. |
| `POST /webhook` | `409` unconfigured · `503` no webhook secret · `401` bad signature · `502` callback raised · `{"status":"ack"}` on success · `{"status":"ignored"}` for unknown event types. |
| `GET /status` | `{"configured": bool, "agent_id": str \| null}` |

Refresh-triggering event types (`X-Swarmd-Event-Type`):
`SUBSCRIPTION_CHANGED`, `AGENT_LIFECYCLE`, `TENANT_LIFECYCLE`,
`MCP_GRANTS_CHANGED`.

### `AgentDirectory`

```python theme={null}
AgentDirectory(
    client: SwarmDClient,
    agent_id: UUID,
    refresh_interval: float = 60,
    on_change: Optional[Callable[[Set[UUID], Set[UUID]], None]] = None,
)
```

| Member | |
| - | - |
| `agents` | `Tuple[AgentResponse, ...]` — immutable snapshot, swapped atomically |
| `get(name)` | `Optional[AgentResponse]` |
| `refresh()` | Force a refresh now. Exceptions are logged, not raised. |
| `start()` / `stop()` | Also a context manager. `start()` refreshes once synchronously first. |
| `running` | `bool` |

`on_change(added, removed)` receives sets of UUIDs. A raising callback is
logged and does not abort the refresh.

### `McpClient`

Same constructors and retry semantics as `SwarmDClient`, but mints
`mcp:call`-scoped tokens bound to `{base_url}/relay`.

| Method | Returns |
| - | - |
| `list_available()` | `List[McpServerInfo]` — servers granted to you |
| `list_tools(mcp_server_id, *, session_id=None)` | `List[dict]` — the `tools` array from `tools/list` |
| `call_tool(mcp_server_id, name, arguments=None, *, session_id=None)` | Raw `result` from `tools/call` |
| `call(mcp_server_id, method, params=None, *, request_id=None, session_id=None, protocol_version=None)` | Raw `result` for any JSON-RPC method |

`McpServerInfo` fields: `mcp_server_id: UUID`, `name: str`,
`description: Optional[str]`, `transport: Optional[str]`,
`protocol_version: Optional[str]`.

`mcp_tool_namespace(server) -> str` returns `mcp_{sanitised_name}_{uuid8}`;
characters outside `^[a-zA-Z0-9_-]+$` become `_`. No trailing separator — the
caller adds its framework's own.

### Correlation

| | |
| - | - |
| `CorrelationIdMiddleware` | ASGI middleware. Captures `X-Correlation-Id` into a `ContextVar`, resets it in a `finally`. |
| `correlation_id_var` | `ContextVar[Optional[str]]`. Read it to forward the header yourself. |

### `verify_webhook`

```python theme={null}
verify_webhook(
    headers: Mapping[str, str],
    body: bytes,
    secret: str,
    *,
    max_age_seconds: int = 300,
    now_seconds: int | None = None,
) -> None
```

Raises `WebhookVerificationError` on a missing header, malformed timestamp,
timestamp outside the symmetric ±`max_age_seconds` window, or signature
mismatch. Signature is
`HMAC-SHA256(secret, f"{timestamp}.".encode() + body)`, compared with
`hmac.compare_digest`.

<Warning>
  Pass the **raw request body bytes**. Re-serialising a parsed dict changes key
  order and whitespace, and the signature will never match.
</Warning>

### Models

**`AgentResponse`** — `agent_id: UUID`, `name: str`, `description: str`,
`agent_card_url: str` (relay-proxied), `visibility: Optional[Visibility]`
(`PUBLIC` · `PRIVATE` · `INTERNAL`). Accepts the wire's camelCase aliases.

**`OAuth2Token`** — `access_token`, `token_type`, `expires_in`, `scope`,
`issued_at`, and `is_expired(buffer_seconds=60)`.

### Exceptions

| Exception | Raised when | Extra attributes |
| - | - | - |
| `SwarmDSDKError` | Base for everything below | `message`, `cause` |
| `AuthenticationError` | Authentication fails | |
| `TokenRefreshError` | Token acquisition fails (subclass of the above) | |
| `APIError` | HTTP request fails | `status_code`, `response_body` |
| `McpJsonRpcError` | Response carries a JSON-RPC `error` | `code`, `data` |
| `McpNotSubscribedError` | `403` from the relay's MCP proxy path (subclass of `APIError`) | |
| `WebhookVerificationError` | Signature verification fails | |

***

## `swarmd-google-adk`

```python theme={null}
from swarmd_google_adk import (
    create_runtime, create_llm_agent, serve,
    fetch_remote_agents, fetch_mcp_tools,
    PollingRemoteA2aAgent, create_a2a_client_factory,
)
```

| Function | Signature |
| - | - |
| `create_runtime()` | `-> SwarmDRuntime` |
| `create_llm_agent(runtime, name, description, instruction, tools, *, generate_content_config=None, tool_choice=None)` | `-> LlmAgent` |
| `serve(agent, runtime)` | `-> None` (blocks) |
| `fetch_remote_agents(runtime)` | `-> List[PollingRemoteA2aAgent]` |
| `fetch_mcp_tools(runtime)` | `-> List[BaseToolset]` |
| `create_a2a_client_factory(token_manager, timeout=300.0, include_correlation_id=True)` | `-> ClientFactory` |

### `PollingRemoteA2aAgent`

```python theme={null}
PollingRemoteA2aAgent(
    name: str,
    description: str,
    agent_card: str,
    a2a_client_factory: Optional[ClientFactory] = None,
    poll_interval: float = 5.0,
    max_wait: float = 600.0,
)
```

Drop-in for ADK's `RemoteA2aAgent`. Polls `tasks/get` while the task state is
`working`, `submitted`, `input_required` or `auth_required`; stops at
`completed`, `failed`, `canceled`, `rejected` or `unknown`. With no factory,
builds an authenticated one from the active runtime.

### `tool_choice` values

`"auto"` / `None` · `"required"` · `"none"` · a specific tool name. Forwarded
to `litellm.acompletion` on every turn.

***

## `swarmd-langchain`

```python theme={null}
from swarmd_langchain import (
    create_runtime, create_llm_agent, serve,
    fetch_remote_agents, fetch_mcp_tools,
    PollingA2aTool, LangChainA2aExecutor, create_a2a_client_factory,
)
```

| Function | Signature |
| - | - |
| `create_runtime()` | `-> SwarmDRuntime` (does not read `SWARMD_WEBHOOK_SECRET`) |
| `create_llm_agent(runtime, name, description, instruction, tools)` | `-> CompiledStateGraph` |
| `serve(agent, runtime, *, skills=None, on_refresh=None)` | `-> None` (blocks) |
| `fetch_remote_agents(runtime)` | `-> List[PollingA2aTool]` |
| `fetch_mcp_tools(runtime)` | `-> List[BaseTool]` |
| `create_a2a_client_factory(token_manager, timeout=300.0, include_correlation_id=True)` | `-> ClientFactory` |

### `PollingA2aTool`

```python theme={null}
PollingA2aTool(
    *,
    name: str,
    description: str,
    agent_card_url: str,
    a2a_client_factory: Optional[ClientFactory] = None,
    poll_interval: float = 5.0,
    max_wait: float = 600.0,
)
```

A LangChain `BaseTool`. Same terminal/non-terminal state sets as the ADK
wrapper.

### `LangChainA2aExecutor`

`LangChainA2aExecutor(agent)` adapts a compiled graph to the A2A protocol.
`set_agent(new_graph)` swaps the graph in place — how `serve()`'s refresh
works.

### Attributes `serve()` reads off the graph

`create_llm_agent()` sets these; set them yourself if you compile your own
graph and want auto-refresh to work.

| Attribute | Used for |
| - | - |
| `swarmd_name` | Agent card name, task store filename |
| `swarmd_description` | Agent card description, default skill |
| `swarmd_instruction` | System prompt on rebuild |
| `swarmd_local_tools` | Local tools preserved across a rebuild |

***

## Dependency ranges

The wrappers pin their framework dependencies deliberately. Widening a bound
is how these packages break.

| Package | Constraint | Why |
| - | - | - |
| `google-adk[a2a,extensions]` | `>=1.0.0,<3.0` | An unpinned floor resolved 2.7.0 in CI, which stopped re-exporting `McpToolset` / `StreamableHTTPConnectionParams` from `google.adk.tools.mcp_tool`. 2.7.0 itself is compatible; the ceiling stops the next major doing it silently. |
| `a2a-sdk` | `>=0.3.9,<1.0` | 1.0.x dropped `a2a.client.middleware` and `a2a.types.TextPart`, which the SDK still uses. |
| `mcp` | `>=1.24,<2` (ADK) · `>=1.0,<2` (LangChain) | An unbounded floor let pip resolve `mcp` 2.0.0 independently of google-adk's own `>=1.24,<2`, breaking the MCP server images on import. |
| `swarmd-sdk` | `>=0.2.0` | |

`swarmd-sdk` itself needs `httpx>=0.27`, `pydantic>=2.0`,
`python-dotenv>=1.0`, `starlette>=0.37` (for `CorrelationIdMiddleware`, which
`import swarmd_sdk` loads eagerly), and `fastapi>=0.110` (imported lazily,
only by `create_admin_app`).

`swarmd-google-adk` additionally needs `openai>=1.0` — `helpers.py` imports
`AsyncOpenAI` at module scope to build the LLM-gateway client.

All three packages require **Python 3.9+**.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.