Waymark
OfficialSends notifications when agent actions require approval, with Approve/Reject buttons directly in Slack.
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., "@WaymarkSet policy: require approval for writes to /etc"
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.
https://github.com/user-attachments/assets/ed36654a-29c4-46ee-b331-5b9afde2b932
waymark
Human oversight as a property of the system, not a property of the agent.
Waymark is MCP middleware that sits between an AI agent and the filesystem. Every write_file and bash call is evaluated against your policy before it touches anything — blocked, held for approval, or logged and allowed. Nothing the agent does is permanent until you say so.
Updated every 6 hours via GitHub Actions
Three Pillars
Control — enforce the rules you set, unconditionally
Policy lives in waymark.config.json, not in a system prompt. An agent cannot forget a rule, be jailbroken past it, or accumulate enough context to override it. Every tool call is evaluated in Node.js process space before the filesystem is touched.
blockedPaths → BLOCK (hard deny — secrets, system files)
requireApproval → PENDING (human must approve before execution)
allowedPaths → ALLOW (execute immediately, log it)
(default) → BLOCK (unknown paths are rejected)Observe — know what is happening, in real time and historically
The action ledger records every tool call to SQLite with decision, reason, output, and timestamps. The dashboard at http://localhost:47000 is always available. waymark watch gives you an ANSI terminal view. The Agent Monitor (/agents) shows every AI session on the machine — model, context%, tokens, turns, rate limits — as a live btop-style table.
Recover — every write is undoable
Before-snapshots are captured at write time. A single action can be rolled back. An entire session can be atomically undone in reverse order. Approving a write knowing you can roll it back is categorically different from approving without a net.
Related MCP server: safe-code-mcp
60-Second Quickstart
Option A — Zero global install
cd your-project
npx @way_marks/cli init --yes # create waymark.config.json + CLAUDE.md, register MCP
npx @way_marks/cli start # start server, open dashboard at localhost:<port>Restart Claude Desktop or reload your Claude Code session to pick up the new MCP server. Done.
Option B — Global install with daemon (recommended for v5)
npm install -g @way_marks/cli
waymark global-setup # register universal MCP entry once across all hosts
cd your-project
waymark init # project-specific init (idempotent — safe to re-run)
waymark daemon start # start one daemon at localhost:47000 for all projectswaymark daemon status # list registered projects + uptime
waymark status # check this project's status + pending count
waymark logs --pending # show pending actions waiting for approvalThe daemon serves all your registered projects from a single process. Switch projects in the dashboard via the Active Project dropdown.
Part of a Suite
Waymark is the control plane of a four-tool developer suite:
Tool | Role | What it does |
Build | Multi-agent dev pipeline — architect → worker → reviewer loop | |
waymark | Run | Policy enforcement + observability for AI agents |
Break | Adversarial scenario testing — acts as a real, impatient user | |
Document | Decision-context wiki for AI agents |
Adversarial testing: teststop runs 52 predicted risk scenarios against waymark's live API, targeting edge cases a real user would hit — concurrent approvals, stale tabs, rollback races, policy bypass attempts. See examples/waymark-demo for the full scenario set and confidence report. The v5.0.13–v5.0.16 safety fixes were all surfaced by this suite.
Platform Support
Platform | Status | Features | Setup |
Claude Desktop | ✅ Recommended | Full features |
|
Claude Code | ✅ Recommended | Full features |
|
GitHub Copilot CLI | ✅ Supported | Full features |
|
GitHub Copilot Chat | ⏳ Future | Waiting for GitHub MCP | See Platform Guide |
CodeWhisperer, Codeium, others | ⏳ Future | Waiting for MCP adoption | See Platform Guide |
👉 For detailed platform information, see docs/README_PLATFORMS.md
📚 Documentation
Full documentation site: shaifulshabuj.github.io/waymark
Covers installation, quickstart, CLI reference, API reference, policy engine, approvals, rollback, agent monitor, philosophy, and changelog.
User stories and feature walkthroughs: docs/user-stories/README.md
Feature 01: Team Approval Routing — Human-in-the-loop control for sensitive changes
Feature 02: Session-Level Rollback — Atomic undo for entire AI agent runs
Feature 03: Email Notifications — SMTP-based alerts for pending approvals
Feature 04: Multi-Platform Support — Consistent governance on Windows, macOS, and Linux
Feature 05: Agent Monitor — Live observability for running and completed AI agent sessions
Feature 06: Enterprise Notifications & Analytics — Block persistence, webhook fan-out, email approval tokens, per-member prefs, and analytics cards
Changelog
See CHANGELOG.md for the full release history.
Recent highlights:
v5.0.20 —
maxBashTimeoutMspolicy: per-call bash timeout clamping viawaymark.config.jsonv5.0.16 — 30+ robustness fixes + complete Vitest migration (624 tests, 33 files)
v5.0.13 — 17 P0 safety fixes surfaced by teststop adversarial testing
v5.0.0 — Unified daemon architecture: one process, all projects, one URL
Architecture
┌──────────────────────────────────────────────────┐
│ mcp/server.ts │
│ │
│ 1. loadConfig() → policies/engine.ts │
│ 2. checkFileAction() │
│ or checkBashAction() │
│ │
│ 3a. decision=block → insertAction(blocked) │
│ throw Error (no exec) │
│ │
│ 3b. decision=pending → insertAction(pending) │
│ notifyPendingAction() │
│ return message+action_id │
│ (agent can poll status) │
│ │
│ 3c. decision=allow → insertAction(allow) │
│ execute tool │
│ updateAction(result) │
└──────────────────────────────────────────────────┘
│ ▲
│ SQLite │ REST API
▼ │
┌─────────────┐ ┌──────────────────────────┐
│ data/ │ │ api/server.ts │
│ waymark.db │◄───────│ GET /api/actions │
└─────────────┘ │ GET /api/actions/:id │
│ POST /api/actions/:id/ │
│ approve │
│ POST /api/actions/:id/ │
│ reject │
│ GET /api/actions/:id/ │
│ status │
│ POST /api/actions/:id/ │
│ rollback │
│ │
│ [Phase 1: Sessions] │
│ GET /api/sessions │
│ GET /api/sessions/:id │
│ GET /api/sessions/:id/ │
│ actions │
│ POST /api/sessions/:id/ │
│ rollback │
│ GET /api/sessions/:id/ │
│ status │
│ │
│ POST /api/slack/interact │
│ GET /api/config │
│ GET / → index.html │
└──────────────────────────┘
▲
Browser http://localhost:<port>Three packages, seven server modules, two runtime processes. The dashboard is a separate React + Vite app (packages/web) whose build output is served statically by the API process.
packages/server/ — MCP server + API server + UI host
File | Process | What it does | Imports |
| MCP (stdio) | Exposes tools to AI agent, enforces policy, logs actions, sends Slack notifications |
|
| API (default port from 47000-47999) | REST API + serves the built React dashboard from |
|
| both | SQLite schema, prepared statements, CRUD functions |
|
| both | Loads config, evaluates file/bash actions against rules |
|
| MCP (stdio) | Sends Slack Block Kit message for pending actions; silent if webhook not configured |
|
| API (port from CLI) | Re-executes approved write_file actions; marks approved/rejected in DB |
|
| browser | Vite build output — the React dashboard (produced by | — |
| API (port from CLI) | Server-Sent Events broadcaster; emits per-topic events on every mutation so the UI can invalidate without polling. | — |
| API only (single | btop-style agent monitor (added in 4.1). |
|
| API (port from CLI) | REST endpoints under |
|
| MCP (stdio) | Three read-only MCP tools — |
|
packages/web/ — React + Vite dashboard (added in 2.0.3)
Area | Purpose |
| Build output goes to |
| Typed fetch client + TanStack Query hooks (3-second |
| 232 px sidebar + topbar with Cmd-K search + live MCP status footer |
| Cross-screen primitives |
| Feature-complete Actions screen (filter pills, session groups, approve/reject/rollback) |
| btop-style live agent view (added in 4.1) — |
| Placeholders for Sessions/Approvals/Policy/Stats/Settings (Phases 2–4) |
| Zustand store for |
| oklch-based dark + light tokens, IBM Plex Sans/Mono self-hosted, density variants, semantic colors, responsive breakpoints |
packages/cli/ — waymark CLI (init, start, stop, status, logs)
File | What it does |
| Entry point — routes |
|
|
|
|
|
|
|
|
|
|
|
|
The two server processes run concurrently and share the same SQLite file. The MCP process writes; the API process reads. SQLite handles concurrent access safely because all database calls are synchronous (better-sqlite3).
CLI Reference
waymark init # first-time setup (idempotent — safe to re-run)
waymark init --yes # non-interactive, accept all defaults
waymark init --dry-run # preview what would be created without writing files
waymark global-setup # register universal MCP entry across all supported hosts
waymark cleanup-mcp # remove stale per-project waymark-* entries
waymark daemon start # start global daemon (port 47000, serves all projects)
waymark daemon stop # stop the daemon
waymark daemon status # list registered projects + uptime
waymark daemon restart # reload after upgrading
waymark service install # auto-start daemon on login (launchd/systemd)
waymark service uninstall # remove auto-start
waymark start # start per-project API + MCP server in background
waymark stop # stop the background servers
waymark status # check if server is running + pending count + port
waymark logs # print recent action table
waymark logs --pending # only pending actions
waymark logs --blocked # only blocked actions
waymark logs --limit 50 # show up to 50 rows (default: 20)
waymark agents # live table of AI coding agent sessions
waymark agents --json # full token / tool / turn breakdown
waymark watch # live ANSI terminal dashboard (2s refresh)
waymark explain <id> # human-readable summary of any action
waymark doctor # validate dependencies and configuration
waymark update # self-upgrade to latest version
waymark relocate # update registry after rename/move
waymark global-config init # create global defaults in ~/.waymark/global.config.json
waymark team add <email> # add a team member
waymark workspace create # group related projectswaymark.config.json Reference
Waymark reads waymark.config.json from the project root fresh on every tool call — no restart needed when you edit it.
{
"version": "1",
"policies": {
"allowedPaths": ["./src/**", "./data/**", "./README.md"],
"blockedPaths": ["./.env", "./.env.*", "./package-lock.json", "/etc/**", "/usr/**"],
"blockedCommands": [
"rm -rf",
"DROP TABLE",
"regex:\\|\\s*bash",
"regex:\\|\\s*sh\\b"
],
"requireApproval": ["./src/db/**", "./waymark.config.json"],
"maxBashOutputBytes": 10000,
"maxBashTimeoutMs": 120000
}
}Field reference
Field | Type | Description |
|
| Glob patterns for paths agents may read/write. Relative patterns resolve from the project root. |
|
| Glob patterns that are always denied. Checked before |
|
| Bash rules. Plain strings match as substrings; |
|
| Glob patterns that are logged as |
|
| Cap on stdout/stderr bytes returned per bash call (default: 10000). Output beyond this limit is truncated with |
|
| Per-call bash timeout cap in ms (clamped to 1000–600000). Overrides the MCP client's default. |
File action precedence
For every write_file or read_file call, the path is tested in this order:
1. blockedPaths? → decision: block (throws error, file not touched)
2. requireApproval? → decision: pending (returns message with action_id, file not touched)
3. allowedPaths? → decision: allow (proceeds)
4. (default) → decision: block ("Path not in allowedPaths")blockedCommands: plain string vs regex
// Substring match — blocks any command containing "rm -rf"
"rm -rf"
// Regex match — blocks any pipe to bash (curl url | bash, cat x | bash, etc.)
"regex:\\|\\s*bash"
// Regex match — blocks command substitution with curl
"regex:\\$\\(curl"Regex rules use JavaScript RegExp with the i (case-insensitive) flag. Invalid regex entries are skipped with a console.warn.
Pattern matching
Relative patterns (./src/**) are resolved to absolute paths from the project root before matching. Absolute patterns (/etc/**) pass through unchanged. Matching uses micromatch with { dot: true } so dotfiles (.env) are included.
REST API Reference
All endpoints are served by api/server.ts on the project's allocated port (default range 47000-47999; pin with port in waymark.config.json or --port flag). Run waymark status to see the live URL.
Method | Path | Response | Notes |
|
|
| Last 100 actions, newest first |
|
|
| Count of pending actions; used by UI badge |
|
|
| Single action by UUID |
|
|
| Lightweight status for agent polling |
|
|
| Executes the original action; sets |
|
|
| Body: |
|
|
|
|
|
|
| Slack interactive components endpoint; requires public URL (use ngrok locally) |
|
|
| Grouped by MCP server process start |
|
|
| Current parsed |
|
| HTML | Web dashboard ( |
Pending action flow
When decision=pending, the MCP server:
Logs the action to DB with
status='pending'Fires a Slack notification (if
WAYMARK_SLACK_WEBHOOK_URLis set in.env)Returns a message to the agent containing the
action_idand the polling URL:Action requires approval. Action ID: <uuid> Check status: GET /api/actions/<uuid>/statusThe agent can poll
/api/actions/:id/statusand continue other work while waiting.
On approve: approvePendingAction re-executes the original tool (writes the file for write_file), sets status='success', decision='allow', and records approved_by + approved_at.
On reject: rejectPendingAction sets status='rejected' and records rejected_reason + rejected_at. The file is never written.
Slack setup
Slack integration is optional. If WAYMARK_SLACK_WEBHOOK_URL is empty, notifications are silently skipped and approval works entirely from the UI dashboard.
There are two independent pieces:
Incoming webhook — posts a notification card when an action goes pending (one-way, read-only setup)
Interactive components — lets you approve/reject directly from the Slack message (requires a public URL)
You can use (1) without (2). Notifications will arrive but the buttons won't work.
Step 1 — Create a Slack App
Go to https://api.slack.com/apps → Create New App → From scratch
Name it
Waymark, pick your workspace → Create App
Step 2 — Incoming Webhook (notifications)
In your app's sidebar: Incoming Webhooks → toggle Activate Incoming Webhooks ON
Click Add New Webhook to Workspace → pick the channel (e.g.
#engineering) → AllowCopy the webhook URL — it looks like
https://hooks.slack.com/services/T.../B.../...Add to
.env:
WAYMARK_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../...
WAYMARK_SLACK_CHANNEL=#engineering
WAYMARK_BASE_URL=http://localhost:47000WAYMARK_BASE_URL is used to build the "View in Dashboard" link inside the Slack card. Set it to whatever URL your Waymark UI is reachable at.
Restart the server. The next pending action will post a card to Slack.
Step 3 — Interactive Components (approve/reject buttons)
The Approve and Reject buttons in the Slack card POST to POST /api/slack/interact. Slack requires this endpoint to be publicly reachable over HTTPS.
For local development — use ngrok:
ngrok http 47000Copy the https:// forwarding URL (e.g. https://abc123.ngrok-free.app).
In your Slack app sidebar: Interactivity & Shortcuts → toggle Interactivity ON
Set Request URL to:
https://abc123.ngrok-free.app/api/slack/interactClick Save Changes
ngrok URLs change on every restart. Update the Request URL in Slack each time.
For production — set a stable public URL:
Deploy Waymark behind a reverse proxy (nginx, Caddy, etc.) with TLS, then set the Request URL once to https://your-domain.com/api/slack/interact. Update WAYMARK_BASE_URL in .env to match.
Step 4 — Verify
Trigger a pending action (write to a
requireApprovalpath via the MCP tool)A card should appear in your Slack channel with Approve and Reject buttons
Click Approve — the button should update the message and the UI dashboard should show the action as
success / allow
Troubleshooting
Symptom | Cause | Fix |
No Slack message appears |
| Check |
Buttons do nothing | Request URL not set or ngrok restarted | Update Interactivity URL in Slack app settings |
| Waymark returned non-200 or timed out | Check server logs; ensure |
| Webhook URL is malformed | Re-copy URL from Slack app settings |
Buttons appear but action stays pending |
| Check server logs for handler errors |
Database Schema
Single table: action_log in data/waymark.db.
Column | Type | Nullable | Description |
| INTEGER | NO | Auto-increment primary key |
| TEXT | NO | UUID — unique per tool call |
| TEXT | NO | UUID — shared across all calls in one MCP server process |
| TEXT | NO |
|
| TEXT | YES | Absolute file path for write/read; |
| TEXT | NO | JSON-serialized tool arguments |
| TEXT | YES | File content before write ( |
| TEXT | YES | File content after write, or file content for reads |
| TEXT | NO |
|
| TEXT | YES | Error detail when status=error |
| TEXT | YES | bash stdout output |
| TEXT | YES | bash stderr output |
| INTEGER | NO |
|
| TEXT | YES | ISO datetime of rollback |
| DATETIME | NO | Row creation time |
| TEXT | NO | Policy decision: |
| TEXT | YES | Human-readable reason for the decision |
| TEXT | YES | The specific rule that matched (e.g. |
| TEXT | YES | ISO datetime when approved |
| TEXT | YES |
|
| TEXT | YES | ISO datetime when rejected |
| TEXT | YES | Human-provided rejection reason |
Schema migrations use try { ALTER TABLE ADD COLUMN ... } catch {} — safe to run against existing databases.
Rollback
Rollback is available for write_file actions only. The UI shows a "rollback" or "delete (new file)" button in the Action column.
Overwrite rollback: before_snapshot is non-null → the file existed before. Rollback writes before_snapshot back to target_path.
New file rollback: before_snapshot is null → the file was created from scratch. Rollback calls fs.unlinkSync(target_path) — the file is deleted.
Blocked actions never modify the filesystem, so they have no rollback button.
Pending actions also never modify the filesystem until explicitly approved. After approval, the approved action row shows a ✅ Approved badge instead of a rollback button.
Once rolled back, rolled_back=1 is set and the action cannot be rolled back again.
Plan Mode Logging & Observability
Waymark logs execution-phase tool calls only — not plan-mode reads, not internal reasoning. Only actual MCP tool calls (write_file, read_file, bash) appear in the ledger. This is a protocol-level constraint: MCP has no mechanism for "I'm thinking about this file" vs "I'm executing with this file."
For AI Agents
Key facts for working with this codebase:
Module dependency order (bottom-up):
micromatch, better-sqlite3, express, dotenv, fetch (Node 18+ built-in)
↓
packages/server/src/db/database.ts (no imports from waymark modules)
packages/server/src/policies/engine.ts (no imports from waymark modules)
↓
packages/server/src/notifications/slack.ts (imports db/database for ActionRow type only)
packages/server/src/approvals/handler.ts (imports db/database)
↓
packages/server/src/mcp/server.ts (imports database + engine + notifications/slack)
packages/server/src/api/server.ts (imports database + engine + approvals/handler)Path resolution — WAYMARK_PROJECT_ROOT: Both database.ts and policies/engine.ts resolve their data/config paths via:
const PROJECT_ROOT = process.env.WAYMARK_PROJECT_ROOT || process.cwd();
const DATA_DIR = path.join(PROJECT_ROOT, 'data'); // database.ts
const CONFIG_PATH = path.join(PROJECT_ROOT, 'waymark.config.json'); // engine.tsWAYMARK_PROJECT_ROOT is set by the --project-root CLI argument parsed at the very top of mcp/server.ts before any imports (because database.ts and engine.ts read the env var at module load time). When running via waymark start, process.cwd() is the project directory so the fallback works correctly.
Database is synchronous: better-sqlite3 is used throughout. There is no await on any database call. Do not introduce async database wrappers.
Config is read fresh every call: loadConfig() in engine.ts reads and parses waymark.config.json on every invocation. No caching. Changes to the config file take effect immediately without restarting the MCP server.
MCP server must be restarted to pick up recompiled code: The policy config is hot-reloaded, but the TypeScript-compiled JavaScript is not. After npm run build, restart Claude Desktop (or the MCP process) for engine changes to take effect.
SESSION_ID is a UUID generated once per MCP server process start (const SESSION_ID = uuidv4() at module level in mcp/server.ts). It groups all tool calls within one session.
Environment variables (loaded via dotenv/config as first import in both server files):
WAYMARK_PROJECT_ROOT— absolute path to the project being monitored; determines wheredata/waymark.dbandwaymark.config.jsonare read from. Set via--project-rootCLI arg orwaymark start. Falls back toprocess.cwd().WAYMARK_SLACK_WEBHOOK_URL— Slack incoming webhook; if empty, Slack notifications are silently skippedWAYMARK_SLACK_CHANNEL— informational only, not used in code (webhook targets its own channel)WAYMARK_BASE_URL— used to build "View in Dashboard" link in Slack messages (default:http://localhost:47000)
Route registration order in api/server.ts is significant: /approve, /reject, /status sub-routes are registered before the /:action_id catch-all GET, otherwise Express would match the sub-routes as action IDs.
Before modifying mcp/server.ts: the policy check must remain before insertAction for blocked/pending cases — these actions must never touch the filesystem. For pending, the Slack notification fires after insertAction with an inline ActionRow object (no DB round-trip).
Before modifying policies/engine.ts: the precedence order (blocked → pending → allowed → default deny) is load-bearing. Changing it changes observable behavior for all callers.
Known gaps:
No rate limiting on API endpoints
getActions()is hardcoded toLIMIT 100with no pagination
Documentation
The full documentation lives in docs/. Start at docs/README.md for a grouped index of every guide.
Most-used pages:
FEATURES.md— High-level feature list.docs/FAQ.md— Frequently asked questions.docs/APPROVALS.md·docs/ESCALATIONS.md·docs/REMEDIATION.md— Approval, escalation, and rollback workflows.docs/SESSIONS.md— Agent session tracking.docs/README_PLATFORMS.md— Supported platforms.docs/COPILOT_CLI.md— GitHub Copilot CLI integration.docs/user-stories/README.md— Feature walkthroughs with screenshots.docs/PHASES.md·docs/RELEASES.md— Phase and release indexes.docs/philosophy.md— The full philosophy behind why Waymark exists.
Development
npm run build # compile both packages (tsc in each workspace)
cd packages/server && npm run dev:api # API server only (ts-node)
cd packages/server && npm run dev:mcp # MCP server only (ts-node)
cd packages/server && npm run db:reset # delete data/waymark.db and recreate schema
npm test # run the full test suite (vitest, 624 tests, 33 files)Building the CLI: cd packages/cli && npm run build compiles to packages/cli/dist/. Test locally with node packages/cli/dist/index.js <command>.
Extending tools: Add new tool schemas to ListToolsRequestSchema handler and a new else if (name === '...') branch in CallToolRequestSchema handler in packages/server/src/mcp/server.ts. Follow the existing pattern: policy check → insertAction → execute → updateAction.
Release Operations
Automated Release (Recommended)
npm run release major|minor|patchThe script validates git state, bumps versions in both packages/cli/package.json and packages/server/package.json, updates CHANGELOG.md, commits, tags, and pushes. GitHub Actions release.yml then publishes to npm and creates the GitHub Release.
Important: Both package versions must always match — the CLI declares a hard dependency on the exact server version.
Manual Release
Bump versions in
packages/server/package.jsonandpackages/cli/package.json(both must match).Update the server dependency reference inside
packages/cli/package.json→dependencies["@way_marks/server"]to the same new version.Regenerate the lock file:
npm install --package-lock-onlyAdd a
release/CHANGELOG.mdentry for the new version.Add a root
CHANGELOG.mdentry.Commit, then tag:
git tag v<version> && git push origin v<version>
Known CI/CD failure: E403 "cannot publish over previously published version"
Cause: The version in either packages/cli/package.json or packages/server/package.json was not bumped before tagging. npm does not allow re-publishing an already-released version.
Fix: Bump both package versions, update the @way_marks/server dependency in packages/cli/package.json, run npm install --package-lock-only, commit the version bump, re-tag, and push.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP enforcement layer that intercepts AI agent actions and blocks rule violations before execution.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
- gatewayOAuthai.sealgate
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAn MCP server that enforces runtime governance on AI agent actions — file access, command execution, delegation chains, and permission escalation.MIT
- FlicenseNot gradedqualityDmaintenanceA local MCP server that provides controlled repository access with policy-based file filtering, secret redaction, and audit logging for AI coding agents.-
- AlicenseNot gradedqualityBmaintenanceA self-hosted MCP server that enables AI coding agents to read, edit, search, and run code in local projects with human review loops and policy controls.MIT
- FlicenseNot gradedqualityCmaintenanceMCP server that provides a security gateway for AI agents, enforcing allow/confirm/deny policies on tool calls and requiring human approval for risky operations, with full audit logging.-