Skip to main content
Glama
josephkamau32

ERP-lite MCP Server

ERP-lite MCP Server

An enterprise-ready Model Context Protocol (MCP) server that exposes ERP functionalities to AI agents. Built as a portfolio project to demonstrate AI/ML engineering maturity, this project features a realistic data schema, a genuinely enforced human-in-the-loop approval workflow for write actions, and a full compliance-style audit trail.

Overview

As enterprise AI adoption accelerates, providing LLMs with direct read/write access to ERPs is becoming essential. However, autonomous agents should not execute consequential operations (like creating purchase orders or altering system configurations) without human oversight.

This server demonstrates a robust "human-in-the-loop" pattern:

  • The AI agent can query open sales orders, check inventory levels, and identify low-stock items using its read-only tools.

  • When an agent decides to replenish stock, it can only propose a purchase requisition in a pending_approval state.

  • The agent cannot approve its own requisition. When it creates the requisition, a secure approval_token is generated and saved to the database, but is never returned to the agent.

  • A human administrator can view pending requisitions and their tokens via a dedicated, authenticated REST endpoint (GET /admin/pending-requisitions) that sits entirely outside the MCP tool surface — no agent can reach it.

  • The human retrieves the token through that endpoint and supplies it back to approve the requisition, closing the loop with a real access-control check (constant-time token comparison), not just a naming convention.

  • Every tool call — successful or failed — is written to an append-only audit log, with sensitive values like approval_token redacted before persistence.

Related MCP server: PyerP MCP Server

Architecture

graph TD
    Client[Claude Desktop / Custom Client] -- "MCP (stdio or Streamable HTTP)" --> FastMCP[FastMCP Server]
    FastMCP -- "SQLAlchemy" --> DB[(PostgreSQL Database)]
    DB --> Seed[Seed Data]
    Admin[Human Admin] -- "X-Admin-Key (REST, outside MCP surface)" --> FastMCP

Demo

https://github.com/user-attachments/assets/8584d882-ecb2-43f2-bd31-7a095bedd25c

Checking low-stock inventory → agent proposing a purchase requisition → retrieving the approval token via the admin endpoint → approving the requisition → resulting audit log entry.

Quick Start (Docker)

First, create your .env file and set a secure Admin API key:

cp .env.example .env
# Edit .env and set ADMIN_API_KEY to a secure, random value

Then start the containers:

docker compose up

This spins up:

  • A PostgreSQL database, health-checked, pre-seeded with realistic enterprise data for sales orders, inventory, requisitions, and an empty audit log table.

  • The MCP server, exposing the Streamable HTTP transport on port 8000.

⚠️ Upgrading from a previous version? seed_data.sql only runs via docker-entrypoint-initdb.d on a fresh, empty Postgres volume. If you already have a pgdata volume from an earlier run, new tables (like audit_log) won't be created automatically. To pick up schema changes:

docker compose down -v
docker compose up --build

Tools Exposed (MCP)

Tool

Type

Description

get_open_orders(status="open", limit=20)

Read

Retrieves sales orders by status.

check_inventory(material_id)

Read

Checks inventory level and computes whether it's below the reorder point.

get_low_stock_items()

Read

Identifies all inventory items below their reorder threshold.

create_requisition(material_id, quantity, requested_by)

Write

Creates a purchase requisition in pending_approval state; silently generates and stores an approval_token.

approve_pending_requisition(requisition_id, approved_by, approval_token)

Write

Approves a pending requisition — only succeeds with the correct token, sourced from the human-only admin endpoint below.

All tool calls, successful or failed, are recorded in the append-only audit log. Sensitive arguments (e.g. approval_token) are redacted before being persisted.

Admin Endpoints (human-only, outside the MCP tool surface)

  • GET /admin/pending-requisitions — lists pending requisitions with their approval tokens.

  • GET /admin/audit-log?limit=50 — returns the most recent audit log entries, newest first (limit default 50, max 500).

Both require an X-Admin-Key header matching the ADMIN_API_KEY environment variable, and fail closed (HTTP 500) if that variable isn't set at all — there is no default key baked into the app.

Testing Locally

Custom Python client (proves remote Streamable HTTP transport works independent of any chat client)

python client.py

Claude Desktop (stdio transport)

{
  "mcpServers": {
    "erp-lite": {
      "command": "/absolute/path/to/erp-lite-mcp/.venv/Scripts/python.exe",
      "args": ["-m", "src.server"],
      "env": {
        "PYTHONUNBUFFERED": "1",
        "PYTHONIOENCODING": "utf-8",
        "PYTHONPATH": "/absolute/path/to/erp-lite-mcp"
      }
    }
  }
}

Windows note: Claude Desktop's sandboxing frequently fails to resolve uv run relative module paths correctly. Using the absolute path to .venv\Scripts\python.exe, with PYTHONPATH set explicitly, is the reliable configuration.

Running Unit Tests

uv run pytest

Covers tool logic, the full requisition lifecycle (create → pending → wrong-token rejection → correct-token approval), and audit logging (including token redaction and failed-attempt capture).

All tests run against an in-memory SQLite database (SessionLocal monkeypatched in fixtures) — no Postgres or Docker required. CI (GitHub Actions) uses the same approach, so no database service is configured in the workflow.

CI

Every push and pull request to main runs the full pytest suite via GitHub Actions.

Design Decisions Worth Knowing

  • The approval gate is an access-control mechanism, not a naming convention. create_requisition never returns the token to the caller; approve_pending_requisition performs a constant-time comparison (secrets.compare_digest) against the stored value, so there's no timing side-channel and no path by which the same agent session can complete both halves of the workflow on its own.

  • The admin surface is intentionally separate from the MCP tool surface. Tokens and audit history are retrievable only via authenticated REST routes an agent has no tool access to — the trust boundary is structural, not just a prompt-level instruction telling the agent not to self-approve.

  • Audit logging is fire-and-forget but not silent. A logging failure never blocks a real tool response, but is written to server stderr, so an audit pipeline failure is observable in ops rather than invisible.

Future Enhancements

  • Token security: the approval_token is currently stored as plaintext so the admin endpoint can serve it directly. A production version would deliver it via a side channel (email/Slack) at creation time and store only a salted hash, never exposing plaintext through any API.

  • Authentication & RBAC: the admin routes currently use a single shared-secret ADMIN_API_KEY. Production use would need real identity-based auth and role checks (e.g. verifying approved_by actually holds approval rights for the requisition's value/material).

  • Audit loggingImplemented. Every tool call is recorded in an append-only audit_log table with tool name, redacted arguments, result (including failures), and timestamp — accessible via GET /admin/audit-log.

  • Policy search resource: expose procurement policy documents to the agent as an MCP Resource with semantic search, so the agent can check policy context before proposing a requisition.

Available Tools

5 tools
approve_pending_requisitionB

Approve a pending purchase requisition. This MUST be triggered by a human.

ParametersJSON Schema
NameRequiredDescriptionDefault
approved_byYes
requisition_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
statusYes
quantityYes
created_atYes
approved_atNo
material_idYes
requested_byYes
requisition_idYes

TDQS

B3.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden for transparency. It only discloses the human-trigger requirement, but does not mention side effects, irreversibility, permission requirements, or what the approval action entails. This is a significant gap for a mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise with two sentences. The first states the purpose clearly, and the second emphasizes a critical human-in-the-loop constraint. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This is an approval action with side effects, yet the description provides minimal context. It lacks prerequisites, postconditions, and any explanation of the approval workflow. The existence of an output schema may cover return values, but the overall description is insufficient for an AI agent to safely and correctly invoke the tool in a given context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description provides no explanations for 'requisition_id' or 'approved_by'. The parameter names are somewhat self-explanatory, but the tool does not compensate for the lack of schema documentation, leaving meaning entirely to inference.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Approve a pending purchase requisition') with a specific verb and resource. It distinguishes itself from siblings like create_requisition and get_open_orders, and the extra note about human triggering adds clarity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear usage constraint: it MUST be triggered by a human, implying it should not be used in automated flows. However, it does not explicitly name alternatives or conditions under which this tool is preferred over others.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_inventoryC

Check the inventory level for a specific material ID.

ParametersJSON Schema
NameRequiredDescriptionDefault
material_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
warehouseYes
descriptionYes
material_idYes
reorder_pointYes
quantity_on_handYes
below_reorder_pointYes

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden for behavioral disclosure. It states a simple check but does not describe whether the operation is read-only, what response format to expect (though an output schema exists), how invalid material IDs are handled, or any authentication requirements. The description adds little beyond the tool name.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence that directly states the tool's purpose with no redundant or irrelevant information. It is appropriately sized for a simple one-parameter tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one parameter and an output schema, the description is minimally adequate. However, it lacks differentiation from sibling tools and provides no situational context, making it incomplete for an agent trying to select among similar inventory-related tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description mentions 'specific material ID' but provides no additional meaning about the parameter, such as format, source, or constraints. Since schema description coverage is 0%, the description was expected to compensate, but it only restates the parameter name without clarifying its semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool checks inventory level for a specific material ID, which is a specific verb+resource. It inherently differentiates from list-style siblings like get_low_stock_items and get_open_orders, but does not explicitly name alternatives or scope limitations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus the sibling tools. It does not mention any exclusions, prerequisites, or alternative tools, leaving the agent to infer its appropriate context from the name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_requisitionA

Create a new purchase requisition (pending approval). The agent CANNOT approve its own requisition.

ParametersJSON Schema
NameRequiredDescriptionDefault
quantityYes
material_idYes
requested_byYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
statusYes
quantityYes
created_atYes
approved_atNo
material_idYes
requested_byYes
requisition_idYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden of behavioral disclosure. It discloses the key constraint that the agent cannot approve its own requisition, which is non-obvious and valuable. However, it does not mention other potential side effects such as validation behavior or permission requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long and immediately states the core purpose. The second sentence adds crucial behavioral context without unnecessary words. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has a simple create operation with 3 required parameters and an output schema, so return value details are likely covered. The description provides the essential workflow context (pending approval) and the critical self-approval restriction. It does not address error handling or preconditions, but given the presence of an output schema and sibling context, this is sufficient for a minimal viable description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides no descriptions for the parameters, and the description adds none either. The agent must rely solely on parameter names (material_id, quantity, requested_by), leading to ambiguity about required formats, constraints (e.g., positive quantity), or source of values. The description does not compensate for the low schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Create') and the resource ('purchase requisition'), with a parenthetical clarifying its initial state ('pending approval'). This uniquely distinguishes it from sibling tools like approve_pending_requisition, which perform a different action on the same entity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implicitly indicates this tool is for creating requisitions but does not explicitly state when to use it over alternatives. The note about not being able to approve its own requisition implies a workflow dependency but offers no explicit usage guidance or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_low_stock_itemsA

List all inventory items where the quantity on hand is below the reorder point.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It clearly states the tool is a listing operation (read-only) and defines the filter. It doesn't cover edge cases like empty results or pagination, but for a simple list tool, this is sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that is direct and front-loaded with the primary action and target. Every word is necessary, with no filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has no parameters and an output schema exists, the description fully specifies the tool's behavior. The list of all low-stock items is unambiguous, and no further context is required.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema is trivially complete. The description adds no parameter details because there are none to explain, aligning with the baseline score of 4 for no-parameter tools.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('List') and identifies the exact resource (inventory items) and condition (quantity on hand below reorder point). This clearly distinguishes it from sibling tools like get_open_orders and check_inventory.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly implies the use case: when you need all items with low stock. It does not explicitly name alternatives or exclusions, but the context (sibling tools) makes the distinction clear enough, earning a 4 rather than a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_open_ordersC

Retrieve a list of sales orders by status.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
statusNoopen

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must disclose behavior but only states the basic retrieval action. It fails to mention pagination, default status behavior, or any side-effect or safety implications. A read operation is inferred, but this is not disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no fluff. It is concise, though slightly under-specified, which is acceptable for a simple tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having an output schema, the description lacks critical context such as parameter semantics, default behavior, and relationship to sibling tools. It is a minimal viable description but leaves the agent to infer how to invoke it correctly, especially regarding 'status' values.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It only mentions 'status' but does not explain valid values or default, and it omits 'limit' entirely. The schema provides defaults but no semantic meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves a list of sales orders by status, using a specific verb and resource. It distinguishes from all sibling tools which deal with inventory or requisitions, not sales orders.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no guidance on when to use this tool versus alternatives, nor does it mention exclusions or prerequisites. The only implied usage is that it's for sales orders, but no explicit context is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A3.6/5.0
Disambiguation5/5

Each tool targets a distinct resource and action: reading sales orders, checking inventory, listing low stock, creating requisitions, and approving requisitions. There is no overlap or ambiguity between tool purposes.

Naming Consistency5/5

All names follow a clear verb_noun pattern in snake_case (get_open_orders, check_inventory, get_low_stock_items, create_requisition, approve_pending_requisition). The mix of 'get' and 'check' is minor and still consistent as retrieval verbs.

Tool Count5/5

Five tools is well-scoped for an ERP-lite server covering sales, inventory, and purchasing. Each tool fills a distinct role, and the count is neither too thin nor bloated.

Completeness2/5

The tool surface has significant gaps: there is no way to view order details, update or close orders, adjust inventory, list pending requisitions, or create purchase orders after approval. The workflow ends abruptly after requisition approval.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    An MCP server that enables AI assistants to interact with Odoo ERP apps like Inventory, CRM, Sales, and Manufacturing. It allows users to read, create, and manage Odoo records and workflows using natural language commands.
    25
    24
    1
    ISC
  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    An MCP server that enables LLM agents to interact with PyerP ERP systems via a REST API. It allows users to search, read, create, and update ERP records such as inventory, clients, and users using natural language.
  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    An enterprise-grade MCP server that enables AI agents to execute SAP RFC functions and read business data securely through the Model Context Protocol.
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server that enables AI platforms to search products, customers, and warehouses, and prepare and submit sales orders to a fixed ERP endpoint with per-session bearer authentication.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/josephkamau32/erp-lite-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server