# Connect LangChain to Axiom

**Transport:** stdio (MultiServerMCPClient also supports HTTP/SSE)
**Auth:** local-stdio (Phase 0)
**Status:** Untested — recipe inferred from upstream docs

LangChain integrates MCP servers via the `langchain-mcp-adapters`
package. The `MultiServerMCPClient` wrapper turns each MCP tool into a
LangChain `Tool` that an `AgentExecutor` (or LangGraph node) can call.

## Install prerequisites

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

Same Axiom prerequisites as the other recipes.

## Configure the connection

```python
import asyncio

from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic


async def main() -> None:
    client = MultiServerMCPClient(
        {
            "axiom-root": {
                "transport": "stdio",
                "command": "/absolute/path/to/.venv/bin/axi",
                "args": ["mcp", "serve"],
            }
        }
    )
    tools = await client.get_tools()           # one LangChain Tool per MCP tool
    print(f"Loaded {len(tools)} Axiom tools:")
    for t in tools:
        print(f"  {t.name}")

    # Plug into a LangGraph React agent (or any LangChain chain that
    # accepts a Tool list — RunnableLambda, AgentExecutor, etc.).
    agent = create_react_agent(
        ChatAnthropic(model="claude-sonnet-4-5"),
        tools,
    )
    result = await agent.ainvoke(
        {"messages": [("user", "Call axiom_node__hooks_list and show the JSON result.")]}
    )
    print(result["messages"][-1].content)


if __name__ == "__main__":
    asyncio.run(main())
```

Worktree variant — set `env` on the server entry:

```python
client = MultiServerMCPClient({
    "axiom-root": {
        "transport": "stdio",
        "command": "/absolute/path/to/.venv/bin/python3",
        "args": ["-m", "axiom.extensions.builtins.mcp.server"],
        "env": {"PYTHONPATH": "/absolute/path/to/axiom-checkout/src"},
    },
})
```

## Verify

The script above prints the loaded tool list and the agent's response.
A direct, agent-free check:

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient


async def smoke() -> None:
    client = MultiServerMCPClient({
        "axiom-root": {"transport": "stdio", "command": "axi", "args": ["mcp", "serve"]},
    })
    tools = await client.get_tools()
    hooks_tool = next(t for t in tools if t.name == "axiom_node__hooks_list")
    result = await hooks_tool.ainvoke({})
    print(result)


asyncio.run(smoke())
```

Expected output:

```json
{
  "node": "/Users/<you>/.axiom",
  "hooks_total": 0,
  "events_observed": [],
  "by_event": {}
}
```

## Notes

- `MultiServerMCPClient` accepts multiple servers; you can mix Axiom
  with other MCP servers (filesystem, Brave search, etc.) in the same
  agent. Tool-name collisions are caught at `get_tools()` time.
- The package supports both `transport: "stdio"` and
  `transport: "streamable_http"`; the latter needs Axiom's Phase 5 auth.
- LangGraph state-machine integration: `tools` can be passed to
  `langgraph.prebuilt.ToolNode` as well as to `create_react_agent`.

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| `FileNotFoundError: axi` | venv not on PATH | Use absolute path in `command` |
| `pydantic.ValidationError` on tool args | LangChain inferred wrong schema | The MCP tool's `inputSchema` is authoritative; report upstream if mismatch |
| `RuntimeError: event loop closed` | reusing client across loops | Use one `MultiServerMCPClient` per loop; don't cache across coroutines |

## References

- ADR-038 §D7
- langchain-mcp-adapters: https://github.com/langchain-ai/langchain-mcp-adapters

_Copyright (c) 2026 B-Tree Ventures, LLC. Apache-2.0 licensed._
