Skip to main content
Glama
erp-mcp

erp-mcp

erp-mcp

A Model Context Protocol server that gives an agent guarded access to an ERP REST API: look up vendors and purchase orders, and post vendor invoices. It ships with a small mock ERP, so it runs and is tested without a real system.

The interesting part of putting an agent in front of an accounting system is not the read tools. It is making sure a write happens once, after a person has seen it, and that a failure tells the model what to do next. This repository is a worked example of that. The code is written from scratch; it contains no client code or data.

Tools

Tool

Kind

What it does

search_vendors

read

Find vendors by name or tax number, at most 10 matches

list_purchase_orders

read

A vendor's orders with the amount still open, paged

get_purchase_order

read

One order by number

post_vendor_invoice

write

Run the posting checks; post only when confirm is true

Related MCP server: namakan-mcp-erp

Design decisions

  • Writes are two-step. post_vendor_invoice without confirm runs every check and returns a preview; nothing is sent to the ERP. The server instructions tell the model to show the preview and post only after the user agrees. A failed check blocks the posting even when confirm is true.

  • Posting is idempotent. The idempotency key is derived from vendor and invoice reference, so a retry after a timeout returns the document that was already created instead of creating a second one. A later attempt to post the same reference is caught by the duplicate check before anything is written.

  • Checks are data, not prose. The result lists each check (vendor, amounts, currency, duplicate, purchase_order) with pass or fail and the values compared, so the model can explain what is wrong and a host application can render it.

  • Errors say what to do next. ERP errors are mapped to tool errors with a stable code and one line of advice per code: retry, ask the user, or stop. A timeout on a posting says the call is safe to repeat.

  • Arguments are validated by the schema. Amount ranges, the currency pattern, the status enum and page limits are part of the tool's input schema, so invalid calls are rejected before the ERP is called.

  • The ERP is behind one module. client.py is the only place that knows paths, headers and the error format. The API key lives there and never appears in tool input or output.

  • Typed, bounded results. Tools return Pydantic models, so results carry an output schema. Lists are capped and paged with next_offset.

Quick start

pip install -e ".[dev]"
pytest

Run it against the mock ERP:

ERP_API_KEY=dev-key erp-mock          # terminal 1: mock ERP on 127.0.0.1:8080
ERP_API_KEY=dev-key erp-mcp           # terminal 2: MCP server on stdio

To use it from an MCP host, register the stdio command:

{
  "mcpServers": {
    "erp": {
      "command": "erp-mcp",
      "env": { "ERP_BASE_URL": "http://127.0.0.1:8080", "ERP_API_KEY": "dev-key" }
    }
  }
}

A preview call and its result:

{"vendor_tax_id": "4821963570", "reference": "FTR2026000123", "document_date": "2026-03-12",
 "gross_amount": "12000.00", "tax_amount": "2000.00", "currency": "TRY", "purchase_order": "4500001201"}
{
  "posted": false,
  "document_number": null,
  "checks": [
    {"name": "vendor", "passed": true, "detail": "V1001 Kuzey Ambalaj Sanayi A.Ş."},
    {"name": "amounts", "passed": true, "detail": "gross 12000.00, tax 2000.00"},
    {"name": "currency", "passed": true, "detail": "invoice TRY, vendor TRY"},
    {"name": "duplicate", "passed": true, "detail": "0 posted with reference FTR2026000123"},
    {"name": "purchase_order", "passed": true, "detail": "open, remaining 75000.00, net 10000.00"}
  ],
  "next_step": "All checks passed. Call again with confirm=true to post."
}

Tests

The suite connects an MCP client to the server in memory and the server to the mock ERP through an ASGI transport, so every test goes through the protocol and the HTTP layer without opening a socket. It covers:

  • tool listing and read/write annotations

  • paging

  • a preview writes nothing

  • a confirmed posting writes exactly once, also when the call is repeated

  • the same idempotency key returns the same document

  • each failed check blocks posting even with confirm

  • invalid arguments never reach the ERP

  • the API key does not appear in tool output

  • a wrong API key is reported as a configuration problem

Layout

src/erp_mcp/
  server.py      tools, checks, error advice
  client.py      HTTP client and error mapping
  mock_erp.py    in-memory ERP with a REST API
tests/
  test_server.py

Pointing it at a real system

Replace client.py. The tools depend on five client methods (search_vendors, list_purchase_orders, get_purchase_order, find_invoices, post_invoice) and on ErpError codes; the mapping in ADVICE lists the codes the server knows how to explain. The mock ERP is not a model of any particular product.

Requires the MCP Python SDK 2.x.

License

MIT

Available Tools

4 tools
get_purchase_orderC
Read-only

Get one purchase order by its number.

Inputs: number (required). Returns: amount, number, status, currency, +3 more

list_purchase_ordersB
Read-only

List a vendor's purchase orders with the amount still open on each.

Inputs: vendor_id (required), limit, offset, status. Returns: items, total, next_offset

post_vendor_invoiceB
Idempotent

Post a vendor invoice. Without confirm it only runs the checks and returns a preview.

Posting is idempotent: repeating a confirmed call with the same vendor and reference returns the document that was already created.

Inputs: currency (required), reference (required), tax_amount (required), gross_amount (required), +4 more. Returns: checks, posted, next_step, document_number

search_vendorsA
Read-only

Find vendors by name or tax number. Returns at most 10 matches.

Inputs: query (required). Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 4 tools

    Disambiguation5/5

    Each tool targets a distinct resource and action: vendor search, PO listing, single PO retrieval, and invoice posting. There is no overlap between listing purchase orders and getting one order, since one is scoped per-vendor and the other per-document.

    Naming Consistency5/5

    All four names follow a strict verb_noun snake_case pattern (search_vendors, list_purchase_orders, get_purchase_order, post_vendor_invoice). Verbs are well chosen and consistently distinguish read vs. write operations.

    Tool Count4/5

    Four tools is on the lean side but each supports a step in the vendor-invoice workflow, so nothing is redundant. A slightly broader surface (e.g. retrieving posted invoices) would make the set feel less minimal.

    Completeness4/5

    The core workflow — find vendor, inspect open PO amounts, read a PO, then post/preview an invoice — is fully covered, including idempotency. Minor gaps remain: no way to list or fetch invoices by vendor/reference outside of a repeat post, and no vendor-detail retrieval.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to query and operate on ERP data through a unified tool interface, working across CSV exports, SFTP drop folders, SQLite/ODBC, and optional enterprise APIs. It supports purchase orders, vendors, and inventory lookups while keeping agent-facing tools consistent regardless of backend.
      MIT
    • F
      license
      A
      quality
      C
      maintenance
      Enables an AI agent to handle accounts payable tasks against a mock ERP, including reading and writing bills and vendors, checking duplicates, matching invoices, recommending approvals, and queuing payment releases, with configurable profiles that limit available tools.
      11
      -