> ## 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()`                       | `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+**.

***

## Not yet available

`swarmd-crewai` exists on the roadmap but ships as a placeholder — it exports a
version string and nothing else. For CrewAI today, use
[the core SDK](/sdks/python/core-sdk) directly.
