Connect from LangGraph

Guide Python

Give a LangGraph agent policy-scoped, read-only access to your bank accounts through the agentis MCP server — about twenty lines of code, one API key, no OAuth flow.

# Overview

The agentis MCP server speaks Streamable HTTP at https://mcp.agentis.capital/mcp and authenticates server-to-server clients with a single Bearer header. LangGraph consumes it through langchain-mcp-adapters, which turns the server's tools — list_accounts, list_account_transactions, check_account_balance, and the rest of the toolset — into ordinary LangChain tools your agent can call.

The OAuth flow described in the MCP server reference exists for interactive clients where a human approves the connection in a browser. For headless deployments — a LangGraph service, a cron job, a worker — use an agent API key directly; it's the pattern the MCP spec anticipates for machine-to-machine clients.

You'll need:

  • An agentis workspace with a connected bank account (setup guide)
  • An agent created in the dashboard, with bank accounts assigned to it
  • Python ≥ 3.10 with langgraph ≥ 1.0, langchain ≥ 1.0, langchain-mcp-adapters ≥ 0.3, and the integration package for whichever model provider you use — the MCP server is model-agnostic, and any chat model with solid tool calling works

# Get an agent API key

Every key is agent-scoped and bound to exactly one agent — it inherits that agent's policy and bank-account assignments. Two ways to issue one:

  • Dashboard — under API Keys, choose Generate Key, pick the agent, and name the key after the deployment that will hold it.
  • Self-registration — run agentis register on the machine that will host the agent. The CLI prints a verification code, an admin approves it in the browser, and the CLI receives the key.
The full key (agts_agent_live_…) is shown exactly once at creation. Copy it straight into your secret store; if you lose it, rotate the agent's key to get a new one.

Keys don't expire on their own — they stay valid until you revoke or rotate them from the dashboard. See Bearer API key in the server reference for prefix semantics.

# Connect

Install
pip install langgraph langchain langchain-mcp-adapters
pip install langchain-anthropic  # or langchain-openai, langchain-google-genai, ...

Export the agent key as an environment variable, plus your model provider's API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, …), then:

agent.py
import asyncio
import os

from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient

# Example model — any provider works: "openai:...", "google_genai:...", ...
MODEL = "anthropic:claude-sonnet-5"


async def main():
    client = MultiServerMCPClient({
        "agentis": {
            "transport": "http",  # Streamable HTTP
            "url": "https://mcp.agentis.capital/mcp",
            "headers": {"Authorization": f"Bearer {os.environ['AGENTIS_AGENT_API_KEY']}"},
        }
    })
    tools = await client.get_tools()

    agent = create_agent(
        MODEL,
        tools,
        system_prompt="You are a finance assistant with read-only access "
                      "to the company's bank accounts.",
    )
    result = await agent.ainvoke(
        {"messages": [{"role": "user", "content": "Summarize last week's transactions."}]}
    )
    print(result["messages"][-1].content)


asyncio.run(main())

That's the whole integration. The MCP client underneath handles the Mcp-Session-Id round-trip, the dual Accept header, and streamed responses for you.

  • The model is just an example. create_agent takes a "provider:model" string (resolved through the matching langchain-<provider> package) or any chat-model instance — swap in whatever your stack runs.
  • Use create_agent from langchain.agentslanggraph.prebuilt.create_react_agent is deprecated since LangGraph 1.0.
  • Compute concrete dates in code and put them in the prompt (“between 2026-07-08 and 2026-07-15”) rather than letting the model interpret “last week”.
  • To verify the connection before wiring up a model: await client.get_tools() should return the full toolset, and calling view_identity confirms which agent the key is bound to.

# Routers and local models

The same agent runs on a multi-model router or a model hosted on your own hardware. One rule to know: a "provider:model" string only works for providers LangChain can resolve from the prefix alone — anything that needs a base_url or a gateway key takes a model instance instead.

Model backends
# OpenRouter — one key, hundreds of models (pip install langchain-openrouter)
from langchain_openrouter import ChatOpenRouter
model = ChatOpenRouter(model="anthropic/claude-sonnet-5")  # reads OPENROUTER_API_KEY

# Ollama — local open-source models (pip install langchain-ollama)
model = "ollama:qwen3"  # string form works; ChatOllama(...) for a remote host or options

# vLLM, LM Studio, llama.cpp — any OpenAI-compatible server (pip install langchain-openai)
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="qwen3", base_url="http://localhost:8000/v1", api_key="EMPTY")

agent = create_agent(model, tools, system_prompt="...")
  • Tool calling is non-negotiable. Every agentis operation is a tool call, so the model must support tools reliably. On OpenRouter, filter the model list by tool support; in the Ollama library, use the tools capability tag. Small models tend to mis-parse tool schemas — prefer mid-size and larger models; as of mid-2026, Qwen3 (14B+), Llama 3.1+, and Mistral Small are solid starting points.
  • Local models keep your prompts local. With a self-hosted model, prompts, reasoning, and tool results never leave your infrastructure — only the tool calls themselves reach the agentis MCP server, and those stay policy-scoped and read-only either way.

# Handle the key safely

The design principle: the key lives in the transport layer, never in the model's context. The LLM sees tool schemas, the arguments it generates, and tool results — the Authorization header is attached by the MCP client when the tool call executes. A model can't leak a credential it never sees. Keeping it that way in a long-running deployment:

  • Inject from the environment or a secret manager. Never hardcode the key or commit it. On LangGraph Platform, use workspace environment secrets.
  • Keep it out of graph state. LangGraph checkpointers persist the full state — messages, tool arguments, tool results — and replay it into the context window. Anything placed in state is effectively shown to the model and stored durably. Configure the key on the MultiServerMCPClient at startup; never thread it through state, prompts, or tool arguments.
  • Mind tracing. LangSmith records tool arguments and results (not transport headers). If your tool traffic is sensitive, use its hide-inputs/outputs settings or anonymizer hooks.
  • One deployment, one agent, one key. Revoking a key then kills exactly one integration. Rotate from the agent's page in the dashboard — rotation issues a new key and immediately revokes the old one, so plan a secret update alongside.
  • The policy is the real guardrail. Hiding the key limits exfiltration, not use: the model can invoke whatever the key authorizes, and tool results are untrusted input. Scope the agent's data-access policy and account assignments to the minimum the job needs — that's enforced server-side on every call.
  • Short-lived credentials, if you need them. The connection config also accepts an auth field (an httpx.Auth) instead of static headers — pair it with the OAuth token endpoint for rotating 60-minute access tokens. For most headless deployments, a static key plus rotation is the right trade-off.

# Handle errors

Tool failures come back as structured JSON your agent can route on — reason says why, action says what to do:

Tool result · isError
{
  "reason": "transient_bank_error",
  "action": "retry_with_backoff",
  "retry_after_seconds": 30,
  "hint": "Bank had a transient error or timed out. Retry with exponential backoff starting at ~30s."
}

A capable model will follow these envelopes on its own; for deterministic behavior, handle retry_with_backoff and wait_then_retry in a tool-wrapping layer and surface escalate reasons (like session_expired — the bank consent needs admin re-authorization) to a human. The full enum lives in the server reference.

At the transport level, 401 means the key is invalid or revoked — fix the credential, don't retry. Expect sub-second latency on most tool calls.