LangChain Deep Agents 和 MCP 如何構建小型企業發票智能體
Build a Small-Business Invoice Agent with LangChain Deep Agents and MCP
文章示範如何以 LangChain Deep Agents 和 MCP 建構小型企業發票分流多智能體系統,由協調器安排資料抽取與審核,並產生供業主審批的摘要。MCP 伺服器提供只讀發票及供應商查詢工具,系統檢查重複發票、總額不符及金額異常,但不會核准或支付款項。文章亦建議以 Pydantic 驗證結構化輸出、記錄工具呼叫,並用 20–30 張匿名舊發票建立回歸測試集。
Most "AI agent" tutorials stop at a chatbot that calls a weather API. Real small-business work looks different: a stack of PDFs in an inbox, a spreadsheet of vendors, and a person who has to decide what gets paid. In this post we'll build a small but realistic multi-agent system that triages incoming invoices, using two pieces that fit together well:
- MCP (Model Context Protocol) to expose business data and actions as tools in a standard way.
- LangChain Deep Agents to plan the work and delegate to specialist subagents.
The goal isn't a production system in 1,200 words. It's a clean architecture you can extend. Library APIs move fast, so pin your versions and check the docs for deepagents, langchain-mcp-adapters and mcp before you copy this into anything serious.
The use case
A small company receives vendor invoices by email. For each one, someone has to:
- Pull out the vendor, invoice number, date, line items and total.
- Check the vendor is known and the invoice isn't a duplicate.
- Flag anything unusual (a new vendor, a total far above that vendor's history).
- Write a short summary for the owner to approve.
None of this needs a model to "be creative". It needs reliable extraction, a few lookups, and a clear audit trail. That's a good fit for agents with narrow tools.
Architecture
┌──────────────────────────┐
│ Orchestrator (Deep │
│ Agent: plans + todo) │
└─────┬───────────┬────────┘
│ │
┌─────────▼──┐ ┌────▼─────────┐
│ extractor │ │ reviewer │
│ subagent │ │ subagent │
└─────┬──────┘ └────┬─────────┘
│ MCP tools │
┌─────▼───────────────▼────────┐
│ MCP server: vendors, invoice │
│ history, file reader │
└──────────────────────────────┘
The orchestrator never touches the database directly. Everything goes through MCP tools, which means the same server can later serve a different agent framework, a desktop assistant, or an internal app.
Step 1: Expose business data with an MCP server
Install the dependencies:
pip install "mcp[cli]" langchain-mcp-adapters deepagents langchain-openai
Here's a minimal MCP server using FastMCP. For the demo, the "database" is SQLite.
# invoice_server.py
import sqlite3
from pathlib import Path
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("invoices")
DB = Path("business.db")
def q(sql: str, params: tuple = ()):
with sqlite3.connect(DB) as conn:
conn.row_factory = sqlite3.Row
return [dict(r) for r in conn.execute(sql, params).fetchall()]
@mcp.tool()
def read_invoice_text(filename: str) -> str:
"""Return the extracted text of an invoice file in the inbox folder."""
path = Path("inbox") / Path(filename).name # prevent path traversal
return path.read_text()
@mcp.tool()
def lookup_vendor(name: str) -> list[dict]:
"""Find known vendors whose name matches (case-insensitive)."""
return q("SELECT id, name, payment_terms FROM vendors WHERE name LIKE ?",
(f"%{name}%",))
@mcp.tool()
def invoice_history(vendor_id: int) -> list[dict]:
"""Past invoices for a vendor: number, date, total."""
return q("SELECT number, date, total FROM invoices WHERE vendor_id = ? "
"ORDER BY date DESC LIMIT 24", (vendor_id,))
@mcp.tool()
def is_duplicate(vendor_id: int, invoice_number: str) -> bool:
"""True if this vendor already has an invoice with this number."""
return bool(q("SELECT 1 FROM invoices WHERE vendor_id = ? AND number = ?",
(vendor_id, invoice_number)))
if __name__ == "__main__":
mcp.run(transport="stdio")
A few deliberate choices:
- Read-only tools. The agent can look things up but cannot pay or approve anything. Approval stays human.
- Narrow, well-described tools. The docstring is what the model sees. Specific beats clever.
-
Input hygiene.
Path(filename).namestops the model (or a malicious PDF) from reading files outside the inbox.
You can poke at it with the MCP inspector: mcp dev invoice_server.py.
Step 2: Load MCP tools into LangChain
langchain-mcp-adapters turns MCP tools into regular LangChain tools:
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"invoices": {
"command": "python",
"args": ["invoice_server.py"],
"transport": "stdio",
}
})
tools = await client.get_tools() # inside an async function
by_name = {t.name: t for t in tools}
Splitting by name matters. Each subagent should only see the tools it needs.
Step 3: Define specialist subagents
Deep Agents gives you a planning tool (a todo list), a virtual file system for scratch notes, and the ability to spawn subagents with their own prompt and toolset. Subagents keep the orchestrator's context clean: the extractor can wade through a messy invoice without dumping all of that text into the main conversation.
extractor = {
"name": "extractor",
"description": "Reads one invoice file and returns structured JSON.",
"system_prompt": (
"You extract invoice data. Call read_invoice_text, then return ONLY JSON "
"with keys: vendor_name, invoice_number, date (YYYY-MM-DD), "
"line_items (list of {description, amount}), total. "
"If a field is missing, use null. Never guess numbers."
),
"tools": [by_name["read_invoice_text"]],
}
reviewer = {
"name": "reviewer",
"description": "Checks extracted invoice JSON against vendor records.",
"system_prompt": (
"You review invoices. Given extracted JSON: look up the vendor, check for "
"duplicates, and compare the total to that vendor's history. Report "
"flags: UNKNOWN_VENDOR, DUPLICATE, TOTAL_MISMATCH (line items don't sum), "
"UNUSUAL_AMOUNT (well above typical). Explain each flag in one sentence."
),
"tools": [by_name["lookup_vendor"], by_name["invoice_history"],
by_name["is_duplicate"]],
}
Note what's not delegated to the model: the definition of each flag. You decide the business rules. The model applies them and explains its reasoning.
Step 4: The orchestrator
import asyncio
from deepagents import create_deep_agent
ORCHESTRATOR_PROMPT = """You triage vendor invoices for a small business.
For each file you are given:
1. Plan the work with your todo list.
2. Delegate extraction to the `extractor` subagent.
3. Pass its JSON to the `reviewer` subagent.
4. Write a summary per invoice: vendor, number, total, flags, and a
recommendation of APPROVE or NEEDS_HUMAN_REVIEW.
You never approve payments yourself."""
async def main(files: list[str]):
agent = create_deep_agent(
model="openai:gpt-4.1", # any tool-calling model works
tools=[], # orchestrator delegates everything
system_prompt=ORCHESTRATOR_PROMPT,
subagents=[extractor, reviewer],
)
result = await agent.ainvoke({
"messages": [{"role": "user",
"content": f"Triage these invoices: {', '.join(files)}"}]
})
print(result["messages"][-1].content)
asyncio.run(main(["acme-1043.txt", "newco-0007.txt"]))
(In real code, create the MCP client and subagent dicts inside main so await client.get_tools() runs in the same event loop.)
Step 5: Make it trustworthy
The demo works. Here's what turns it into something a business can actually rely on:
Validate structured output. Don't trust the extractor's JSON. Parse it with Pydantic and send it back for a retry on failure:
from pydantic import BaseModel, model_validator
class LineItem(BaseModel):
description: str
amount: float
class Invoice(BaseModel):
vendor_name: str
invoice_number: str
date: str
line_items: list[LineItem]
total: float
@model_validator(mode="after")
def totals_match(self):
if abs(sum(i.amount for i in self.line_items) - self.total) > 0.01:
raise ValueError("line items do not sum to total")
return self
Arithmetic checks belong in code, not in a prompt.
Keep a human in the loop. The agent recommends, a person approves. Deep Agents supports interrupting before sensitive tool calls if you later add write tools like mark_approved.
Log everything. Store every tool call and result. When the owner asks "why was this flagged?", you can show them.
Evaluate with real samples. Gather 20–30 anonymized past invoices with known correct answers, and re-run them every time you change a prompt or model. That's your regression suite.
Treat documents as untrusted input. An invoice can contain text like "ignore previous instructions". Read-only tools and narrow subagent permissions limit what a prompt injection can do.
Why this pattern generalizes
Swap "invoices" for insurance claims intake, real estate transaction documents, or permit applications, and the shape is the same: an MCP server wrapping your systems, an extractor, a reviewer that applies your rules, and an orchestrator that writes a decision-ready summary. That's the core of most intelligent document processing work. The hard parts are the business rules and the evaluation set, not the framework.
If you're deciding whether to build something like this or buy a SaaS tool, I wrote a longer comparison of custom AI vs. off-the-shelf solutions. For more workflow ideas beyond invoices, see 5 AI workflow automations every business needs.
Jimmy Hernandez is a senior AI engineer and founder of Business Edge Analytics, a Louisville, KY firm that builds multi-agent systems, RAG assistants and document automation for businesses and public-sector teams. He works with LangChain Deep Agents, MCP and Python, and has a background in insurance and real estate.
來源:DEV Community · dev.to