enterprisemcperp

Enterprise Data Access for AI Agents via MCP

VP asked for full API access to ERP. The ERP has no API. MCP bridge pattern: read-only access, audit logging, approval gates. Architecture for IBM i, SAP, and legacy systems.

9 min

A post on r/sysadmin with 660 upvotes described a familiar scenario: a VP asked for "full API access to our ERP for the AI integration." The ERP runs on IBM i (formerly AS/400) and communicates via JDBC. It has no REST API. It has no GraphQL endpoint. It barely has a web interface. The gap between what executives expect from AI integration and what enterprise systems actually support is enormous. MCP bridges that gap with a pattern that works without rewriting the backend.

The Enterprise System Reality

Most enterprise systems in production today were not built for AI agent access. The stack looks like this:

  • ERP: SAP (BAPI/RFC), Oracle E-Business (PL/SQL), IBM i (JDBC/ODBC)
  • CRM: Salesforce (REST API exists), legacy systems (SOAP or direct DB)
  • Financial: mainframe batch processing, FTP file drops, EDI
  • Manufacturing: OPC-UA, proprietary protocols, PLC interfaces

None of these were designed for an AI agent to query in real time. The executive vision of "just let the AI read our ERP" ignores decades of access control, data governance, and system architecture that exist for good reasons.

The MCP Bridge Pattern

Model Context Protocol provides a standardized way for AI agents to interact with external systems through tool definitions. The MCP bridge pattern wraps enterprise system access in a controlled interface: the agent calls MCP tools, the MCP server translates those calls into the native system protocol, and returns structured results.

Text
[AI Agent] --> [MCP Server] --> [Enterprise Bridge] --> [ERP/CRM/DB]
    |               |                  |                    |
  Calls         Validates          Translates           Executes
  tools         permissions        to JDBC/SOAP         query
    |               |                  |                    |
  Gets          Logs audit         Applies filters      Returns
  JSON          trail              and limits           raw data

The critical architectural decision: the MCP server is the security boundary. It controls what the agent can access, logs every request, and enforces rate limits. The agent never gets direct database access.

Read-Only Access Is Non-Negotiable

The first rule of connecting AI agents to enterprise systems: read-only access. Period. An AI agent that can modify ERP data is a production incident waiting to happen. The agent might misinterpret a query and update 10,000 inventory records. It might hallucinate a valid-looking order number and modify real orders.

The MCP server enforces this by only exposing read operations as tools. There is no "update_inventory" tool. There is no "create_order" tool. If write operations are needed, they go through an approval gate (described below), not through direct agent access.

Python
# MCP Server for IBM i ERP access
# Read-only tools only

import jaydebeapi  # JDBC bridge for Python

class ErpMcpServer:
    def __init__(self, jdbc_url, jdbc_driver, credentials):
        self.conn = jaydebeapi.connect(
            jdbc_driver,
            jdbc_url,
            credentials,
        )
        self.cursor = self.conn.cursor()

    def get_inventory_level(self, product_code: str) -> dict:
        """Read current inventory for a product."""
        self.cursor.execute(
            "SELECT PRDCD, PRDNM, QTYOH, QTYAV, WHSCD "
            "FROM INVMST WHERE PRDCD = ?",
            (product_code,)
        )
        row = self.cursor.fetchone()
        if not row:
            return {"error": "Product not found"}
        return {
            "product_code": row[0].strip(),
            "product_name": row[1].strip(),
            "quantity_on_hand": int(row[2]),
            "quantity_available": int(row[3]),
            "warehouse": row[4].strip(),
        }

    def get_open_orders(self, customer_code: str) -> list:
        """Read open orders for a customer."""
        self.cursor.execute(
            "SELECT ORDNO, ORDDT, ORDST, ORDTL "
            "FROM ORDHDR WHERE CUSTCD = ? AND ORDST IN ('O', 'P') "
            "ORDER BY ORDDT DESC",
            (customer_code,)
        )
        rows = self.cursor.fetchall()
        return [
            {
                "order_number": row[0].strip(),
                "order_date": str(row[1]),
                "status": "Open" if row[2] == "O" else "Processing",
                "total": float(row[3]),
            }
            for row in rows
        ]

Audit Logging

Every query an AI agent makes against enterprise data must be logged. This is not optional. When the CFO asks "who accessed the financial data last Tuesday," the answer cannot be "an AI agent, we are not sure which query."

Python
import json
import datetime

class AuditLogger:
    def __init__(self, log_file="agent_audit.jsonl"):
        self.log_file = log_file

    def log(self, tool_name, params, result, user_id, agent_id):
        entry = {
            "timestamp": datetime.datetime.utcnow().isoformat(),
            "tool": tool_name,
            "params": params,
            "result_summary": self._summarize(result),
            "user_id": user_id,
            "agent_id": agent_id,
            "rows_returned": len(result) if isinstance(result, list) else 1,
        }
        with open(self.log_file, "a") as f:
            f.write(json.dumps(entry) + "\n")

    def _summarize(self, result):
        """Log metadata, not full data, for sensitive systems."""
        if isinstance(result, list):
            return f"{len(result)} records returned"
        if isinstance(result, dict) and "error" in result:
            return result["error"]
        return "single record returned"

Approval Gates for Write Operations

When the business requires AI-initiated changes (updating a shipping address, adjusting an order quantity), the approval gate pattern puts a human in the loop. The agent proposes a change, the system queues it, a human approves or rejects it, and only then does the change execute.

Python
class ApprovalGate:
    def __init__(self, notification_channel):
        self.pending = {}
        self.channel = notification_channel

    def propose_change(self, change_type, params, reason, agent_id):
        """Queue a proposed change for human approval."""
        change_id = generate_id()
        self.pending[change_id] = {
            "type": change_type,
            "params": params,
            "reason": reason,
            "agent_id": agent_id,
            "proposed_at": datetime.datetime.utcnow().isoformat(),
            "status": "pending",
        }
        # Notify human reviewer
        self.channel.send(
            f"Agent proposed: {change_type}\n"
            f"Params: {json.dumps(params)}\n"
            f"Reason: {reason}\n"
            f"Approve: /approve {change_id}\n"
            f"Reject: /reject {change_id}"
        )
        return change_id

    def approve(self, change_id, approver_id):
        """Execute an approved change."""
        change = self.pending.get(change_id)
        if not change or change["status"] != "pending":
            return {"error": "Invalid or already processed"}
        change["status"] = "approved"
        change["approved_by"] = approver_id
        change["approved_at"] = datetime.datetime.utcnow().isoformat()
        # Execute the actual change
        return self._execute(change)

Input Validation and Query Limits

AI agents generate queries based on natural language, which means they can construct unexpected inputs. The MCP server must validate every parameter:

  • Parameter type checking: product codes must match expected patterns, dates must be valid, numeric ranges must be bounded.
  • Row limits: every query has a maximum result count. An agent should never be able to dump an entire customer table.
  • Rate limiting: cap queries per minute per agent to prevent runaway loops from overwhelming the ERP.
  • Data masking: sensitive fields (SSN, credit card, salary) are masked or excluded from results.

The Architecture Applies Beyond ERP

This pattern works for any enterprise system that was not built for AI access. The MCP bridge pattern has been used for:

  • Mainframe financial systems (CICS transactions via MCP)
  • Manufacturing execution systems (OPC-UA data via MCP)
  • Legacy CRM systems (SOAP/XML via MCP)
  • Data warehouses (JDBC/ODBC via MCP with query governance)

In each case, the pattern is identical: MCP server as the security boundary, read-only default, audit logging on every request, approval gates for write operations, and strict input validation.

What to Tell the VP

When the executive asks for "full API access for the AI," the answer is: "We can give the AI read access to specific data through a controlled interface. Every query is logged. Write operations require human approval. The ERP itself is never directly exposed."

This is not a compromise. It is the only responsible way to connect AI agents to systems that run the business. The 660-upvote thread on r/sysadmin exists because sysadmins understand what happens when untested code gets write access to production ERP. AI agents are untested code that generates its own queries at runtime. The guardrails are not optional.