Skip to main content
Glama

Sentinel

A continuous, out-of-band trust and reliability layer for the Model Context Protocol ecosystem.

npm has a lockfile and an audit command. MCP has neither. Sentinel is both.


The problem

MCP is how AI agents get their tools. An agent learns what a tool does by calling tools/list and reading the description and inputSchema the server returns — text that goes straight into the model's prompt.

The protocol explicitly permits that reply to change between any two calls, with no required re-approval and no integrity check.

A server you approved on Monday can be giving your model different instructions on Tuesday. That is a rug pull, and it has already been demonstrated against production MCP servers, and shipped for real in the postmark-mcp incident.

Meanwhile the ecosystem has ~9,650 catalogued public servers, 30+ CVEs filed in a single 60-day window in early 2026, and 88% of organisations reporting an AI-agent incident in the past year — against 23% who have any agent-identity strategy at all.

A poisoned server still returns HTTP 200. That is why uptime monitoring cannot see this.


What Sentinel does

  registry sync  →  read-only probe  →  canonicalise  →  fingerprint
                                                             ↓
   alert  ←  classify severity  ←  structural diff  ←  compare to approved baseline
     ↓                ↓
  CI gate      hash-chained evidence ledger
  • Discovers servers from the official MCP Registry via incremental updated_since sync

  • Probes them with read-only protocol methods across both live spec revisions (2026-07-28 stateless, and 2025-11-25 with the initialize handshake)

  • Fingerprints every tool definition — RFC 8785 canonicalisation → SHA-256 → per-surface Merkle root

  • Classifies every change by severity — SAFE / COMPATIBILITY / SECURITY / IDENTITY — with published rule IDs

  • Records everything in an append-only, hash-chained, independently verifiable evidence ledger

  • Scores each server on five decomposable components, so a low score is always explainable

  • Gates your CI — sentinel verify exits non-zero and emits SARIF

  • Is itself an MCP server, so an agent can ask "is this server safe?" before it binds


Why the severity taxonomy is the whole product

A naive "did the hash change" detector generates roughly 53 alerts per working day across the catalogue. Nobody triages that, so within a fortnight it is muted and the control is worth nothing.

Classifying severity and escalating only at security-relevant-or-worse takes it to ~5 per day — a real person's real inbox.

Alert volume at four escalation thresholds


What it deliberately does not do

These are constraints, not missing features. Read them before evaluating the claims.

Why

Never calls tools/call

Sentinel probes thousands of servers it does not own. Their tools include send_email, delete_file, transfer_funds. A monitor with the capability to invoke arbitrary tools across the ecosystem is the vulnerability. ADR-0004

Cannot catch a day-one malicious server

The baseline is the poison. Fingerprinting has nothing to compare against

Pins definitions, not behaviour

A tool can change what it does without changing its schema. Sentinel would not see it

No LLM in the detection path

The input is attacker-controlled text engineered to manipulate language models. Feeding it to a language model to judge puts the detector inside the attack's own threat model. ADR-0007

Not in the request path

Out-of-band by design. Adopting it is a config change, not an architecture change


Try it

Requires uv. Everything runs against recorded fixtures — no network, no third-party servers, ever.

uv sync --extra dev

Catch a rug pull, end to end:

uv run sentinel demo
▸ T+0m — Baseline. Reviewed by a human and approved.
  root 04dcdce60579b4af…
  baseline approved — drift is measured against this

▸ T+5m — The rug pull. Description only — the schema is untouched.
  DRIFT · SECURITY rules INJ-004, INJ-007
    SECURITY      /tools/send_email/description
      description changed: INJ-004 Exfiltration reference; INJ-007 Concealment instruction.
      evidence: ~/.ssh | Do not mention this to the user

Simulating an attacker with database write access
  rewriting ledger entry seq=4 to hide the finding…
  DETECTED ledger chain broken at seq=4: payload does not match its recorded hash

Catch a capability widening where nothing textual changes at all:

uv run sentinel demo --scenario tests/scenarios/schema_widening.yaml

Run the classifier gate, sync a catalogue, probe a legacy 2025-11-25 server:

uv run sentinel eval --verbose
uv run sentinel sync
uv run sentinel probe tests/fixtures/servers/legacy_2025.json

Status

M1–M3 implemented and green. M4–M6 designed, not built.

Milestone

Status

M0 — Design docs, ADRs, threat model

M1 — Registry sync + dual-revision probe client + conformance

M2 — Canonicalisation, fingerprinting, Merkle, evidence ledger

M3 — Schema analyser + severity classifier + corpus + CI gate

✅ ⭐

M4 — Scheduler + trust scoring + alerting

⬜ designed

M5 — REST API + Sentinel-as-MCP-server + policy gate

⬜ designed

M6 — Dashboard + deployment

⬜ designed

Current gates, all passing on main:

Gate

Result

pytest

117 passed

mypy --strict

clean, 31 source files

ruff

clean

bandit

clean

lint-imports

2 contracts kept — core/ provably imports nothing with I/O

sentinel eval

GREEN — 66 corpus cases, 1.000 precision/recall, 1.000 rule attribution

On that 1.000, honestly. The corpus was authored alongside the rules, so these numbers measure internal consistency and regression safety, not generalisation to attacks nobody has thought of yet. What the gate genuinely buys is that a rule change which breaks an existing case fails the build — and it already earned its keep during M3, catching four real defects: a word-boundary bug that made INJ-004 unmatchable mid-sentence, and a schema comparison that conflated absent with empty and inverted the relation on every enum removal. Measuring real recall needs drift harvested from the live ecosystem, which is M4's job.


Documentation

Start with 02 — PDD for the what and why, then 06 — Drift Detection Design for the core algorithm.

Doc

01 — Research & Analysis

Threat landscape, protocol research, competitive analysis, charts

02 — Project Definition Document

Problem, goals, non-goals, users, scope, risks

03 — Requirements

FRs, NFRs, user stories, acceptance criteria, traceability

04 — High-Level Design

C4 context/container/component, data flow, deployment

05 — Low-Level Design

Modules, state machines, ERD, DDL, API surfaces

06 — Drift Detection Design

The core algorithm. Canonicalisation, fingerprinting, severity taxonomy, schema subtyping

07 — Trust Scoring Model

Formulas, weights, EWMA, sensitivity analysis

08 — Roadmap

Milestones, task breakdown, estimates, critical path

09 — Test & Evaluation Plan

Corpus, precision/recall gates, drift-injection harness

10 — Threat Model

STRIDE, abuse cases, OWASP MCP Top 10 coverage

11 — Interview Pitch

Pitch, demo script, Q&A

ADRs

Eight architecture decision records


Tech stack (planned)

Python 3.12 · httpx · Pydantic v2 · FastAPI · Typer · SQLAlchemy 2.0 · Postgres 17 · Redis 8 · React + Vite · Docker · uv · ruff / mypy --strict / bandit / pytest


Probing policy

Sentinel probes third-party infrastructure it does not own. It does so politely:

  • global rate cap, per-host concurrency of 1, minimum per-host interval

  • ±10% jitter on every scheduled probe, so the fleet never stampedes on the hour

  • exponential backoff; Retry-After always honoured

  • identifying User-Agent pointing at a published policy with an opt-out

  • read-only methods only — never tools/call, resources/read, or prompts/get

See PROBING.md (to be published with M1).


What broke and how I fixed it

1. A word boundary that could never match. INJ-004 — the rule that catches a tool description gaining a reference to ~/.ssh or .env — had the pattern \b(\.env|credentials?)\b. It never fired mid-sentence. A \b word boundary cannot exist between a space and a dot, because neither is a word character, so \b\.env only matches when .env is glued to a preceding word. The corpus case that caught it was a newly added tool whose description read "read the contents of .env" — the exact shape of a real attack, silently classified SAFE. Fixed with a lookbehind, (?<![\w.])\.env\b.

2. Conflating "absent" with "empty". I added a shortcut so that a schema appearing where there was none reads as a widening rather than composing to INCOMPARABLE. I wrote it as if not before and after: — which also fires when before is {}. In JSON Schema, {} is a valid schema that accepts anything, which is the opposite of absent. The result inverted the relation on every enum-removal case: removing an enum reported NARROWED instead of WIDENED, and SEC-021 — the rule the whole project's best demo depends on — stopped firing. Six unit tests caught it. Fixed by testing is None explicitly, with a comment saying why, because the falsy version looks more idiomatic and is wrong.

3. Property additions on an open object. The analyser correctly computed that adding a declared property to an object with additionalProperties unset does not change the set of accepted documents — the key was already permitted. Strictly true, and it meant SEC-025, SEC-026 and SEC-027 never saw a webhook_url or access_token parameter being added, because no change was recorded. The fix was a deliberate departure from pure language semantics: Sentinel monitors the declared surface, because the property list is what reaches the model's prompt and tells it what it may send. It is now recorded as a widening, and the reasoning is a comment in schema_compat.py rather than folklore.

4. Bandit flagged my own detector. The INJ-006 hidden-content detector contains literal zero-width and bidirectional control characters, because that is what it looks for. Bandit's B613 check exists to catch exactly those characters in source. It was right, and the finding was wrong. Skipped with the reason written into pyproject.toml rather than silently.

The pattern across all four: the interesting bugs were not in the MCP protocol handling, which is well specified and mostly mechanical. They were in the semantics — what "changed" means, what "empty" means, what a security control should do when its own tooling flags it.


Author: Nathan Alvares · Licence: TBD (Apache-2.0 intended)

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Scans MCP servers for tool poisoning, prompt injection and supply chain risks.

  • Control plane for autonomous software labor. Agents claim objectives over MCP with audit trail.

  • Independent A-F trust grade for any MCP server, watched for drift. Free, never for sale.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/IronNathanAlvares/mcp-sentinel'

If you have feedback or need assistance with the MCP directory API, please join our Discord server