red-castle
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@red-castleplease quarantine old-report.pdf for human review"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
AI agents may ask to delete things. Only a human decides.
Red Castle is a guarded deletion pipeline for AI agents. An agent never deletes anything itself: it asks the castle, the object is hidden in quarantine, and a human approves or restores it from the terminal. The castle runs silently in the background and only speaks up, with a desktop notification, when a human is needed.
AI Agent
│ "I want to delete X" (cannot delete it themselves)
▼
[The Gateway] receives deletion requests via API or MCP tools
▼
[The Judge] calculates its own risk score and compares it with the agent's
▼
[The Quarantine] the object is hidden but still exists
▼
[The Throne Room] a human approves, restores, or freezes the agent (in the terminal)
▼
[The Executioner] the only role in the world authorized to permanently delete
▼
[The Chronicle] tamper-evident log of every decision
Around the wall:
[The Moat] guardrails
[The Watchtower] monitoring and desktop notifications
[The Traps] canariesPure Python standard library. No dependencies, except boto3 if you use the S3 backend.
Quick start
git clone https://github.com/babayaga370/red-castle.gitcd red-castlepip install .Or skip the install and use python -m redcastle in place of redcastle below.
redcastle demoThe demo walks through the whole flow in a throw-away castle: quarantine, approval, restore, a sprung trap, a frozen agent and the Chronicle. Nothing on your machine is touched.
To set up a real castle, go to the folder that should hold it and run:
redcastle initinit greets you with the castle and a short guide. It creates redcastle.toml (the
configuration), realm/ (the folder agents can see and ask to delete from) and
.redcastle/ (database, keys, quarantine and log; keep it private).
redcastle agent add clauderedcastle startredcastle notify testThat's it: the castle runs silently in the background, and agents reach it at
http://127.0.0.1:8750/api/v1. When something needs you, a notification pops up, and you
answer it with:
redcastle throne reviewRunning redcastle without a command shows the welcome screen and the most useful commands.
Every command accepts --root <folder> (or REDCASTLE_HOME) when the castle lives elsewhere.
Related MCP server: genesys-mcp
Running silently
Command | |
| Start the castle detached from the terminal, with no window ( |
| Is it running? How many cases await you? Any alerts in the last day? Is the Chronicle intact? |
| Ask the castle to close its gates; forced after 15 seconds if it does not answer. |
| Run in the foreground of this terminal (Ctrl+C to stop). |
The castle does not start at login by itself. To make it, schedule
pythonw -m redcastle --root <folder> serve --quiet with Windows Task Scheduler (or launchd / systemd).
Notifications
The Watchtower stays quiet unless a human is needed:
An object is waiting in quarantine. The notification names the agent and the object, shows the Judge's and the agent's risk scores, and says if the agent underestimated the risk.
Alerts (urgent): an agent reached for a canary, an agent was frozen, a canary vanished behind the castle's back, the Chronicle or the quarantine was tampered with, or the Executioner refused to act.
Reminders when cases have waited longer than
remind_interval_minutes.
Everything that happens within batch_seconds is gathered into one notification, so a busy
agent cannot flood your screen. Rejected requests (bad keys, missing objects …) stay quiet.
Windows uses native toast notifications through Windows PowerShell (they show
"Windows PowerShell" as the sender), macOS uses osascript and Linux notify-send. Without
any of these, notifications go to the log; REDCASTLE_NOTIFY=off forces that, for CI for
example.
[notify]
enabled = true
on_quarantine = true # notify when an object awaits your judgement
min_severity = "warning" # "warning" or "critical"
batch_seconds = 3
remind_interval_minutes = 60 # 0 = neverNothing shows up? Check the system's notification settings and Focus Assist / Do Not Disturb.
The Throne Room, in your terminal
redcastle throne reviewThe review goes through the quarantine one case at a time. For each case it shows the object, the agent, its reason, both risk scores and the Judge's reasoning:
[1/2] Case rc-6c9b6e01fd01 [quarantined] 4m ago
object: config/prod.env (31 bytes)
agent: claude
reason: Looks unused
risk: agent 5, Judge 70 (high) <-- agent UNDERESTIMATED the risk
+10 permanent deletion is never risk-free
+35 path matches sensitive pattern '*.env'
+20 path matches sensitive pattern '*prod*'
+5 terse justification
[a]pprove (delete forever) [r]estore [s]kip [f]reeze claude [q]uit >Approving asks you to type yes. Every decision is recorded under your name (your
username, or --by).
Command | |
| Cases waiting in quarantine ( |
| One case, the Judge's reasoning and its Chronicle entries |
| Approve; the Executioner deletes the object permanently |
| Decline and put the object back |
| Freeze an agent (and optionally restore all it asked for) |
| Let it submit requests again |
| Watch every decision as it happens |
| Verify the hash chain |
| Recent alerts / check canaries, quarantine and Chronicle now |
| Plant a canary |
| Carry out approved cases that are still pending |
The Throne Room exists only in the terminal. There is no web interface and no admin API, so nobody can approve a deletion over the network.
For agents: HTTP API
Every call needs Authorization: Bearer <agent token>.
Method | Path | |
GET |
| Visible objects (quarantined ones are hidden) |
POST |
|
|
GET |
| Your own requests ( |
GET |
| Status of one of your requests |
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"object_key":"logs/app-2025.log","reason":"Last year log, already archived","risk_score":10}' http://127.0.0.1:8750/api/v1/deletion-requestsFor agents: MCP
redcastle mcp is an MCP server over stdio with the tools list_objects,
request_deletion, get_deletion_request and list_my_deletion_requests. No tool can
delete, approve or restore anything.
Point it at the running castle with --gateway. The agent's process then holds nothing but
its own token and cannot reach the database or the keys. For Claude Code, in .mcp.json:
{
"mcpServers": {
"red-castle": {
"command": "redcastle",
"args": ["mcp", "--gateway", "http://127.0.0.1:8750"],
"env": { "REDCASTLE_AGENT_TOKEN": "rc_agent_..." }
}
}
}Without --gateway the castle is opened in-process (needs --root and access to .redcastle/).
How it works
Life of a request
received ──► rejected (Moat: frozen agent, invalid key, protected object, rate limit …)
──► trapped (a canary: the agent is frozen and only ever sees "rejected")
──► failed (storage error; nothing was deleted)
──► quarantined ──► approved ──► executed
└─► restoredThe roles in the code
Role | File | What it does |
Gateway | Authenticates the agent and walks the request through the castle. Has no delete function. | |
Judge | Its own 0–100 risk score with explained factors; flags agents that under- or overestimate. | |
Quarantine | Moves the object out of sight and records its checksum. | |
Throne Room | Approve (seals the decision with an HMAC), restore, freeze or unfreeze agents. | |
Executioner | Holds the only storage handle with | |
Chronicle | Hash-chained, HMAC-signed log; SQLite triggers refuse UPDATE and DELETE. | |
Moat | Frozen agents, key validation, protected patterns, required reason and score, rate limits. | |
Watchtower | Alerts, automatic freezes, patrols, reminders, desktop notifications, webhook. | |
Traps | Canaries. Asking to delete one freezes the agent on the spot. |
Storage backends: local folder and S3. Background mode: daemon.py.
The Judge
The Judge starts at 10 points and adds or subtracts for:
sensitive paths (
*.env,*secret*,*backup*,*.db,*prod*…) and low-value ones (*.tmp,*.log,__pycache__…)size (≥ 1 / 10 / 100 MB), and whether the object changed in the last hour or day
the agent's behaviour: bursts of requests, earlier underestimates
a terse reason
If the Judge's score is at least discrepancy_threshold (25) above the agent's own, the
request is flagged as underestimated. The notification says so, and three of those
within an hour freeze the agent. Patterns and weights live in redcastle.toml.
Around the wall
Moat: see
[moat]inredcastle.toml. The castle's own files (.redcastle/,redcastle.toml) are never reachable, even if the realm is the castle's own folder.Watchtower: freezes an agent on a sprung trap, after 5 Moat rejections within 10 minutes, or after 3 underestimates within an hour. The running castle patrols every minute: the Chronicle must verify, every canary must be in place and unchanged, and every quarantined object must match its checksum. Each new problem raises an urgent notification. An optional
webhook_urlreceives alerts as JSON.Traps:
redcastle traps plant "backups/db-dump-prod.sql"writes a decoy with fake credentials, or turns an existing object into a canary.
S3
[storage]
backend = "s3"
bucket = "my-bucket"
prefix = "data/" # what agents can see
quarantine_prefix = ".redcastle-quarantine/"
profile = "redcastle" # the castle's AWS profile
executioner_profile = "redcastle-executioner"Quarantine copies the object to quarantine_prefix and removes the original. With
bucket versioning enabled, that removal is only a delete marker, so nothing is lost.
Suggested IAM roles:
Agent: no S3 permissions at all, or read-only with an explicit
Denyons3:DeleteObject*.Castle (
profile):s3:ListBucket,s3:GetObject,s3:PutObject,s3:DeleteObject, and an explicitDenyons3:DeleteObjectVersion.Executioner (
executioner_profile):s3:DeleteObject,s3:DeleteObjectVersion,s3:ListBucketVersions,s3:GetObject.
The only irreversible call in the whole system is then made with the Executioner's keys.
Security model and limits
The Chronicle is tamper-evident, not tamper-proof. Someone holding both the database and
secrets.jsoncan rewrite history. Publish the head fromredcastle chronicle verify(sequence number and hash) somewhere else now and then to detect truncation too.Run the castle as a different OS user than the agent. An agent with file access to
.redcastle/could runthrone approveitself. With the local backend, an agent with a shell can also delete files inrealm/directly; the Traps and the patrols detect and report that, but cannot prevent it. Real isolation needs separate users or containers, or S3 with IAM.Agent tokens are stored only as SHA-256 hashes. Text written by agents (such as object keys) is XML-escaped before it reaches a notification.
The HTTP server listens on
127.0.0.1by default and has no TLS. Put a TLS-terminating reverse proxy in front of it before exposing it to a network.
Development
python -m unittest discover -s tests -t .CI runs the tests on Windows and Linux with Python 3.11–3.14. macOS is supported
(notifications use osascript) but not tested in CI.
License
Available Tools
4 toolsget_deletion_requestGet deletion requestARead-only
Show the current status of one of your deletion requests.
| Name | Required | Description | Default |
|---|---|---|---|
| request_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety and scope are covered. The description adds only the ownership scoping ('your'), and says nothing about behavior when a request is still pending, completed, or the ID is unknown.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with no filler, and the verb plus resource are front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read tool with no output schema, the agent still doesn't know what 'status' contains (states, fields, terminal vs in-progress values) or how to interpret a pending request, which is exactly what the tool exists to reveal.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
One required parameter with 0% schema description coverage, so the description must carry the burden. 'One of your deletion requests' hints at the ID's origin and ownership, but no format, source tool, or error behavior is given for request_id.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb ('Show') and resource ('the current status of one of your deletion requests'), which an agent can distinguish from the sibling tools that create or list requests. It never names those siblings explicitly, so differentiation relies on inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'one of your deletion requests' implies a request_id obtained from request_deletion or list_my_deletion_requests, but the description gives no explicit when-to-use, no deadline/retention context, and no mention of alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_my_deletion_requestsList my deletion requestsCRead-only
List your deletion requests, newest first.
| Name | Required | Description | Default |
|---|---|---|---|
| status | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds one genuinely new behavioral fact — results are ordered newest first — but says nothing about pagination, result caps, or what happens when the status filter matches nothing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One short front-loaded sentence containing no filler, and the sort order is stated up front. It is efficient but arguably undersized for a tool with an optional filter parameter.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and no per-parameter documentation, the description is the only place the status filter could be explained, and it is silent. Annotations cover the read-only nature, but an agent still lacks enough to know how filtering and pagination behave.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single 'status' parameter has 0% schema description coverage; only the enum values are documented. The description never mentions that filtering by status is possible, so the agent must reverse-engineer the parameter's purpose from its name and enum alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List') and resource ('your deletion requests') plus an ordering guarantee ('newest first'), and the possessive 'your' implicitly scopes results to the caller. It does not explicitly contrast with siblings like list_objects or get_deletion_request, so an agent must infer the boundary from the plural/singular split.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no statement of when to use this tool versus list_objects (for live objects) or get_deletion_request (for a single request), and no prerequisites or exclusions. Usage is only implied by the name and the word 'deletion'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_objectsList objectsBRead-only
List the objects you can see. Objects waiting in quarantine are hidden.
| Name | Required | Description | Default |
|---|---|---|---|
| prefix | No | Only keys starting with this, e.g. 'reports/'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnlyHint=true and openWorldHint=false. The description adds useful behavioral context about visibility scoping—quarantined objects are hidden—but says nothing about pagination, ordering, permissions, or how hidden objects relate to the sibling deletion-request tools.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the core action and followed by the key visibility constraint. There is no wasted wording.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only listing tool this is close to adequate, but with no output schema the description does not indicate the return shape, pagination behavior, or whether an empty result is possible—details an agent might need to use the results correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the single prefix parameter is documented in the schema with an example. The description adds no parameter semantics beyond that, which is acceptable given the schema already carries the burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb+resource: 'List the objects you can see.' It is clearly a listing tool and is distinct from the deletion-request siblings, though it does not explicitly contrast with them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit guidance on when to use this tool versus alternatives, nor any prerequisites or exclusions. The implicit usage is obvious for a list tool, but the description provides no real routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
request_deletionRequest deletionA
Ask Red Castle to delete one object. Nothing is deleted right away: the object is hidden in quarantine until a human approves (permanent deletion) or restores it. Use get_deletion_request to follow up.
| Name | Required | Description | Default |
|---|---|---|---|
| reason | Yes | Why it should be deleted. The human reads this. | |
| object_key | Yes | Key of the object, exactly as list_objects shows it. | |
| risk_score | Yes | Your honest estimate of how risky deleting it is (0 = harmless, 100 = disastrous). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the annotations: the deletion is not immediate, the object is hidden in quarantine, and a human either approves permanent deletion or restores it. This resolves the potentially confusing combination of destructiveHint=false with a deletion tool and tells the agent the real-world lifecycle effect.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each earning its place: what it does, the quarantine/approval consequence, and how to follow up. The key behavioral fact (nothing is deleted right away) is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation tool with annotated safety hints and no output schema, the description covers the lifecycle and the follow-up tool. It stops short of saying what identifier or object reference the agent should use with get_deletion_request, a minor remaining gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so reason, object_key, and risk_score are already well documented in the schema, including that the human reads the reason and the 0-100 risk scale. The description only implies 'one object' and adds no further parameter guidance, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Ask Red Castle to delete one object') with explicit scope of exactly one object. It also differentiates the follow-up path by naming get_deletion_request, so an agent can distinguish it from siblings without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clearly establishes the context of use (requesting deletion of an object) and points to get_deletion_request for follow-up. It does not state explicit exclusions such as when to use list_my_deletion_requests or when not to request deletion, so it falls short of a full when/when-not treatment.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
get_deletion_request - First observed
list_my_deletion_requests - First observed
list_objects - First observed
request_deletion
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: requesting deletion, listing visible objects, checking one request, and listing all of the user's requests. The single-request vs list-request distinction is explicitly clarified by the descriptions.
All tool names use snake_case with a predictable verb-object pattern: request_deletion, list_objects, get_deletion_request, list_my_deletion_requests. The slight inclusion of 'my' is readable and consistent with the possessive scope.
Four tools is well-scoped for a deletion-request workflow. Each tool earns its place by covering requesting, browsing targets, checking status, and listing requests.
The core deletion-request lifecycle is covered: create a request, inspect status, list requests, and view visible objects. A cancellation or withdrawal operation for pending requests is a minor gap, but human approval/restore is intentionally outside the agent surface.
Maintenance
Related MCP Connectors
The system of record for AI agent authority: playbooks, routed policy questions, reusable rules.
Deterministic allow/require_approval/deny verdicts for agent actions, before they happen.
Security gateway for AI agents: policy, approval, and audited execution, no secrets shared.
Auto-approve small agent actions under your rules; approval cards for the rest.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to access registered resources based on a fixed subject identity, enforcing default-deny policies and auditing for controlled context sharing.Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to build and manage Genesys Cloud resources such as queues, skills, users, wrap-up codes, Architect flows, and outbound campaign cadences, while deliberately preventing deletes and leaving campaigns off until humans start them.MIT
- AlicenseAqualityCmaintenanceEnables MCP-capable AI clients to read a local AI ticket triage queue and human decision log through four read-only tools for checking queue status, listing pending review items, viewing recent decisions, and searching decisions. It does not allow approving, rejecting, or sending anything.4MIT
- AlicenseAqualityAmaintenanceEnables AI agents to inspect and manage video renders, templates, generation models, hosted assets, and ingest sources while using isolated private account profiles. It requires explicit approval for mutation operations and supports both MCP and CLI access.2330 npmAGPL 3.0