Skip to main content
Glama
exoticaitsolutions

Acme Enterprise Capability Hub

README.md
# Acme Enterprise Capability Hub

A runnable teaching project for **Model Context Protocol (MCP)** in a
realistic, polyglot enterprise environment.

The one idea it exists to demonstrate:

> **Claude/LLM sees BUSINESS CAPABILITIES, not underlying technologies.**
> Claude asks for `check_device_eligibility`. MCP decides that answering it
> means calling a Java service, PostgreSQL, a Python library, a Node.js
> service, and a Java pricing library, in that order, and merging the
> results. Claude never sees any of that.

Read [architecture.md](architecture.md) for the full design (with diagrams).
This README is the "how do I actually run it" doc.

---

## What's actually running under the hood

| Business capability (what Claude sees) | Technology (what Claude never sees) |
|---|---|
| `create_customer`, `get_customer` | Java HTTP service (plain JDK, no Spring/Maven needed) |
| `calculate_customer_discount` | Java library, invoked as a subprocess |
| `check_customer_credit_status` | Python library, in-process |
| `find_overdue_invoices`, `get_customer_balance`, `get_subscription_status`, `schedule_service_appointment` | PostgreSQL |
| `get_customer_preferences` | MongoDB |
| `get_available_inventory` | Node.js/Express service |
| `get_legacy_customer_status` | SOAP/XML service |
| `get_customer_network_status` | gRPC service |
| `get_customer_product_bundle` | GraphQL service |
| `search_customer_contracts` | Elasticsearch |
| `search_product_knowledge` | Qdrant vector database |
| `retrieve_customer_document` | MinIO (S3-compatible) |
| `create_support_case` | Mock CRM (REST) |
| `generate_billing_report` | Internal CLI utility (controlled subprocess) |
| `publish_customer_event` | Mock event bus (REST) |
| `check_device_eligibility` | **5 systems**: Java + PostgreSQL + Python + Node.js + Java library |
| `run_compliance_check` | **4 systems**: Java + PostgreSQL + SOAP + Python |

A note on pragmatic tech choices: the Java services are plain-JDK
(`com.sun.net.httpserver`), not Spring Boot, so the whole demo runs with
just `javac`/`java` — no Maven, no internet access to a Maven repo required.
They're genuinely Java, genuinely compiled, genuinely a separate process
communicating over HTTP; they're just not wearing a framework. See
`architecture.md` for the reasoning.

---

## Prerequisites

- Python 3.11+
- Node.js 18+
- A JDK (8+) — `javac` and `java` on your `PATH`
- Docker Desktop (for PostgreSQL / MongoDB / Elasticsearch / Qdrant / MinIO)

## 1. Install dependencies

```bash
python -m venv .venv
make install
```

(`make install` runs `pip install -r requirements.txt` into `.venv` and
`npm install` in both Node services. No `make`? See the equivalent raw
commands in each Makefile target — they're two or three lines each.)

## 2. Start the data stores

```bash
make db-up      # docker compose up -d: Postgres, MongoDB, Elasticsearch, Qdrant, MinIO
make seed       # seeds MongoDB, Elasticsearch, Qdrant, MinIO with demo data
                 # (Postgres seeds itself on first boot via database/postgres/init.sql)
```

> **Port note:** Postgres is published on host port **55432**, not 5432 —
> many dev machines already have a local PostgreSQL install on 5432 (or even
> 5433 if you have two versions installed). Check `docker compose ps` and
> `.env.example` if you ever need to confirm the mapping.

## 3. Build the Java pieces and start the mock services

```bash
make build-java     # javac --release 8, plain JDK, no Maven
make run-services   # starts java-customer-service, node-inventory-service,
                     # graphql-service, grpc-network-service, soap-legacy-service,
                     # mock-crm, event-bus-service - logs to logs/*.log
```

Verify everything is up:

```bash
curl http://localhost:8081/health   # java-customer-service
curl http://localhost:3000/health   # node-inventory-service
curl http://localhost:4000/health   # graphql-service
curl http://localhost:8083/health   # soap-legacy-service
curl http://localhost:8090/health   # mock-crm
curl http://localhost:8095/health   # event-bus-service
```

## 4. Register the MCP server with Claude Code

This project ships a project-level [.mcp.json](.mcp.json) already pointing
at `.venv/Scripts/python.exe mcp_server/server.py` with every backend URL
wired up. Just open this folder in Claude Code — it will pick up
`.mcp.json` automatically. To use Claude Desktop instead, copy
[claude_desktop_config.json.example](claude_desktop_config.json.example)
into your Claude Desktop config and replace the example paths with your
actual clone location (Claude Desktop requires absolute paths).

## 5. Try it

Ask Claude Code things like:

- *"What MCP tools does the acme-enterprise-capabilities server expose?"*
- *"Is customer C123 eligible for an iPhone 17 Pro?"*
- *"Show me overdue invoices for customer C123, then explain what happened internally."*

See [Demo scenarios](#demo-scenarios) below for a full script, and
[Claude Code experience](#claude-code-experience) for how to ask Claude to
explain the plumbing without it ever appearing in the tool descriptions
themselves.

---

## Demo data

Three seeded customers to experiment with:

| Customer | Plan | Status | Notable |
|---|---|---|---|
| `C123` (Alice Nakamura) | Premium 5G Unlimited | ACTIVE | 2 overdue invoices, 18mo tenure — good eligibility/discount demo |
| `C456` (Ben Torres) | Basic 4G | ACTIVE | Clean billing history — good "no issues" demo |
| `C789` (Carla Diaz) | Premium 5G Unlimited | SUSPENDED | Fraud flag in legacy system — good compliance-failure demo |

Devices: `iphone-17-pro` (in stock), `iphone-17` (in stock),
`galaxy-s26-ultra` (out of stock), `pixel-10-pro` (low stock).

---

## Demo scenarios

Each of these is a real prompt you can give Claude Code once the server and
mock services are running. "Expected" describes what you should see in the
server logs (`make logs`) as well as Claude's answer.

1. **"Create a customer named John Smith with email john@example.com."**
   `create_customer` → Java REST customer service.

2. **"Show me all overdue invoices for customer C123."**
   `find_overdue_invoices` → PostgreSQL. Returns 2 overdue invoices.

3. **"Check the credit status of C123."**
   `check_customer_credit_status` → PostgreSQL (inputs) + Python credit-risk library. Returns FAIR (tenure is good, but 2 overdue invoices drag it down).

4. **"Is customer C123 eligible for an iPhone 17 Pro?"**
   `check_device_eligibility` → Java → PostgreSQL → Python → Node.js → Java pricing library. Returns `eligible: true`, `discount: 112.5`.

5. **"Find contracts mentioning international roaming."**
   `search_customer_contracts` → Elasticsearch full-text search.

6. **"What are the eligibility rules for the Premium 5G plan?"**
   `search_product_knowledge` → Qdrant semantic search over the knowledge base.

7. **"Get the latest contract document for C123."**
   `retrieve_customer_document` → MinIO/S3.

8. **"Create a support ticket for C123 because they can't make international calls."**
   `create_support_case` → mock CRM.

9. **"Check whether C123 passes compliance."**
   `run_compliance_check` → Java → PostgreSQL → SOAP → Python. Returns `compliant: false`, flag `MULTIPLE_OVERDUE_INVOICES` (try `C789` for a fraud-flag failure instead).

Bonus — the "bad vs good" lesson, in one prompt: *"Would you ever expose a
tool called `execute_sql` to me? Why does this server use
`find_overdue_invoices` instead?"* Claude can answer this directly from the
tool descriptions it's been given — none of which mention SQL.

---

## Claude Code experience

Useful things to ask once the server is registered:

- **"What MCP tools are available?"** — lists all 21 business-capability
  tools, with zero mention of Java/Postgres/gRPC/etc.
- **"What tool would you use to check device eligibility?"** — Claude
  should answer `check_device_eligibility` from the tool description alone.
- **"Call the device eligibility capability for C123 and the iPhone 17
  Pro."** — invokes the real 5-adapter orchestration.
- **"Explain what happened internally."** — this is the interesting one.
  Claude can only answer from the JSON result it got back (customer name,
  credit status, discount, etc.) — it has no visibility into which
  adapters ran, in what order, or what the raw Postgres/HTTP calls looked
  like, because that's deliberately never exposed through MCP. *You* get
  that story by reading the server logs (`make logs`) or the walkthroughs
  in `architecture.md` §7 and §9 — which is the whole point: the
  capability boundary is real, not just a naming convention.

---

## Running the tests

```bash
make test
# or directly:
.venv/Scripts/python.exe -m pytest tests/ -v
```

The suite has two layers:

- **Unit tests** (`tests/adapters/`, `tests/capabilities/`, `tests/test_models.py`,
  `tests/test_server.py`) — HTTP-mocked (via `respx`) or subprocess-mocked,
  run anywhere, no Docker or mock services required. Cover adapter success
  paths, `NOT_FOUND`/timeout/backend-unavailable failure paths, Pydantic
  validation rejection, and — for the two flagship capabilities — that
  *every* expected adapter actually gets called (and that the Java pricing
  subprocess is skipped when the customer isn't eligible).
- **Integration tests** (`tests/integration/`) — hit the real docker-compose
  stack and real mock services. Each self-skips via a `requires_backend`
  marker if its backend isn't reachable, so `pytest` stays green even before
  you've run `make db-up`. Run `make db-up && make seed && make build-java
  && make run-services` first to exercise all of them, including a
  real-subprocess, real-HTTP, real-SQL run of `check_device_eligibility`.

## Viewing logs

```bash
make logs                     # tails logs/*.log for all mock services
docker compose logs -f        # data store logs
```

The MCP server itself logs to stderr with the `[MCP TOOL]` / `[CAPABILITY]`
/ `[ADAPTER]` / `[CALL]` / `[ADAPTER RESULT]` structure described in
`architecture.md` §9 — when Claude Code launches it, that output shows up
in Claude Code's MCP server log viewer; when you run `make run-mcp`
directly, it prints straight to your terminal.

## Inspecting tools directly (no Claude needed)

```bash
make tools
```

Prints every registered tool name and the first line of its description —
useful for confirming the "Claude sees capabilities, not technology" claim
for yourself.

## Stopping everything

```bash
make stop-services   # kills the 7 mock service processes
make db-down          # docker compose down (add -v to also drop volumes/data)
```

---

## Project structure

```
acme-enterprise-mcp/
├── mcp_server/
│   ├── server.py              MCP tool definitions (the only file Claude talks to)
│   ├── models.py               Pydantic request models + structured error types
│   ├── config.py                Environment-driven settings (every backend URL)
│   ├── observability.py         Correlation-ID logging ([MCP TOOL]/[CAPABILITY]/[ADAPTER]/...)
│   ├── adapters/                One class per technology (15 adapters)
│   └── capabilities/             Business logic + orchestration (6 files)
├── services/                    Mock backend services (Java, Node, Python)
├── libraries/                   Java pricing "library" + Python credit-risk library
├── database/                    Postgres schema+seed, Mongo seed
├── documents/                   Sample contracts (for Elasticsearch) + knowledge base (for Qdrant)
├── scripts/                     Seed scripts + the internal billing-report CLI utility
├── tests/                       Unit + integration tests
├── docker-compose.yml            Postgres / MongoDB / Elasticsearch / Qdrant / MinIO
├── capability-registry.yaml      Documentation: capability -> adapters -> technology map
├── architecture.md               Full design doc with diagrams
├── .mcp.json                     Claude Code MCP server config (this project)
└── claude_desktop_config.json.example
```

## Environment variables

See [.env.example](.env.example) for the full list (every backend URL,
credential, and timeout). Copy it to `.env` if you want to override
anything — `mcp_server/config.py` loads it via `python-dotenv`. Docker
Compose reads plain values baked into `docker-compose.yml` directly (this is
a local demo; there are no real secrets anywhere in this repo).