ERP-lite MCP Server
This server provides AI agents with read/write access to ERP data (sales orders, inventory, and purchase requisitions), enforcing human-in-the-loop approval for write operations.
Read-only tools:
get_open_orders: Fetch sales orders by status (e.g., 'open') with a configurable limit.check_inventory: Check inventory level for a specific material ID, including reorder point and whether it's below that threshold.get_low_stock_items: List all inventory items below their reorder point.
Write tools (require human approval):
create_requisition: Propose a new purchase requisition for a material and quantity; starts inpending_approval.approve_pending_requisition: Approve a pending requisition; must be explicitly triggered by a human.
Human-in-the-loop workflow ensures agents can propose purchases but cannot finalize them without human intervention.
Transports: Supports SSE (HTTP) and stdio for integration with custom clients and Claude Desktop.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ERP-lite MCP ServerCheck inventory levels for material M-100"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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_approvalstate.The agent cannot approve its own requisition. When it creates the requisition, a secure
approval_tokenis 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_tokenredacted 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)" --> FastMCPDemo
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 valueThen start the containers:
docker compose upThis 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.sqlonly runs viadocker-entrypoint-initdb.don a fresh, empty Postgres volume. If you already have apgdatavolume from an earlier run, new tables (likeaudit_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 |
| Read | Retrieves sales orders by status. |
| Read | Checks inventory level and computes whether it's below the reorder point. |
| Read | Identifies all inventory items below their reorder threshold. |
| Write | Creates a purchase requisition in |
| 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 (limitdefault 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.pyClaude 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 runrelative module paths correctly. Using the absolute path to.venv\Scripts\python.exe, withPYTHONPATHset explicitly, is the reliable configuration.
Running Unit Tests
uv run pytestCovers 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 (
SessionLocalmonkeypatched 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_requisitionnever returns the token to the caller;approve_pending_requisitionperforms 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_tokenis 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. verifyingapproved_byactually holds approval rights for the requisition's value/material).Audit logging✅ Implemented. Every tool call is recorded in an append-onlyaudit_logtable with tool name, redacted arguments, result (including failures), and timestamp — accessible viaGET /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 toolsapprove_pending_requisitionB
Approve a pending purchase requisition. This MUST be triggered by a human.
| Name | Required | Description | Default |
|---|---|---|---|
| approved_by | Yes | ||
| requisition_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| status | Yes | |
| quantity | Yes | |
| created_at | Yes | |
| approved_at | No | |
| material_id | Yes | |
| requested_by | Yes | |
| requisition_id | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| material_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| warehouse | Yes | |
| description | Yes | |
| material_id | Yes | |
| reorder_point | Yes | |
| quantity_on_hand | Yes | |
| below_reorder_point | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| quantity | Yes | ||
| material_id | Yes | ||
| requested_by | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| status | Yes | |
| quantity | Yes | |
| created_at | Yes | |
| approved_at | No | |
| material_id | Yes | |
| requested_by | Yes | |
| requisition_id | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| status | No | open |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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
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.
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.
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.
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
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
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Let AI agents query data and act across all your business apps via MCP.
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Unified MCP Server is a remote MCP connector for AI agents and vertical AI products that provides access to 22,000+ authorized SaaS tools across 400+ integrations and 24 categories directly inside LLMs (Claude, GPT, Gemini, Cohere). Tools operate only on explicitly authorized customer connections, enabling agents to safely read and write against live third-party systems.
Related MCP Servers
- AlicenseBqualityDmaintenanceAn 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.25241ISC
- FlicenseNot gradedqualityNot gradedmaintenanceAn 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.
- AlicenseNot gradedqualityNot gradedmaintenanceAn enterprise-grade MCP server that enables AI agents to execute SAP RFC functions and read business data securely through the Model Context Protocol.
- FlicenseNot gradedqualityBmaintenanceMCP 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
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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