Skip to main content
Glama
richardtidwell

mcp-gatehouse

README.md
# mcp-gatehouse

[![CI](https://github.com/richardtidwell/mcp-gatehouse/actions/workflows/ci.yml/badge.svg)](https://github.com/richardtidwell/mcp-gatehouse/actions/workflows/ci.yml)

`mcp-gatehouse` is a production-shaped reference implementation for exposing
business systems through MCP without giving a model direct write authority.

The model-facing server can read synthetic CRM, accounting, and field-service
data. It can also create a bounded, immutable proposal. A separately
authenticated human reviews that proposal outside MCP, and a worker executes an
approved proposal with idempotency and an audit trail.

> **Status:** v0.1.0 reference release. This is an executable architecture
> example, not a drop-in security product. Do not connect it to real systems
> until you have adapted and reviewed the controls for your environment.

## What this demonstrates

- MCP `2026-07-28` over Streamable HTTP using the official Python SDK
- OAuth-protected MCP resources with audience and scope validation
- five narrow tools with strict structured inputs and outputs
- a separate operator authorization boundary for approval and rejection
- immutable action proposals with expiry, policy version, and source-version
  preconditions
- idempotent, asynchronous execution across synthetic business systems
- distinct read-only API and write-capable worker credentials
- organization isolation, redacted audit events, and Prometheus metrics
- explicit `STALE` and `UNKNOWN` outcomes instead of unsafe blind retries

It intentionally contains **no LLM**. The MCP client supplies the model;
Gatehouse remains a deterministic authorization and execution boundary.

### Official SDK v2 naming

This repository uses the official `mcp==2.1.1` package. In SDK v2, the high-level
server formerly shown as `FastMCP` is named `MCPServer`; Gatehouse therefore
imports `MCPServer` directly and does not depend on the separate `fastmcp`
package. This distinction matters when comparing older examples with the pinned
implementation here.

## The five tools

| Tool | Scope | Effect |
| --- | --- | --- |
| `customers.search` | `business:read` | Find synthetic customers using bounded filters. |
| `customers.snapshot` | `business:read` | Join one customer's CRM, A/R, and field-service state. |
| `operations.attention` | `business:read` | List items that need management attention. |
| `onboarding.propose` | `actions:propose` | Persist an immutable onboarding proposal; performs no upstream writes. |
| `actions.status` | `actions:propose` | Return the proposal and its step execution status. |

Approval and execution are deliberately absent from `tools/list`. The identity
that asks for a change must not be able to approve that change through the same
model-facing interface.

## Architecture

```text
MCP client -- OAuth token --> Gatehouse API -- read tools --> synthetic upstream
                                  |
                                  +-- proposed action --> PostgreSQL

Human operator -- separate token --> operator route --> approve / reject
                                                        |
                                                        v
                                                     worker
                                                        |
                                                        +--> idempotent writes
```

The Docker demo includes:

- `api` — MCP resource server and operator route on port `8000`
- `worker` — claims and executes approved actions
- `postgres` — durable proposals, mappings, and audit events
- `issuer` — demo-only OAuth issuer on port `9000`
- `upstream` — synthetic CRM, accounting, and field-service APIs on port `9001`

Read [Architecture](docs/ARCHITECTURE.md), [Threat model](docs/THREAT-MODEL.md),
and [Approval flow](docs/APPROVAL-FLOW.md) before adapting the design.

## Quick start

Prerequisites:

- Docker Engine with Docker Compose v2
- GNU Make
- Python 3.12 and [uv](https://docs.astral.sh/uv/) for the scripted walkthrough

From a fresh clone:

```bash
make demo
```

Compose builds the image, starts PostgreSQL, applies migrations, provisions the
synthetic organization, and starts all demo services. Continue with the scripted
walkthrough in [Demo](docs/DEMO.md).

Useful commands:

```bash
make demo-status  # show containers and health
make demo-logs    # follow service logs
make demo-down    # stop containers; retain the database volume
make demo-reset   # stop containers and delete demo state
```

The issuer and its static clients exist only to make the local demonstration
self-contained. They are not an identity-provider template.

For repeatability, the walkthrough script also automates the operator decision.
That proves a separate token audience, scope, route, and execution boundary; it
does not prove that a human was present. A production operator interface must
establish real human identity and intent before calling the decision route.

## Local development

Install [uv](https://docs.astral.sh/uv/), then run:

```bash
make bootstrap
make check
make docker-build
```

`make check` runs formatting verification, Ruff, strict mypy, and the unit and
contract suites with an 80% local coverage floor. CI adds PostgreSQL integration
contracts, raises the coverage floor to 85%, scans for secrets, and builds the
container image.

## Production adaptation checklist

Before connecting a real system:

1. Replace the demo issuer with your organization's trusted OAuth authorization
   server and verify its exact issuer, resource audience, algorithms, and scopes.
2. Replace synthetic connectors one at a time; use distinct least-privilege
   downstream credentials and never pass an MCP token downstream.
3. Put the MCP and operator surfaces behind separate ingress policies. Require
   strong human authentication for the operator surface.
4. Move secrets to a managed secret store and pin the image by digest.
5. Define action-specific approval policy, expiry, source-version checks, and
   reconciliation procedures with the system owners.
6. Send audit events to an externally controlled, retention-enforced sink.
7. Exercise backup, restore, key rotation, upstream outage, and ambiguous-result
   runbooks before enabling writes.

See [Operations](docs/OPERATIONS.md) and
[Support boundaries](docs/SUPPORT-BOUNDARIES.md) for the full handoff boundary.

## Non-goals

Version 0.1 is not:

- a generic connector or workflow framework
- a proxy for arbitrary HTTP, SQL, filesystem, or shell access
- an LLM agent, RAG system, or prompt-defense library
- a production identity provider
- a QuickBooks, Pipedrive, or RazorSync integration
- a guarantee that tool annotations or human-facing descriptions enforce policy
- a substitute for application-specific threat modeling and review

## License and security

Licensed under [Apache-2.0](LICENSE). Please report vulnerabilities according to
[SECURITY.md](SECURITY.md); do not open a public issue for a suspected security
problem.

Protocol references: [MCP `2026-07-28` specification](https://modelcontextprotocol.io/specification/2026-07-28),
[authorization](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization),
[tools](https://modelcontextprotocol.io/specification/2026-07-28/server/tools), and
[security best practices](https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices).