Skip to main content
Glama
azamali992

Agentic Ops Platform MCP Server

by azamali992
README.md
# Agentic Ops Platform

A multi-agent back-office automation platform where **every state-changing
action is gated behind human approval by construction** — a supervisor agent
routes each request to an order, inventory, or billing specialist; role-based
middleware decides which tools that caller is even allowed to see; and any tool
that writes to the database pauses for a human before it runs. The same tools
are exposed a second way, through a real **MCP server**, so Claude Desktop or
any other MCP client can drive the same operations under the same approval
policy. Every turn is traced to MLflow, and a CI gate blocks the build if the
agents' routing, approval behavior, or safety invariants regress.

It is built on the current 2026 stack: LangChain 1.4 `create_agent` with the
agent middleware API, LangGraph checkpointing for interrupt/resume, and the
`mcp` 2.x Python SDK (`MCPServer` — what FastMCP was renamed to).

```
                    ┌─────────────────┐
   user message ──▶ │   SUPERVISOR    │  routes only; never acts
                    └────────┬────────┘
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
        ┌──────────┐  ┌────────────┐  ┌──────────┐
        │  ORDER   │  │ INVENTORY  │  │ BILLING  │   each: own tools,
        │  agent   │  │   agent    │  │  agent   │   own prompt per role,
        └────┬─────┘  └─────┬──────┘  └────┬─────┘   own checkpoint thread
             └──────────────┼──────────────┘
                            ▼
              ┌──────────────────────────────┐
              │  RISK REGISTRY (one source)  │
              │  read_only │ low │ high risk │
              └───────┬──────────────┬───────┘
       enforced for   │              │   enforced for
       LangChain      ▼              ▼   MCP clients
   HumanInTheLoopMiddleware   pending-action tickets
                            │
                            ▼
                   ┌─────────────────┐
                   │  OpsRepository  │  SQLite, all invariants
                   └─────────────────┘
```

## What makes it more than a demo

**One risk declaration, enforced on two surfaces.** `safety/risk.py` declares
each tool's risk tier once. The LangChain agents turn that into a
`HumanInTheLoopMiddleware` interrupt config; the MCP server turns the same
registry into a pending-approval ticket queue. A client cannot use MCP as a
back door around the agents' approval gate — `tests/test_mcp_server.py` asserts
exactly that. Registering a new tool without declaring its risk raises rather
than defaulting to "safe", so the system fails closed.

**Role gating is middleware, not prompt text.** `safety/middleware.py`
generalizes the authenticated/unauthenticated pattern into role → tools and
role → prompt maps. A guest never sees `create_order`; staff never see
`adjust_inventory`. The model is not asked nicely to refuse — the tool is
absent from the request, so refusing is the only thing it *can* do.

**Approval is proven, not asserted.** The tests check repository state, not
response wording: after a gated request, stock and orders must be *byte-for-byte
unchanged* until a human approves. Rejection must leave state untouched. A
credit note cannot be approved twice.

**The CI gate treats safety as absolute.** `scripts/gate_ci.py` gives routing
and gating accuracy zero tolerance, task success a 5% band — and treats
`safety_violations` as a hard invariant that fails the build no matter what the
baseline says. Proven by deliberately misclassifying `create_order` as
read-only: gating accuracy fell 1.0 → 0.92 and the gate failed the build, then
passed again once reverted.

## Layout

```
src/ops_platform/
  domain/        SQLite repository + models — every business invariant lives here
  tools/         core.py (framework-free) + langchain_tools.py (the LangChain wrapper)
  safety/        risk.py (the registry), middleware.py (HITL + role gating), auth.py
  agents/        order / inventory / billing specialists + supervisor router
  mcp_server/    MCPServer exposing the same tools, resources, and a prompt
  observability/ MLflow tracing for every turn
  platform.py    the orchestration entry point
eval/            13-step scripted session, metrics, locked baseline
scripts/         gate_ci.py
demo/            runnable walkthrough + Claude Desktop config example
tests/           88 tests
```

## Running it

No API key, no services, nothing to install beyond the requirements:

```bash
pip install -r requirements-dev.txt
python -m pytest tests/ -v                      # 88 tests
PYTHONPATH=src python demo/run_platform_demo.py  # full walkthrough
python scripts/gate_ci.py                        # eval + safety gate
```

Run the MCP server for Claude Desktop or any MCP client:

```bash
PYTHONPATH=src python -m ops_platform.mcp_server.server
```

See `demo/claude_desktop_config_example.json` for the client config.

## Configuration — where the API key goes

**By default there is no API key.** `LLM_PROVIDER=stub` (the default) runs a
deterministic, rule-based chat model that implements LangChain's tool-calling
interface, so the demo, all 88 tests, and CI run fully offline and reproducibly.

To run the agents on a real LLM, set these in `.env` (see `.env.example`):

| Variable | Needed when | Where to get it |
|---|---|---|
| `LLM_PROVIDER` | always (`stub` or `groq`) | — |
| `GROQ_API_KEY` | only when `LLM_PROVIDER=groq` | free at <https://console.groq.com> → API Keys |
| `LLM_MODEL` | optional | defaults to `llama-3.3-70b-versatile` |

Swapping providers changes nothing else: the agents, middleware, risk registry,
MCP server, and tests are all provider-agnostic.

## Honest limitations

- **The stub model does not understand language.** It matches keywords and
  reads the system prompt for a role-appropriate fallback. It is there to make
  control flow — routing, gating, approval, role visibility — deterministic and
  testable, not to fake comprehension. Real language understanding comes from
  the Groq path.
- **MCP approval uses ticket IDs, not native protocol elicitation.** The
  2026-07-28 MCP spec's Multi Round-Trip Requests could let a tool return
  `resultType: "input_required"` and have the client resume it directly. Client
  support for that is still new, so this uses a transport-agnostic pending-ticket
  queue instead — same policy, more portable.
- **MLflow uses the local file store**, which MLflow 3.x marks maintenance-mode
  (hence `MLFLOW_ALLOW_FILE_STORE`). Fine for a single machine; a real
  deployment would point `MLFLOW_TRACKING_URI` at Postgres or a hosted server.
- **The supervisor routes, then the platform dispatches** — specialists are not
  nested as subgraphs. That keeps each specialist's interrupt/resume cycle on its
  own checkpoint thread and independently testable, at the cost of not doing
  multi-hop agent-to-agent delegation.

## Eval baseline

```json
{
  "n_steps": 13,
  "routing_accuracy": 1.0,
  "gating_accuracy": 1.0,
  "task_success_rate": 1.0,
  "safety_violations": 0
}
```

The 13 steps walk one stateful session: read-only lookups, a guest denied an
order, staff placing one through approval, a follow-up status check against the
order ID the previous step created, a manager restock approved, another
rejected, a manager-only credit note, and an out-of-scope message that must be
routed nowhere.