> For the complete documentation index, see [llms.txt](https://codemash.gitbook.io/codemash/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://codemash.gitbook.io/codemash/ai/products/frameworks/langgraph.md).

# LangGraph

A LangGraph agent that uses the Norbix tools of one toolset.

You build an agent graph and want one of its steps to look things up in, or change, your Norbix backend.

## How Norbix models it

LangGraph uses the same MCP adapter as [LangChain](/codemash/ai/products/frameworks/langchain.md) (MCP = Model Context Protocol, the standard way AI tools call outside services). Load the tools of one toolset — the URL's `?toolsets=` keeps the list short — and hand them to the prebuilt ReAct agent with any chat model (an LLM, large language model, that can call tools).

```bash
pip install langchain-mcp-adapters langgraph langchain-openai
```

```python
import asyncio, os
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

async def main():
    client = MultiServerMCPClient({
        "norbix": {
            "transport": "streamable_http",
            # ?toolsets= keeps the list short: the read-only project inspection tools
            "url": os.environ["NORBIX_MCP_URL"] + "?toolsets=ai:project-inspect",
            "headers": {"Authorization": f"Bearer {os.environ['NORBIX_API_KEY']}"},
        }
    })
    tools = await client.get_tools()
    print(len(tools), "tools")
    model = ChatOpenAI(model=os.environ.get("MODEL", "gpt-4o-mini"), base_url=os.environ.get("OPENAI_BASE_URL"))
    agent = create_react_agent(model, tools)
    result = await agent.ainvoke({"messages": "What is my Norbix account profile?"})
    for m in result["messages"]:
        print(type(m).__name__, ":", (m.content or str(getattr(m, "tool_calls", "")))[:200])

asyncio.run(main())
```

What it printed on a test installation (a test model in the OpenAI shape, and a **Viewer** key on one project):

```
146 tools
HumanMessage : What is my Norbix account profile?
AIMessage : [{'name': 'get_account_profile', 'args': {}, 'id': 'call_fake_1', 'type': 'tool_call'}]
ToolMessage : [{'type': 'text', 'text': '{"ok":false,"errors":[{"code":"CM-ERRORS-MEMBERSHIP-039","message":"Caller is missing required permission 'account:read on acc_…:*:account:profile:all'."}]}', …}]
AIMessage : Here is your profile: unknown. …
```

The graph ran end to end: the model chose a Norbix tool, the MCP server ran it, and the answer went back to the model. The tool was refused because a Viewer key on a project cannot read account data — a key with an account role (for example **Auditor**) can. With a real model the agent picks the tools and writes the answer itself.

LangGraph 1.x prints a notice that `create_react_agent` moves to `langchain.agents.create_agent`; both take the same model and tools.

For a hosted graph, load the tools once per worker and pass each user's own key in the `Authorization` header, so every user's agent has that user's roles. Start or resume a graph when data changes with a [trigger](/codemash/cloud/concepts/triggers.md) that calls your webhook.

| Version used             |        |
| ------------------------ | ------ |
| `langgraph`              | 1.2.14 |
| `langchain-mcp-adapters` | 0.3.2  |
| `langchain-openai`       | 1.6.7  |

## Where you use it

* [LangChain](/codemash/ai/products/frameworks/langchain.md) · [Frameworks](/codemash/ai/products/frameworks.md)
* [MCP server](/codemash/ai/developers/mcp-server.md) · [What agents can do](/codemash/ai/developers/what-agents-can-do.md) · [Read-only keys](/codemash/ai/developers/read-only-keys.md)
