Skip to main content
Glama
Ibrahim-ziaa

Gatehouse

by Ibrahim-ziaa

Gatehouse

A read only MCP gateway you can put in front of a real business system, with an audit console. Python, official MCP SDK, FastAPI, SQLite, React, TypeScript, Tailwind. 129 tests, 84 of them security tests. Runs with one command, or in Docker.

Give Claude (or any AI assistant) safe, read only access to your CRM, with a key per assistant, least privilege scopes, rate limits, and an audit log that shows everything the AI did and everything it was refused.

Audit log with a blocked call explained in plain language

What it does

You have a system full of customer data. You want an AI assistant to answer questions from it. You do not want the AI to change anything, see everything, or do anything you cannot review later. Gatehouse sits between the two:

  • The AI cannot write. There is no tool that writes, the database is opened read only three separate ways, and a test suite attacks it to prove the point.

  • Every assistant gets its own key. A key decides which tools it may call, which rows it may see (for example EMEA accounts only), which fields stay masked (contact emails and phone numbers, deal amounts), and how many calls per minute it may make.

  • Everything is logged. Time, key, tool, arguments, decision, the rule that made the decision, rows returned, fields masked, latency. Blocked calls explain themselves: Blocked: key 'support-bot' is not scoped for list_deals.

  • A console a non engineer can read. Overview, audit log, keys and permissions, the tool catalogue with a Try it runner, a live attack suite, and copy and paste setup for Claude Desktop and Claude Code.

  • Real MCP, not a mock. Built on the official mcp Python SDK (2.x), Streamable HTTP with bearer authentication, plus stdio. The demo's audit log is produced by real MCP client sessions against the running server.

Overview

Keys and permissions

Calls over time, why calls were blocked, top tools and keys, latency. All computed from the audit table.

One row per key, and a tool by key permission matrix. Create and revoke work.

Try it

Security checks

The same call run as three keys: unmasked, masked, and refused.

31 attacks (writes, SQL injection, scope escapes, floods, stolen keys), run live, with evidence.

The demo data is a fictional company (Halden Supply Co.) with 120 accounts, 344 contacts, 215 deals, 137 tickets and 417 activities, generated by a seeded script. No real companies, people or email addresses.

Related MCP server: EspoCRM Assistant

Run it

Requirements: Python 3.11 or newer, Node.js 20 or newer.

make demo

That creates .venv, installs the Python and web dependencies, builds the console and starts everything on one port. Open http://localhost:8104.

On startup the demo creates seven access keys, then replays a scripted week of AI traffic (799 calls, 75 of them blocked) through the real /mcp endpoint using the SDK's own client. It takes about five seconds, and the console shows a banner while it runs. Reset demo in the sidebar does the same again, so the state is reproducible.

Other commands:

make test      # 129 tests: security suite, MCP end to end, API, demo replay
make attack    # run the live attack suite in the terminal
make serve     # start without reinstalling or rebuilding
make shots     # recapture the screenshots (demo must be running)

Docker (a multi stage Dockerfile and a docker-compose.yml are included):

docker compose up --build

Connect Claude

The console's Connect screen builds these for you with a working demo key. For Claude Code:

claude mcp add --transport http gatehouse http://localhost:8104/mcp \
  --header "Authorization: Bearer <access key>"

For Claude Desktop, the Connect screen gives a claude_desktop_config.json block (using the mcp-remote bridge to send the key), and a stdio variant that needs no Node.js.

Configuration

Variable

Default

Meaning

GATEHOUSE_PORT

8104

Port for the MCP endpoint, API and console

GATEHOUSE_HOST

127.0.0.1

Bind address

GATEHOUSE_DEMO

1

1 wipes gateway state on start and replays the demo week. 0 keeps keys and the audit log between restarts and creates no demo keys

GATEHOUSE_ADMIN_PASSWORD

unset

When set, the console and /api require HTTP Basic auth with this password. /mcp always requires a bearer access key

GATEHOUSE_PUBLIC_URL

http://localhost:8104

The URL printed on the Connect screen

GATEHOUSE_ALLOWED_HOSTS

unset

Extra Host values the MCP endpoint accepts (DNS rebinding protection is on)

GATEHOUSE_DATA_DIR

./data

Where crm.sqlite and control.sqlite live

GATEHOUSE_KEY

unset

The access key for the stdio transport

No API keys, no paid services, no network access needed.

Engineering notes

The path of one tool call

Every call, from either transport or from the console's Try it runner, goes through Gateway.call (gatehouse/gateway.py). Checks run in this order and the first failure wins:

  1. Authentication. Bearer token, looked up by SHA-256 hash. Unknown, malformed and revoked keys get HTTP 401 before any MCP message is read. Revocation also cuts off sessions that are already open.

  2. Rate limit. Sliding 60 second window per key. Refused calls still count, so probing is throttled too.

  3. Tool exists. Eight published tools, nothing else. There is no run_sql.

  4. Tool scope. The key's allow list. A key is also only shown the tools it may call.

  5. Argument validation. Strict Pydantic models: closed schema (unknown fields rejected), enums, id patterns, length and range caps, no type coercion.

  6. Masked filters. A key that cannot see deal amounts cannot filter by min_amount either, because repeated filtered calls would binary search the hidden value. Contact search never looks at email or phone for the same reason.

  7. Row scope. Asking for another region, or for the id of a row in another region, is refused. The AI is told "not found", the administrator is told the truth.

  8. Query. A constant SQL template on the read only connection, with the key's region scope inside the SQL.

  9. Masking. Sensitive fields are redacted at any depth of the result, and the AI is told which fields are masked so it does not guess.

Whatever happens, one row is written to audit_log. Tokens are never logged, oversized arguments are truncated, and results are never stored, so the log cannot leak what a key could not see.

Read only, in depth

  • crm.sqlite is opened with mode=ro, then PRAGMA query_only = ON, then an SQLite authorizer that vetoes every operation except reads (this also blocks ATTACH and PRAGMA changes). The tests remove layers and show each one holds alone.

  • Every SQL statement is a module level constant in gatehouse/tools.py. Arguments only ever travel as bound parameters. The one place SQL varies (GROUP BY column in pipeline_summary) picks between three constants using an already validated enum.

  • Keys and the audit log live in a different file (control.sqlite). The tool connection has never opened it.

  • The CRM file is chmod 444 after seeding, and a test hashes it before and after a hostile session.

The security suite

tests/test_security.py is written to be read top to bottom as a threat model: writes, SQL injection, tool scope, row scope, field masking, rate limits, authentication, the audit log. 84 cases. tests/test_mcp_end_to_end.py repeats the important ones over the real wire: it starts the app with uvicorn on a TCP port and connects with the SDK client over Streamable HTTP (and launches the stdio server as a subprocess). gatehouse/attacks.py is the 31 check suite behind the console's Security checks screen and make attack; one test sabotages masking to prove the suite reports failures rather than hiding them.

Honest numbers

The console has no hard coded figures. The Overview is a set of queries over audit_log (gatehouse/audit.py); percentiles are nearest rank over the latencies of served calls. The demo audit log is written by the gateway itself while gatehouse/demo.py drives real MCP sessions: several keys behaving differently, a support bot that keeps asking for deals it is not scoped for, an outside integrator that probes for SQL injection and later floods the endpoint, a revoked key that keeps knocking. The only thing injected is the clock, so a week of traffic can be produced in seconds. Latency is measured inside the gateway (policy checks plus query, not network), which is why it is well under a millisecond against local SQLite.

What this is not

The backing system is SQLite so the demo runs anywhere. Pointing the same gateway at Postgres or a SaaS API means replacing the query layer in tools.py and keeping a read only credential; the policy engine, keys, audit log and console do not change. The admin console has optional Basic auth, not SSO. The rate limiter is in memory, so it is per process.

Layout

gatehouse/
  tools.py        tool catalogue, strict input models, constant SQL templates, masking
  gateway.py      the decision pipeline and audit writer
  keys.py         access keys (hashed), scopes, revocation
  db.py           read only CRM connection, control database
  mcp_server.py   MCP server: Streamable HTTP with bearer auth, and stdio
  api.py, app.py  admin API, static console, one process on one port
  audit.py        audit queries and overview statistics
  demo.py         demo keys and the scripted week replayed over real MCP sessions
  attacks.py      the live attack suite
  crm_seed.py     deterministic fictional CRM
web/              React 19, TypeScript, Vite, Tailwind CSS v4
tests/            security suite, MCP end to end, API, demo replay

MIT licensed.

Related MCP Connectors

  • Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.

  • The HubSpot MCP Server acts as a bridge that enables AI assistants and Large Language Models to securely interact with HubSpot CRM data through natural conversation, without requiring users to understand complex API structures. It provides read-only access to standard CRM objects (contacts, companies, deals, tickets, products, invoices, and more) and their associations, secured via OAuth 2.0, allowing AI agents to perform tasks like summarizing deals, fetching company updates, and looking up record changes.

  • Read-only AI access to HAVN properties, leads, tasks, files, media, and analytics.

  • Read-only AI access to Osboon business card analytics, viewers, links, connections and contacts.

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    Provides AI assistants with read-only access to Secureframe's compliance data, enabling querying of security controls, tests, users, vendors, and more across frameworks like SOC 2 and ISO 27001.
    8
    MIT
  • F
    license
    B
    quality
    D
    maintenance
    Provides read-only EspoCRM access for AI assistants, enabling record retrieval and preparation of signed change sets without direct modification or deletion.
    7
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to interact with a CRM covering companies, people, leads, deals, and more, with role checks, scoped agent keys, approval gates, and a shared audit trail.
    AGPL 3.0
  • A
    license
    A
    quality
    C
    maintenance
    A read-only MCP server that enables AI assistants to query Freshsales CRM data (leads, contacts, deals, accounts) via search, saved views, and record lookup.
    5
    MIT