Documentation
The mailbuttons-langchain package wraps the governed agent surface
(/api/v1/mcp/*) as LangChain tools. Every call is scope-checked,
capability-gated, and appended to a tamper-evident audit log on the server;
the package is a thin, faithful client of that contract.
Install
pip install mailbuttons-langchainSet the connection from the environment (the token is never inlined):
export MAILBUTTONS_API_KEY="<your scoped agent token>"
export MAILBUTTONS_API_URL="https://mbag.ai"The agent token comes from provisioning a sandbox inbox with your account API key — see step 1 of the Claude Agent SDK guide; the flow is identical.
Use
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from mailbuttons_langchain import get_mailbuttons_tools
tools = get_mailbuttons_tools() # reads MAILBUTTONS_API_URL / MAILBUTTONS_API_KEY
agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools)
result = agent.invoke(
{"messages": [("user", "Reply to the latest email in inbox 7 thanking them.")]}
)
print(result["messages"][-1].content)get_mailbuttons_tools() returns eleven StructuredTools — reading
(mailbuttons_list_messages, mailbuttons_get_message,
mailbuttons_get_thread, mailbuttons_get_attachment_text), the governed
send (mailbuttons_send_email), drafts (mailbuttons_list_drafts,
mailbuttons_get_draft), and governance (mailbuttons_extract_code,
mailbuttons_propose_sender, mailbuttons_request_promotion,
mailbuttons_audit_tail). Creating inboxes and approving drafts are not
exposed — those are human, account-key actions, not agent tools.
Governance: blocked/draft are results, not errors
This is the crux of the model. When a send is blocked by policy, or held as a draft for a human to approve, the tool returns that outcome as ordinary data so the agent can read it and reason about what to do next:
{"status": "blocked", "policy": {"decision": "blocked", "matched_rule": "blocklist"}}
{"status": "draft_pending_approval", "policy": {"decision": "draft"}, "reason": "requested_draft", "draft_id": 42}Only auth / scope / capability / network failures raise
MailbuttonsApiError:
from mailbuttons_langchain import MailbuttonsApiError
try:
...
except MailbuttonsApiError as e:
print(e.status, e.code, e.next_step) # e.g. 403 inbox_out_of_scope "Open a request..."A token can never widen its own scope. The only escalation path is a
human-approved request, which mailbuttons_propose_sender and
mailbuttons_request_promotion merely open.