# Build a Small-Business Invoice Agent with LangChain Deep Agents and MCP

> Source: <https://dev.to/businessedgeanalytics/build-a-small-business-invoice-agent-with-langchain-deep-agents-and-mcp-ggp>
> Published: 2026-11-04 03:03:01+00:00

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:

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.

A small company receives vendor invoices by email. For each one, someone has to:

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.

```
            ┌──────────────────────────┐
            │   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.

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.

``` python
# 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:

`Path(filename).name` stops 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`.

`langchain-mcp-adapters` turns MCP tools into regular LangChain tools:

``` python
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.

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.

``` python
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.)

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:

``` python
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.

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](https://www.businessedgeanalytics.com/services/intelligent-document-processing.html) 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](https://www.businessedgeanalytics.com/blog/custom-ai-vs-off-the-shelf.html). For more workflow ideas beyond invoices, see [5 AI workflow automations every business needs](https://www.businessedgeanalytics.com/blog/ai-workflow-automations.html).

*Jimmy Hernandez is a senior AI engineer and founder of [Business Edge Analytics](https://www.businessedgeanalytics.com), 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.*
