LangChain on SwarmD
swarmd-langchain is a thin wrapper on top of
LangChain and
LangGraph that gives a
LangChain agent access to the SwarmD platform: OAuth2 authentication,
sub-agent discovery (as BaseTools), MCP tool discovery, polling for
long-running sub-agents, an A2A server, signed-webhook refresh, and an
admin surface for operators.
If you are already running a LangChain agent and want to move it onto
SwarmD, read this first to learn the helpers, then follow
Migrating from LangChain. If you are starting
from scratch this page is the whole story.
The shape mirrors Google ADK on SwarmD on purpose — the
helper trio (create_runtime, create_llm_agent, serve) is the
same.
What it gives you
The SDK exposes three helpers that, in order, produce a complete agent:| Helper | Returns | What it does |
|---|---|---|
create_runtime() | SwarmDRuntime | Reads SWARMD_* env vars, configures a runtime, and instantiates token managers (platform-API + MCP-relay). |
create_llm_agent(runtime, ...) | LangGraph CompiledStateGraph | Picks the model, discovers subscribed sub-agents (as PollingA2aTool instances), discovers subscribed MCP servers, compiles a LangGraph with the combined toolset. |
serve(agent, runtime) | runs forever | Wraps the graph in an A2A executor, builds the agent card, mounts the /admin surface, correlation middleware, and starts uvicorn. Recompiles the graph in place on signed webhooks. |
@tool-decorated
function, three calls, and an if __name__ == "__main__" guard.
Installation
swarmd-sdk as a transitive dependency, plus langchain,
langgraph, langchain-openai, langchain-mcp-adapters, and
a2a-sdk.
Credentials
Every agent registered on SwarmD receives three secrets:agentId— the agent’s UUID and OAuth2 client ID.clientSecret— used for outbound calls (registry, A2A relay, MCP, LLM gateway).webhookSecret— used by the platform to sign inbound webhooks to your agent’s/admin/webhookendpoint.
POST /registry/v1/agents response. Store them with your
deployment configuration. The webhook secret is technically optional —
outbound calls still work without it — but without it your agent has to
be manually refreshed (POST /admin/refresh) every time a subscription
or grant changes upstream.
Environment
.env
SWARMD_AGENT_ID / SWARMD_CLIENT_SECRET / SWARMD_BASE_URL
/ SWARMD_TOKEN_URL values are the platform-attached set. Leave any of
them unset and the runtime stays in “standalone mode” — the agent still
runs locally as a plain LangChain agent against direct OpenAI, it just
won’t see any subscribed sub-agents or MCP tools.
The whole agent, end to end
main.py
Found N subscribed agents and
Loaded N MCP tool(s) from M server(s) in the logs.
The three helpers, in detail
create_runtime()
Builds a SwarmDRuntime object and, if the four SWARMD_* vars are
set, calls runtime.configure(...). That instantiates:
- A platform-API
SwarmDClientfor registry and audit calls. - An
McpClientfor MCP discovery. - Two
TokenManagerProxyobjects — one for the platform-API audience (swarmd:api), one for the MCP relay audience (mcp:call). Both do double-check-locked OAuth2 client-credentials grants with caching, jittered backoff retries on 5xx, and a single 401 retry.
create_llm_agent(runtime, name, description, instruction, tools)
Returns a LangGraph CompiledStateGraph you can ainvoke like any
other LangGraph. It does five things on top of
langchain.agents.create_agent(...):
- Picks the model. Reads
OPENAI_MODEL(defaultgpt-4o) andOPENAI_API_KEYand instantiatesChatOpenAIwithtemperature=0. If you need a different temperature or a different provider, drop down tofetch_remote_agents+fetch_mcp_toolsand assemble the graph yourself — they are all exported. - Discovers sub-agents. Calls
fetch_remote_agents(runtime), which pulls subscriptions fromGET /registry/v1/agents/{id}/subscriptionsand wraps each one in aPollingA2aTool— a LangChainBaseTool. The tool itself owns A2Amessage/sendand pollstasks/getagainst the relay until the downstream task reaches a terminal state. So a long-running sub-agent doesn’t block your LLM and your tool never returns a stale intermediate payload. - Discovers MCP servers. Calls
fetch_mcp_tools(runtime), which lists MCP grants via the SDK’sMcpClient, then connects to each granted server vialangchain-mcp-adapters(MultiServerMCPClient), pointed at the relay’s MCP proxy (/relay/v1/mcp-servers/{id}/mcp). Tools are namespaced with a sanitised, UUID-seeded prefix so a free-form server name like"GitLab - swarmd.ai"can’t produce a tool name OpenAI rejects. Anhttpx.Authimpl mints a freshmcp:call-scoped token on every request and propagates the current correlation ID. - Compiles the LangGraph. Local tools + remote sub-agent tools +
MCP tools are concatenated and handed to
langchain.agents.create_agentwith yourinstructionas the system prompt and yournameas the graph name. - Stashes the recipe on the graph. Attaches
swarmd_name,swarmd_description,swarmd_instruction, andswarmd_local_toolsto the returnedCompiledStateGraphsoserve()can rebuild the graph in place when subscriptions change, without you having to pass everything through again.
runtime, fetch_remote_agents, and
fetch_mcp_tools directly and call
langchain.agents.create_agent yourself.
serve(agent, runtime, *, skills=None, on_refresh=None)
The thickest of the three helpers because the A2A server is the
agent’s public face. In order, it:
- Wraps the LangGraph in a
LangChainA2aExecutor. This is the adapter that translates incoming A2A protocol calls (message/send,message/stream,tasks/get,tasks/cancel) into LangGraphainvokes and streams events back as A2Ataskupdates. - Builds the agent card. Either from an explicit
skills=[AgentSkill(...), ...]list, or — if you didn’t pass one — a single catch-all skill is synthesised from the agent’sdescription. The card is exposed at/.well-known/agent-card.json. - Persists tasks. A SQLite-backed
DatabaseTaskStoreis created at/tmp/{agent}_tasks.db. Enough for local dev; for production point it at a real database by overriding the create call inside your own copy ofserveif you need durability across pod restarts. - Adds correlation propagation. A
CorrelationIdMiddlewareextractsX-Correlation-Idoff inbound A2A calls into aContextVar, and the SDK’s outbound interceptors copy that var onto every downstream bearer-tokened request. - Mounts
/admin. Four endpoints —POST /admin/configure,POST /admin/refresh,POST /admin/webhook,GET /admin/status./admin/webhookis the platform-driven kick: when a subscription, MCP grant, agent lifecycle, or tenant lifecycle event fires, the platform POSTs a signed payload, the SDK verifies the HMAC againstSWARMD_WEBHOOK_SECRET, and calls the refresh path. - Refreshes in place. The default refresh callback rebuilds the
LangGraph from the recipe stashed by
create_llm_agent— re-discovering sub-agents and MCP tools — and swaps it into the executor atomically. Local tools survive the swap. No restart needed when subscriptions change. Passon_refresh=...to override. - Starts uvicorn on
HOST:PORT(defaults0.0.0.0:8080) withLOG_LEVELfrom env.
Verifying it works
Found N subscribed agents / Loaded N MCP tool(s) appear in
the agent’s startup logs.
Advanced — using the SDK pieces directly
If you need to deviate from whatcreate_llm_agent and serve do,
drop down to the underlying pieces. The whole surface area is
exported:
- Custom polling interval / max wait. Build
PollingA2aToolinstances yourself. - Hand-built LangGraph. Use the discovery functions to get the
tool lists, then call
langchain.agents.create_agent(or build a raw LangGraph) with whatever model, memory, or structured-output config you need. - Custom A2A server. Use
LangChainA2aExecutor(graph)directly and bolt it onto your own Starlette / FastAPI app — callcreate_admin_app(runtime, on_refresh=...)for the/adminsurface.
Next steps
- Migrating from LangChain — step-by-step guide for moving an existing raw-LangChain agent onto this SDK.
- Google ADK on SwarmD — the same shape for Google ADK.
- Configuration — env var reference and advanced
options on the base
swarmd-sdk.
