Skip to main content
Glama
sudsho

mcp-server-enterprise-tools

by sudsho
README.md
# mcp-server-enterprise-tools

An opinionated tool gateway for LLM-driven backends, designed around the
MCP tool model but shipped with its own JSON transports. It focuses on the
governance layer: per-tool RBAC from a declarative YAML policy, a
structured audit row for every invocation, PII redaction on arguments
before persistence, and a typed error envelope so clients can branch on
codes instead of string-matching messages.

The transport is a small HTTPS API (`POST /mcp/call`) plus a Prometheus
`/metrics` endpoint. The stdio path that would let a full MCP host
discover and call these tools is out of scope in this repo.

## The problem

Enterprise backends that want to expose internal APIs to LLM-driven
callers hit three gaps that are not addressed at the protocol layer:

- caller identity is not first-class and needs to be resolved out-of-band
- tool arguments and results can carry PII (SSNs, PANs, account numbers)
  and those cannot land in cleartext in logs or audit tables
- error handling needs to be machine-readable so internal callers can
  branch on codes without string-matching messages

This repo is an opinionated take on filling those gaps while keeping the
tool implementations small and readable.

## What is exposed

Three bank tools (four operations):

| Tool | Verb | Purpose |
|------|------|---------|
| `customer_360.lookup` | read | profile, primary account summary, risk flags |
| `statement_search.query` | read | full-text with date and amount filters |
| `dispute_resolution.open` | mutating | open a dispute against a transaction |
| `dispute_resolution.status` | read | dispute state by id |

Each has a JSON input schema. Adding a fourth is a single file under
`src/mcp_server/tools/` plus a line in `app.build_registry`.

## Architecture

```
              +--------------------+          +------------------+
              |  Internal caller   |          |  Internal agent  |
              |  (any HTTP client) |          |  (http)          |
              +---------+----------+          +---------+--------+
                        |                               |
                        |               X-Bank-Identity |
                        v                               v
                    +---+-------------------------------+---+
                    |          Tool gateway (this repo)      |
                    |  transport -> registry.call -> handler |
                    |                    |                   |
                    |            rbac.check + audit.write    |
                    +----+-----------------+----------+------+
                         |                 |          |
                         v                 v          v
                +------------------+  +---------+  +------------------+
                | internal APIs    |  | Postgres|  | Prometheus       |
                | (customer,       |  | audit   |  | metrics scraped  |
                | statements,      |  | log     |  | at /metrics      |
                | disputes)        |  +---------+  +------------------+
                +------------------+
```

Longer version with request flow: [docs/architecture.md](docs/architecture.md).

## Quick start (runs offline, no keys)

No cloud creds, no api keys, no database to stand up. The audit log defaults
to a local sqlite file, and the three bank tools default to in-repo synthetic
upstreams (`MCP_UPSTREAM_MODE=fake`). Point `AUDIT_DB_URL` at postgres and set
`MCP_UPSTREAM_MODE=http` when you have the real services.

```bash
pip install -r requirements.txt
python -m examples.smoke     # in-process server + client, exits 0
PYTHONPATH=src pytest -q     # 31 passed
```

`python -m examples.smoke` starts the FastAPI gateway in-process, drives it
through the full governance path with the http client, and reads the audit
table back. Real output (trimmed):

```
1. client lists tools
  - customer_360.lookup: Look up a customer 360 view: profile, primary account...
  - statement_search.query: Search a customer's statements. Supports date range...
  - dispute_resolution.open: Open a dispute against a specific transaction...
  - dispute_resolution.status: Get the current status of an open dispute by id.

2. customer_360.lookup  (allowed for svc-analyst-01)
{
  "result": {
    "profile": {
      "name": "Jane A. Doe",
      "email": "[REDACTED]<email>",
      "ssn": "[REDACTED]<ssn>",
      "notes": "primary card [REDACTED]<pan>on file; backup acct [REDACTED]<acct>"
    },
    ...
  }
}

5. dispute_resolution.open  (DENIED for svc-analyst-01 by RBAC)
{
  "error": {
    "code": "forbidden",
    "message": "identity 'svc-analyst-01' is not permitted to call 'dispute_resolution.open'",
    ...
  }
}

6. audit log rows (read back from sqlite; args redacted)
  [ok   ] identity=svc-analyst-01         tool=customer_360.lookup        error=-
          args_redacted={"customer_id": "C-[REDACTED]<acct>"}
  [ok   ] identity=svc-dispute-agent-07   tool=dispute_resolution.open    error=-
          args_redacted={... "notes": "caller gave card [REDACTED]<pan>and ssn [REDACTED]<ssn>"}
  [deny ] identity=svc-analyst-01         tool=dispute_resolution.open    error=forbidden

  4 audit rows written, no raw PAN/SSN present.

SMOKE OK
```

And `pytest`:

```
31 passed in 1.21s
```

## Quick start (production shape)

Local (http + postgres via compose):

```bash
docker compose up --build
# server on :8080, prometheus on :9090, postgres on :5432
curl -s -X POST http://localhost:8080/mcp/call \
  -H "X-Bank-Identity: svc-analyst-01" \
  -H "Content-Type: application/json" \
  -d '{"tool":"customer_360.lookup","args":{"customer_id":"C-01234567"}}'
```

Bare local run against the http transport:

```bash
pip install -e '.[dev]'
export AUDIT_DB_URL=sqlite:///./audit.db   # or a postgres url
make run-http                              # serves on :8080
```

## RBAC model

Deny by default. Policies in `configs/rbac.yaml` map identities to allowed
tool names, with a trailing `.*` wildcard for grouping. Every call, allowed
or denied, writes an audit row. Full spec in
[docs/rbac_model.md](docs/rbac_model.md).

## Audit log design

One row per tool call. It defaults to a local sqlite file so the server runs
offline, and swaps to Postgres for a real deploy by setting `AUDIT_DB_URL`
(the store is plain SQLAlchemy, so nothing else changes). Columns:
`request_id, identity, tool, outcome, args_redacted, error_code, created_at`.

Arguments are passed through `Redactor` before persistence: SSNs, PANs
(Luhn-checked), email addresses, and 8-17 digit account-shaped numbers
are replaced with marked tokens. The two read tools also apply the same
redactor to their upstream response as defense-in-depth. Details in
[src/mcp_server/redaction.py](src/mcp_server/redaction.py) and the tests.

## Structured errors

Every failure comes back as an `ErrorEnvelope`:

```json
{
  "error": {
    "code": "forbidden",
    "message": "identity 'svc-analyst-01' is not permitted to call 'dispute_resolution.open'",
    "request_id": "0d3f4a...",
    "tool": "dispute_resolution.open",
    "retryable": false,
    "details": {}
  }
}
```

Codes: `unauthenticated`, `forbidden`, `not_found`, `invalid_argument`,
`upstream_unavailable`, `rate_limited`, `internal`. Clients can branch on
`code` without string-matching messages.

## Deploy

Terraform under `terraform/` is a sketch of an internal ALB, ECS Fargate
service, and RDS Postgres for the audit log. It has not been applied
against a live account; treat it as a reference layout.

Prod checklist for a real rollout:

1. Populate a real `configs/rbac.yaml`. Prefer many narrow identities over
   a few broad ones.
2. Wire the `X-Bank-Identity` header to be set by a trusted upstream (mTLS
   terminator, OIDC proxy). The server does not authenticate; it only
   reads the resolved identity.
3. Ship the audit table to your SIEM nightly. The table is the source of
   truth.
4. Rotate the break-glass credential regularly and alert on every use.

## Repo layout

```
src/
  mcp_server/       server, registry, rbac, audit, redaction, tools/
  mcp_client/       http client used for testing and examples
configs/            default.yaml, rbac.yaml, prometheus.yml
terraform/          aws vpc + ecs + rds reference layout
tests/              rbac, audit, redaction, tools, registry
examples/           request/response transcripts
docs/               architecture, rbac model
```

## License

MIT. See [LICENSE](LICENSE).