inhold Book a call

LangGraph

How do I connect a LangGraph agent to Inhold?

Inhold connects to LangGraph through langchain-mcp-adapters with one streamable HTTP server entry. The ToolNode returns a refusal as a tool error and the graph keeps going.

Last checked 31 Oct 2026, written by Vivek

Inhold is being built. No customers yet. Repo goes public 9 Nov 2026.

The part you copy

One MCP server entry

Everything else in the example file is the demo.

headers = {"Authorization": "Bearer <token>", "Inhold-Purpose": "billing"}
MultiServerMCPClient({"inhold": {"transport": "streamable_http", "url": url, "headers": headers}})

Start a local gateway

From the repo root, with Inhold installed. It makes a fresh database, seeds one billing note and one clinical note, and serves MCP at http://127.0.0.1:8700/mcp.

python examples/gateway.py

Run the LangGraph example

In a second terminal. Each SDK gets its own virtualenv because their MCP client versions disagree.

python -m venv .venv-lg && .venv-lg/bin/pip install -r examples/requirements-langgraph.txt
.venv-lg/bin/python examples/with_langgraph.py --no-model

Read the output

The billing agent gets the billing note and a refusal for the clinical one. Then the script reads the log the way the hospital's security team would.

context_get ctx_a4fc2fea0a10 -> Payer Northwind rejects claims without modifier 25; resubmit within 30 days.
context_get ctx_1c79d9bf86eb -> REFUSED: Error executing tool context_get: not readable; do not retry

audit rows written during this run:
  #5 billing-agent read ctx_a4fc2fea0a10 purpose=billing -> ALLOW (purpose 'billing' may read internal)
  #6 billing-agent read ctx_1c79d9bf86eb purpose=billing -> DENY (purpose 'billing' may not read items written under 'prior_auth')

This run used --no-model: the same two MCP calls through the SDK's own client, with no model in the loop. With a key, the model decides when to call the tools. Output from our run on main 478aded, 31 Oct 2026; ids differ on each run.

Worth knowing

Four things the example shows

The purpose is a header

It sits in your config. The model never sees it and no tool takes it as an argument, so a prompt injection cannot talk its way into another purpose.

A refusal is a normal tool error

The SDK hands it back to the model and the run carries on. The message is the same for a forbidden note and a missing one, so the agent cannot probe. The real reason is only in the audit row.

Hiding tools is your hygiene

The example offers the model only the read tools. The hospital's control is the policy on the gateway, which decides every call either way.

Sign-in in a real install

In AWS your agent signs each request with its IAM role instead of a bearer token. The adapter's client takes an auth= object, so every request gets a fresh signature; demo/integrate/remote_client.py shows the signing. Agent sign-in has been tested against a stand-in for STS, not yet a real AWS account.