Source: https://cli.nylas.com/guides/agent-accounts-langchain

# Use Agent Accounts with LangChain

LangChain and LangGraph agents reason; they need tools to act. Giving one email and calendar usually means a provider SDK, OAuth, and a pile of glue. The Nylas CLI shortcut is to wrap each command as a tool: one subprocess call sends mail or reads a calendar, JSON goes in and out, and the agent account handles auth. This guide wraps the CLI as LangChain tools, builds a LangGraph agent around them, and keeps the agent contained by workspace rules the Python can't override.

Written by [Pouya Sanooei](https://cli.nylas.com/authors/pouya-sanooei) Software Engineer

Updated June 8, 2026

> **TL;DR:** Wrap each Nylas CLI command in a LangChain `@tool` that shells out with `subprocess.run` and parses `--json` output. Bind the tools to a LangGraph agent, and a workspace policy bounds what the agent can send.

## What does using agent accounts with LangChain mean?

Using an agent account with LangChain means giving a LangChain or LangGraph agent the ability to send and read email and calendar through the CLI, exposed as tools the model can call. The agent reasons about what to do; each tool is a thin Python function that runs one `nylas` command and returns its result. The agent account provides the identity and the auth, so the Python never touches OAuth or a provider SDK.

This is the subprocess tool pattern: instead of importing an email library, the agent calls a command-line program. It works because the CLI already does the hard parts — authentication, provider differences, JSON formatting — so each tool is a few lines. The model sees a clean function signature; the function shells out.

LangGraph agent calls a Python tool, which shells out to the Nylas CLI and returns JSONLangGraph agentmodel + ReAct loop@tool wrappersubprocess.runNylas CLIagent account, JSONJSON flows back the same way

## Why wrap the CLI instead of a provider SDK?

Wrapping the CLI removes the integration layer a provider SDK forces on you. An SDK means OAuth setup, token refresh, and provider-specific clients in your Python process; the CLI means one binary that already handles all of that behind an agent account. Each tool becomes a subprocess call returning JSON, not an SDK object graph you marshal yourself.

The payoff is fewer dependencies and a smaller blast radius. Your LangChain process holds no OAuth tokens and imports no mail library — the credential is the agent account's, managed by the CLI, and a bug in your agent code can't leak a provider token it never had. For the broader pattern of driving the CLI from agent code, see [building an AI agent CLI](https://cli.nylas.com/guides/build-email-agent-cli).

## How do I wrap a nylas command as a LangChain tool?

A tool is a Python function with the LangChain [`@tool` decorator](https://python.langchain.com/docs/concepts/tools/) and a docstring the model reads to decide when to call it. The body runs one CLI command with [`subprocess.run`](https://docs.python.org/3/library/subprocess.html) and returns the output. A shared helper keeps each tool to a few lines:

```python
import json
import subprocess
from langchain_core.tools import tool

def _nylas(*args: str) -> str:
    """Run a nylas CLI command and return stdout (raises on a non-zero exit)."""
    result = subprocess.run(
        ["nylas", *args],
        capture_output=True,
        text=True,
        check=True,
    )
    return result.stdout

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email from the agent account to a single recipient."""
    _nylas("email", "send", "--to", to, "--subject", subject, "--body", body)
    return f"sent to {to}"
```

The `check=True` argument raises on a non-zero exit, so a failed send surfaces as a Python exception the agent can see rather than a silent no-op. Passing arguments as a list — never a shell string — means a recipient or subject from the model can't inject a shell command. That list form is the input-validation boundary for the whole pattern.

## How do I give the agent read and calendar tools?

The same helper turns every command into a tool. A read tool parses `--json` output into Python objects the model can reason over, and a calendar tool books an event — all on the one agent account grant:

```python
@tool
def list_unread() -> list:
    """List unread emails in the agent's inbox with sender and subject."""
    out = _nylas("email", "list", "--unread", "--json")
    return [
        {"id": m["id"], "from": m["from"][0]["email"], "subject": m["subject"]}
        for m in json.loads(out)
    ]

@tool
def list_events(days: int = 1) -> list:
    """List upcoming calendar events for the next N days."""
    out = _nylas("calendar", "events", "list", "--days", str(days), "--json")
    return [{"title": e["title"], "start": e["when"]["start_time"]} for e in json.loads(out)]
```

Each tool projects only the fields the model needs, which keeps tool output small and the model's context cheap — a list of three-key dicts instead of full message objects. The `json.loads()` parsing happens in Python, so the model receives clean structured data, not raw text to interpret.

## How do I build the agent with LangGraph?

With the tools defined, a LangGraph agent is a few lines. The [`create_react_agent`](https://langchain-ai.github.io/langgraph/) prebuilt binds the tools to a model and runs the reason-act loop — the model decides which tool to call, the tool runs the CLI command, and the result feeds back into the model. Recent LangChain versions also ship an equivalent `create_agent`; either binds the same tools:

```python
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent

model = ChatAnthropic(model="claude-opus-4-8")
agent = create_react_agent(model, tools=[send_email, list_unread, list_events])

result = agent.invoke({
    "messages": [("user", "Check my inbox and reply to any meeting requests with my availability.")]
})
print(result["messages"][-1].content)
```

The agent now reads the inbox through `list_unread`, reasons over the results, and sends through `send_email` — each step a CLI command behind a tool. Swap the model for any LangChain chat model; the tools don't change because they only depend on the CLI being on the path.

## How do I keep the LangChain agent contained?

A LangChain email agent reads untrusted content and can send mail — the [lethal trifecta](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/) of private data, untrusted input, and external communication. You can't fully prevent a prompt injection in an email from steering the model, so the containment lives outside the agent's decision loop, on the agent account's workspace. An outbound rule blocks unexpected recipients before the send command reaches the pipeline:

```bash
nylas agent rule create \
  --name "Block flagged langchain-agent outbound" \
  --trigger outbound \
  --condition recipient.domain,is,exfil.example \
  --action block
```

Because the rule is enforced by the platform, not the Python, the model can't prompt its way past it — even a tool call the model was tricked into making hits the workspace rule. Pair it with a policy send cap so a runaway loop can't flood recipients. The full containment pattern is in [Stop Your AI Agent From Going Rogue](https://cli.nylas.com/guides/stop-ai-agent-going-rogue), and the rule reference is in [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies).

## LangChain tools vs MCP

Wrapping the CLI as LangChain tools and connecting over MCP solve the same problem two ways. Subprocess tools are simplest when the agent is a single Python program — no server, no protocol, just functions. MCP is the better fit when many different agents or clients need the same email tools through a standard interface.

| Concern | LangChain subprocess tools | MCP server |
| --- | --- | --- |
| Setup | A function per command, in-process | **A server the client connects to** |
| Best for | One Python agent | **Many agents/clients, one interface** |
| Coupling | Tools live in your code | **Tools live behind a protocol** |

Both reach the same agent account and the same workspace guardrails. For the MCP route, see [setting up an email MCP server](https://cli.nylas.com/guides/mcp-email-server-setup) and the [MCP vs API comparison](https://cli.nylas.com/guides/mcp-vs-api-ai-agents).

## Next steps

- [Build an AI Agent CLI for Email](https://cli.nylas.com/guides/build-email-agent-cli) — the subprocess tool pattern in depth, framework-agnostic
- [Getting Started with Agent Accounts](https://cli.nylas.com/guides/getting-started-agent-accounts) — the identity behind the CLI the tools call
- [Set Up an Email MCP Server](https://cli.nylas.com/guides/mcp-email-server-setup) — the MCP alternative to subprocess tools
- [Stop Your AI Agent From Going Rogue](https://cli.nylas.com/guides/stop-ai-agent-going-rogue) — the workspace rules that contain the LangChain agent
- [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies) — every outbound rule and send cap available
- [Build a CrewAI email agent](https://cli.nylas.com/guides/crewai-email-agent) — the same CLI-as-tool pattern in CrewAI
- [Full command reference](https://cli.nylas.com/docs/commands) — every `nylas email` and `nylas calendar` command the tools wrap
- [Nylas v3 API documentation](https://developer.nylas.com/) — the API the CLI calls under the tools
