Spend + tool-approval gate for AI agents — budget caps, HITL, and signed receipts so an MCP allowlist is never treated as authorization.
Strong domain-specific tool design with comprehensive schemas and clear descriptions. All 12 tools have explicit input schemas with typed parameters and descriptions. Tool names follow verb_noun patterns (evaluate_*, approve_*, request_*, place_*, wire_*, rebalance_*, inspect_*, issue_*, authorize_*, chp_*). Descriptions are detailed (100-300 chars) and explain WHAT, WHEN, and consequences. However, output schemas are not documented in the visible code, responses are returned as JSON text without formal schema declarations. Error handling is present but generic (jsonContent/errorContent helpers). No tool annotations (readOnlyHint, destructiveHint, idempotentHint) despite clear risk levels (READ_ONLY vs WRITE). Parameter descriptions are thorough but some lack explicit format/range constraints (e.g., ttl_seconds, notional bounds).
Human-in-the-loop approval for a proposal that returned HITL_REQUIRED. Cannot approve BLOCKED / hard-rule failures (spec §6.3 / §6.5). When tool + bound_args are supplied, mints a signed authorization receipt.
Consume a receipt (deny on drift/expiry/replay). Verifies and consumes an approval receipt, checking for signature validity, expiry, argument drift, and replay attacks.
Float-aware canonical content hash. Computes a canonical content hash of a value using CHP's float-aware canonicalization.
Package / protocol versions. Returns the CHP package version and MCP protocol version.
Run CHP Profile B capital/spend gate on a proposed action. Returns LOCKED, HITL_REQUIRED, or BLOCKED with claims and a content hash. Hard policy violations cannot be overridden by a human. BLOCKED is also written as a structured policy_deny to the CHP-signed audit ledger.
Allowlist ≠ authorization; bind a proposed call. Evaluates whether a tool call is allowed under the approval policy, checking allowlist membership, host-bound field bindings, and receipt requirements.
Output schemas not documented. Tools return JSON text via jsonContent() helper, but no formal response schema is declared. LLMs cannot predict return structure or plan downstream calls. Audit ledger, receipt objects, and gate results lack documented field contracts.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Tools are marked with Risk: READ_ONLY or WRITE in comments, but MCP tool definitions lack formal annotations. LLMs cannot distinguish safe reads from destructive writes without parsing descriptions.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | 2026-07-28+ | v2 |
CHP-chained deny / authorize / execute log. Returns the full audit ledger with chain verification.
Human allow/deny → signed receipt. Issues a signed approval receipt for a tool call decision (allow or deny).
Synthetic equity order (scope trading:equities:place). No live venue. Requires a signed authorization receipt bound to these args. Receipt is optional on the wire so a missing receipt becomes a logged missing_receipt deny.
Synthetic portfolio rebalance (scope portfolio:rebalance). HITL at/above the default $1,000 notional. Requires a signed authorization receipt.
Request a signed authorization receipt for a scoped reference tool (place_equity_order, wire_treasury_transfer, rebalance_portfolio). Auto-lock mints a receipt; HITL_REQUIRED waits for approver; hard fails return a structured deny that is already on the audit ledger.
Synthetic treasury wire (scope treasury:wire). Default policy always requires a human-issued receipt. No live bank rail. Missing receipt is a logged deny, not a bare MCP error string.
Numeric parameter constraints incomplete. ttl_seconds, notional, daily_cap, max_notional, limit_price, quantity lack explicit min/max bounds in descriptions. LLMs may pass absurd values (negative amounts, zero TTL, 1M notional) without validation guidance.
Error handling is generic. jsonContent() and errorContent() return raw JSON without recovery guidance. When a gate returns BLOCKED or a receipt expires, the LLM receives the status but no actionable next step (e.g., 'Try with lower notional' or 'Request new receipt').
Parameter interdependencies underdocumented. approve_spend requires tool + bound_args together; request_authorization requires approver only when gate returns HITL_REQUIRED. These conditional requirements are stated in descriptions but not formalized as schema constraints or clear error messages.