Skip to main content
Glama
mffnxman
by mffnxman

mcp-erp-bridge

A Model Context Protocol server that wraps a session-cookie-authenticated enterprise web app (an ERP, a work-order system, a ticketing portal) and exposes structured tools to any MCP-compatible client.

This is a reference implementation of a deployment pattern: turning an internal-only enterprise web app with no API into an MCP server so non-technical operators can drive it with Claude Code, Claude Desktop, or any other MCP client.

The pattern is deliberately generic. Endpoint paths, login form fields, the session cookie name, and every HTML selector live in one config object (ErpConfig) and can be overridden from a JSON file, so the same code adapts to any system with a cookie-based auth flow and HTML pages.


Why this exists

Most MCP examples wrap clean SaaS APIs (Slack, Linear, GitHub). The real deployment surface in the messy middle of the economy is internal enterprise tools with no public API and a session cookie for auth. This server is a worked example of how to wrap one.

The shape: a server, a handful of tools, a typed schema on every call, and an opinionated deterministic-core / probabilistic-edge split.


Related MCP server: mare-browser-mcp

Tool status

Tool

Status

get_work_order

Implemented

list_pending_work

Implemented

add_closeout_note

Implemented

find_field_schedule

Designed (schema + overtime walk-back logic ready; schedule-export parser not bundled)

detect_overtime

Designed (schema ready; awaits roster iteration on a real backend)

Three tools are fully wired against the HTTP client. The two scheduling tools have schemas and supporting logic in schemas.py and overtime.py, but the schedule-export parser is intentionally not bundled: every system serves schedules differently, and shipping a stub would be dishonest.


Features

  • Three production-ready tools plus two designed-but-unbundled

  • Session-cookie + CSRF auth with automatic refresh on 401

  • SQLite cookie cache so re-auth happens once per session window, not per call

  • Selector-driven parsing: every CSS selector, form field, and endpoint path is configuration, not code

  • Pydantic schemas on every tool input/output: Claude sees a typed contract, not a free-form dict

  • Stdio transport for Claude Code / Claude Desktop integration

  • Injectable HTTP transport so the whole client is testable offline

  • MIT licensed: fork it, point it at your own system


Install

From source:

git clone https://github.com/mffnxman/mcp-erp-bridge
cd mcp-erp-bridge
pip install -e ".[dev]"

Configure

Credentials come from the environment:

export ERP_USERNAME="your.login@example.com"
export ERP_PASSWORD="..."
export ERP_BASE_URL="https://erp.example.com"

Endpoints and selectors come from an optional JSON file. Copy examples/erp_config.example.json, edit it to match your system's markup, and point at it:

export ERP_CONFIG_FILE="/path/to/erp_config.json"

Any key you leave out keeps its default. Unknown keys raise at startup so typos don't silently fall back to placeholders.

{
  "endpoints": { "login": "/account/signin", "work_order": "/orders/{wo_number}" },
  "selectors": {
    "session_cookie": "ASPSESSIONID",
    "detail_fields": { "status": "span.order-state", "technician": "#assigned-to" },
    "list_rows": "table#orders tr.row"
  }
}

Use with Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (or the Windows equivalent at %APPDATA%\Claude\claude_desktop_config.json):

{
  "mcpServers": {
    "erp-bridge": {
      "command": "python",
      "args": ["-m", "mcp_erp_bridge"],
      "env": {
        "ERP_USERNAME": "your.login@example.com",
        "ERP_PASSWORD": "...",
        "ERP_BASE_URL": "https://erp.example.com",
        "ERP_CONFIG_FILE": "/path/to/erp_config.json"
      }
    }
  }
}

Use with Claude Code

claude mcp add erp-bridge python -m mcp_erp_bridge \
  -e ERP_USERNAME=your.login@example.com \
  -e ERP_PASSWORD=... \
  -e ERP_BASE_URL=https://erp.example.com \
  -e ERP_CONFIG_FILE=/path/to/erp_config.json

Tools

get_work_order(wo_number: str) -> WorkOrder

Fetches a single work order by number. Returns the full structured record: status, assigned technician, schedule, NTE, customer, location, parent work order, and the close-out fields.

get_work_order("12345678")
-> WorkOrder(
    number="12345678",
    status="In Progress",
    technician="Doe, Jane",
    scheduled="2026-05-08 14:00",
    nte=850.00,
    closeout_fields={...}
  )

list_pending_work(manager: str = None) -> List[WorkOrder]

Returns all work orders currently pending dispatch or in progress, optionally filtered to a single manager.

list_pending_work(manager="Doe, Jane")
-> [WorkOrder(...), WorkOrder(...), ...]

add_closeout_note(wo_number: str, fields: CloseoutFields) -> NoteResult

Adds a close-out note to a work order with all required fields templated from structured input. Validates every field before submission; raises rather than silently submitting an incomplete note.


Architecture

+-----------------+   stdio   +-----------------+
|   MCP Client    |<--------->| mcp-erp-bridge  |
| (Claude Code)   |           |  server         |
+-----------------+           +--------+--------+
                                       | tool calls
                              +--------v--------+
                              |   ErpClient     |
                              | (httpx + SQLite |
                              |  cookie cache)  |
                              +--------+--------+
                                       | HTTPS
                                       v
                              +-----------------+
                              | Enterprise ERP  |
                              | (session cookie |
                              |  + CSRF)        |
                              +-----------------+

The deliberate choice: all "right-answer" logic is deterministic Python. Claude is only on the probabilistic edge: drafting prose, summarizing, matching intent. Tool calls go straight to deterministic Python. This is the pattern that survives production.


Project structure

mcp-erp-bridge/
+-- README.md
+-- LICENSE
+-- pyproject.toml
+-- src/
|   +-- mcp_erp_bridge/
|       +-- __init__.py
|       +-- __main__.py
|       +-- server.py        # MCP server + tool registration
|       +-- erp_client.py    # HTTP client w/ cookie cache
|       +-- config.py        # endpoints, selectors, credentials
|       +-- schemas.py       # Pydantic models
|       +-- overtime.py      # overtime walk-back recovery logic
+-- examples/
|   +-- claude_desktop_config.json
|   +-- erp_config.example.json
|   +-- usage.md
+-- tests/
    +-- test_client.py       # login, cookie cache, 401 re-auth, parsing (mocked HTTP)
    +-- test_schemas.py      # input validation
    +-- test_overtime.py     # walk-back logic

Tests

python -m pytest -q

The client tests run against httpx.MockTransport; no network, no real system.


Background

This is a reference implementation, not a product. The shape (a session-cookie login, a CSRF token, HTML pages instead of an API, a handful of typed tools with a deterministic core and a probabilistic edge) is the shape of most internal enterprise tools, so anyone wrapping one can fork this and swap the selectors. Every URL, field name, cookie name, and selector is a configurable placeholder; none of them point at a real system.

License

MIT (c) 2026 Rafael Garcia

Available Tools

3 tools
add_closeout_noteB

Submit a close-out note with all required fields. Validates every field; raises rather than submitting incomplete.

ParametersJSON Schema
NameRequiredDescriptionDefault
fieldsYes
wo_numberYes

TDQS

B3/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It notes validation ('raises rather than submitting incomplete'), which is useful, but does not disclose whether this is a destructive write, what permissions are required, whether it can be called multiple times, or what the response looks like. For a mutation tool with zero annotation coverage, this is a significant transparency gap.

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?

Two sentences with zero waste, front-loading the action and the validation behavior. It could be slightly more informative without becoming verbose, but it is appropriately sized for the 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?

For a mutation tool with no annotations and no output schema, the description omits critical context: it does not explain prerequisites (e.g., work order must be in a closeable state), consequences of submission, or error conditions beyond validation failure. The nested schema compensates for parameter details but not for operational context.

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 schema has 0% description coverage at the top level, but the nested $defs.CloseoutFields is richly described with per-field descriptions and required lists, so the parameter semantics are largely self-documenting. The description itself adds no new parameter meaning, which is acceptable given the schema's internal documentation.

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 states a clear verb+resource: submitting a close-out note. It is distinguishable from its read-only siblings (get_work_order, list_pending_work) by being the sole write operation. However, it gives no indication of the domain context (field-service work orders) that would make the target entity unambiguous.

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?

There is no explicit guidance on when to use this tool versus alternatives, no prerequisites stated, and no mention of what happens after submission or when a work order is ready for close-out. The agent must infer usage 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.

get_work_orderA

Look up a single work order by its number. Returns full structured record: status, technician, schedule, NTE, customer, location, parent WO, and the close-out fields.

ParametersJSON Schema
NameRequiredDescriptionDefault
wo_numberYesThe numeric work order id, e.g. '12345678'.

TDQS

A3.5/5.0
Behavior3/5

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

With no annotations, the description carries the full behavioral burden. It does usefully enumerate the returned record fields (status, technician, schedule, NTE, customer, location, parent WO, close-out fields), which substitutes for a missing output schema, but it says nothing about permissions, whether it errors on unknown numbers, or read-only guarantees.

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?

Two sentences, front-loaded with purpose followed by the return contents. Efficient, with only mild redundancy in the field enumeration, which earns its place given there is no output schema.

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?

For a one-parameter read tool with no annotations and no output schema, the definition covers both what it does and what it returns, which is the key missing piece. It is adequately complete, though usage guidance relative to siblings remains absent.

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

Parameters3/5

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

Schema description coverage is 100%, so the single wo_number parameter, including its pattern and example, is fully documented in the schema. The description only restates 'by its number' and adds no meaning beyond it; baseline 3 applies.

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?

States a specific verb ('look up') and resource ('a single work order'), and the phrase 'a single' implicitly contrasts with the sibling list_pending_work. It does not name the sibling explicitly, so it stops short of full differentiation.

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?

Usage is implied: you call it when you already have a work order number. There is no explicit statement of when to prefer this over list_pending_work, and no prerequisites or exclusions are given.

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

list_pending_workB

List all pending-dispatch and in-progress work orders, optionally filtered to a single manager.

ParametersJSON Schema
NameRequiredDescriptionDefault
managerNoManager name in 'Last, First' format. Omit for org-wide view.
include_in_progressNoIf True, include both pending-dispatch and in-progress work.

TDQS

B3.3/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden. It discloses the returned set (pending-dispatch plus in-progress) but says nothing about ordering, pagination, result volume, or permissions — meaningful gaps for an unfiltered list endpoint.

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?

A single front-loaded sentence with no filler; the resource and its scope lead, and the filter qualifier follows. Every clause earns its place.

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?

Adequate for a simple two-param list tool with no output schema, but with no annotations and no output contract it leaves pagination, ordering, and result-size expectations unspecified.

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

Parameters3/5

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

Schema description coverage is 100%, so both parameters (manager format 'Last, First', include_in_progress default) are already documented. The description only restates the manager filter at a high level and adds no syntax or default-behavior detail beyond the schema, so the baseline 3 applies.

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?

States a specific verb (List) and resource (pending-dispatch and in-progress work orders) plus the optional scope. It is clearly a collection-read operation, distinguishable from get_work_order and add_closeout_note by implication, though it never explicitly contrasts itself with them.

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 phrase 'optionally filtered to a single manager' implies the usage context for the filter, but there is no explicit when-to-use guidance, no when-not-to-use, and no reference to the sibling tools (e.g. use get_work_order for a single order).

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

Tool Schema Changelog

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

  1. 3 tool updatesv0.1.0
    • First observedadd_closeout_note
    • First observedget_work_order
    • First observedlist_pending_work

TDQS

A3.5/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct action and resource: get_work_order retrieves a single record, list_pending_work lists multiple records, and add_closeout_note submits a note. There is no overlap in purpose, making selection unambiguous.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case: get_work_order, list_pending_work, add_closeout_note. The verbs (get, list, add) clearly indicate the action, and the nouns are descriptive.

Tool Count4/5

Three tools is on the low end for an ERP bridge, but they form a coherent subset for a specific work order closeout workflow. It is slightly under what might be expected for broader ERP work order management, but not unreasonable.

Completeness3/5

The set covers read (single and list) and one write (closeout note), but lacks create, update, delete, and broader listing operations for work orders. These gaps would cause failures for agents needing to manage the full work order lifecycle.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    A Model Context Protocol (MCP) integration that provides Claude Desktop with autonomous browser automation capabilities. This agent enables Claude to interact with web content, manipulate DOM elements, execute JavaScript, and perform API requests.
    13
    3 npm
    41
    TypeScript
    Mozilla Public 2.0
  • A
    license
    A
    quality
    C
    maintenance
    A lean, LLM-first browser automation MCP server that gives Claude (or any MCP client) a real Chromium browser to navigate, interact with, and debug web apps.
    14
    9 npm
    6
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server that drives the user's real signed-in Chrome on macOS via AppleScript (Apple Events). 32 tools covering tab/window control, navigation, DOM extraction, form interaction, and arbitrary JS execution. Same engine as the familiar Claude Code skill in the repository.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that gives Claude a real, persistent Chrome browser with logged-in sessions, enabling automation of sites that block headless browsers. It supports 50+ tools, cross-session knowledge, and recipe replay for complex workflows.
    -