Back to Browse

Cubiczan Chp MCP Server

Developer ToolsModerate5.2MCP RegistryLocal
Free

Server data from the Official MCP Registry

Spend approval gate for AI agents: budget caps, HITL thresholds, signed audit hash.

About

Spend approval gate for AI agents: budget caps, HITL thresholds, signed audit hash.

Security Report

5.2
Moderate5.2Moderate Risk

This is a well-structured MCP server that wraps a spend-approval library with appropriate input validation and error handling. Authentication and authorization are delegated to the underlying @cubiczan/chp library, which is the correct design pattern. No malicious patterns, credential leaks, or dangerous operations detected. Permissions are appropriately scoped to the server's purpose of evaluating spending policies. Supply chain analysis found 3 known vulnerabilities in dependencies (0 critical, 3 high severity). Package verification found 1 issue.

5 files analyzed · 6 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

env_vars

Check that this permission is expected for this type of plugin.

process_spawn

Check that this permission is expected for this type of plugin.

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-icohangar-ops-chp-mcp": {
      "args": [
        "-y",
        "@cubiczan/chp-mcp"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

@cubiczan/chp-mcp

icohangar-ops/cubiczan-chp-mcp MCP server

One-command MCP install for CHP Profile B spend / capital gates and tool-approval receipts (an allowlist is not authorization), plus a structured deny ledger and receipt-gated finance tools.

MCP Registry npm Conformance

Wraps @cubiczan/chp so Cursor, Claude Code, or any MCP client can call evaluate_spend_gate without vendoring protocol code. Engine digests match the normative golden vectors (Profile B 30/30).

How the pieces fit

MCP client (Cursor / Claude / …)
        │  tools/call
        ▼
┌───────────────────────────┐
│  MCP server (transport)   │  ← you are here (@cubiczan/chp-mcp)
│  evaluate_spend_gate      │
│  approve_spend            │
│  evaluate_tool_approval   │  allowlist ≠ authorization
│  issue_approval_receipt   │
│  authorize_tool_call      │
│  request_authorization    │  finance-tool receipt / HITL / deny
│  place_equity_order       │  scoped + receipt-gated (synthetic)
│  wire_treasury_transfer   │
│  rebalance_portfolio      │
│  inspect_audit_ledger     │  CHP-signed deny / authorize / execute
│  chp_content_hash         │
└─────────────┬─────────────┘
              │ depends on
              ▼
┌───────────────────────────┐
│  Published CHP packages   │
│  npm:  @cubiczan/chp                 (Profile B)
│  PyPI: consensus-hardening-protocol  (Profile A)
└───────────────────────────┘

For AGENTS.md + skills + Profile A decision_gate / decision_adversary, use agent-conductor instead.

Install

npm install -g @cubiczan/chp-mcp
# or one-shot
npx -y @cubiczan/chp-mcp

Cursor / Claude Desktop

{
  "mcpServers": {
    "chp": {
      "command": "npx",
      "args": ["-y", "@cubiczan/chp-mcp"]
    }
  }
}

Claude Code

claude mcp add chp -- npx -y @cubiczan/chp-mcp

Tools

ToolMaps toPurpose
evaluate_spend_gateevaluateGateLOCKED / HITL_REQUIRED / BLOCKED + claims + content hash. BLOCKED is also a ledgered policy_deny.
approve_spendapproveHumanHuman lock when HITL_REQUIRED (cannot override hard fails). Optional tool + bound_args mint a signed receipt.
evaluate_tool_approvalevaluateToolApprovalAllowlist is a pre-filter; host-bound fields merge into args_hash; a receipt is still required
issue_approval_receiptissueApprovalReceiptHuman allow/deny → HMAC-signed receipt + decision log
authorize_tool_callauthorizeToolCallConsume a receipt; deny on drift, host-bound override, expiry, replay, or a bad MAC
request_authorizationruntimeMint a receipt bound to a scoped reference tool, or return HITL / structured deny
place_equity_orderreferenceSynthetic equity order — scope trading:equities:place, receipt required
wire_treasury_transferreferenceSynthetic treasury wire — scope treasury:wire, always HITL
rebalance_portfolioreferenceSynthetic rebalance — scope portfolio:rebalance
inspect_audit_ledgerledgerTrailing CHP-chained deny / authorize / execute entries
chp_content_hashcontentHashFloat-aware canonical SHA-256
chp_version—Server + protocol versions + deny reason codes + receipt schema

Example — evaluate a spend

// tools/call evaluate_spend_gate
{
  "action": { "action": "LONG", "asset": "ETH", "notional": 300, "confidence": 0.9 },
  "policy": {
    "max_notional": 500,
    "daily_cap": 2500,
    "hitl_threshold": 250,
    "min_confidence": 0.55,
    "allowed_actions": ["LONG", "SHORT"]
  }
}

Cookbook — Claude / Cursor tool approval

Managed MCP allowlists (Cursor mcpServers, Claude Desktop, Claude Code) only answer “is this tool name installed?”. They do not bind tenant, arguments, risk, or a human decision. This server treats that gap as a hard deny unless a signed approval receipt still matches the call that is about to run.

Receipts are HMAC-SHA256 over CHP canonical JSON (the same payload discipline as Profile B contentHash / audit-ledger sig). The MAC covers:

FieldRole
actorHuman who allowed or denied
toolConcrete tool name (no *)
resourceTenant / resource binding (no *)
args_hashcontentHash(host ∪ model arguments, { floatAware: true })
policy_versionPolicy the human saw
riskPolicy risk for that tool
issued_at / expiryLifetime
decisionallow or deny
nonceSingle-use; replay denies
signatureHMAC-SHA256 hex

Set CHP_RECEIPT_KEY (or AUDIT_LEDGER_KEY) in the MCP server env. Without it the process falls back to a documented insecure default — fine for the local cookbook, not for production.

Example policy: examples/tool-approval-policy.json. stripe.create_charge is on the allowlist and still cannot run without a receipt bound to acct_live_acme and the exact charge args. Host-injected tenant/index bindings use examples/host-injected-policy.json (see the host-injected args cookbook below).

{
  "mcpServers": {
    "chp": {
      "command": "npx",
      "args": ["-y", "@cubiczan/chp-mcp"],
      "env": { "CHP_RECEIPT_KEY": "replace-me" }
    }
  }
}

1. Allowlist alone — denied

Claude/Cursor has stripe.create_charge enabled. That is not a grant.

// tools/call evaluate_tool_approval
{
  "call": {
    "tool": "stripe.create_charge",
    "resource": "acct_live_acme",
    "arguments": { "amount": 2500, "currency": "usd", "customer": "cus_123" }
  },
  "policy": { "$ref": "examples/tool-approval-policy.json" }
}

Result: RECEIPT_REQUIRED, deny_code: "allowlist_is_not_authorization".

Calling authorize_tool_call with the same payload and no receipt returns DENIED / allowlist_is_not_authorization.

2. Human allow — then authorize

// tools/call issue_approval_receipt
{
  "actor": "cfo@acme.example",
  "decision": "allow",
  "reason": "invoice INV-104 matches amount",
  "ttl_seconds": 120,
  "call": { /* same as above */ },
  "policy": { /* same as above */ }
}

The response includes receipt (take the whole object) and decision_log (actor, decision, args hash, nonce). Pass that receipt into authorize_tool_call with the same call. Result: AUTHORIZED.

3. Human deny

Issue with "decision": "deny". The decision is logged. Authorizing with that receipt returns DENIED / human_denied. A deny receipt cannot be flipped to allow by editing decision — the MAC breaks.

4. Changed arguments after approval — denied

Approve { "amount": 2500, ... }, then authorize with { "amount": 2500000, ... }. Result: DENIED / changed_arguments. Key order does not matter; the hash is CHP canonical. The original receipt remains valid for the args that were approved (until expiry or a successful consume).

5. Expired receipt — denied

Issue with ttl_seconds: 30. After the expiry instant, authorize_tool_call returns DENIED / expired_receipt. The nonce is consumed so a clock rewind cannot resurrect it.

6. Replayed receipt — denied

A successful AUTHORIZED consume burns the nonce. Presenting the same receipt again returns DENIED / replayed_receipt.

7. Ambiguity — denied

These never produce a usable allow receipt:

  • resource: "*", any, all, or an empty string
  • missing arguments
  • policy without a concrete version
  • actor / tool wildcards
  • extra keys on a receipt (strict parse)

Fail-closed: deny_on_ambiguity cannot be turned off.

Cookbook — host-injected args + gateway _meta

Semantic Kernel and other hosts need to pass index, key, and tenant without letting the model choose them (SO-style routing). Putting those fields on the tool schema so the LLM can “decide” is the bug. An MCP allowlist does not fix it: the tool name can stay allowlisted while the model swaps index_name to another tenant.

The host (or a gateway in front of this server) injects bound fields. This package hashes host ∪ model arguments into the receipt and denies when the model overrides a host-bound field. The allowlist is still only a pre-filter.

Contract — _meta.cubiczan (no hard dependency)

@cubiczan/governed-mcp-gateway already injects identity on every tools/call and SSE frame:

{
  "_meta": {
    "cubiczan": {
      "principal": {
        "id": "agt_search",
        "kind": "agent",
        "orgId": "org_acme",
        "displayName": "Search Runner"
      }
    }
  }
}

This server does not import that package. It reads the same envelope. Hosts MAY add host_bound next to principal. A trusted gateway should overwrite _meta.cubiczan so the model cannot self-attest.

{
  "_meta": {
    "cubiczan": {
      "principal": { "id": "agt_search", "kind": "agent", "orgId": "org_acme" },
      "host_bound": { "tenant_id": "acme", "index_name": "prod-docs" }
    }
  }
}

Library callers can also pass host_bound on the proposed call (explicit keys overlay _meta). Policy examples/host-injected-policy.json declares host_bound_fields so index_name and tenant_id must be host-injected and concrete. If tenant_id is declared and omitted, _meta.cubiczan.principal.orgId may fill it.

model args ──┐
             ├─ override check ─→ deny host_bound_override
host_bound ──┘         │
                       ▼
              merged args → args_hash → receipt MAC
                       │
allowlist ──── pre-filter only (never a grant)

1. Host injects index + tenant — allowlist still denied

The model chose query / top_k. The host chose the index.

// tools/call evaluate_tool_approval
{
  "call": {
    "tool": "search.azure_ai",
    "resource": "tenant:acme",
    "arguments": { "query": "Q3 revenue", "top_k": 5 },
    "_meta": {
      "cubiczan": {
        "principal": { "id": "agt_search", "kind": "agent", "orgId": "org_acme" },
        "host_bound": { "tenant_id": "acme", "index_name": "prod-docs" }
      }
    }
  },
  "policy": { "$ref": "examples/host-injected-policy.json" }
}

Result: RECEIPT_REQUIRED, deny_code: "allowlist_is_not_authorization". args_hash is contentHash of { query, top_k, tenant_id, index_name } — not the model object alone.

2. Model changes a host-bound field — denied

Same host _meta, but the model adds "index_name": "other-index".

"arguments": { "query": "Q3 revenue", "top_k": 5, "index_name": "other-index" }

evaluate_tool_approval, issue_approval_receipt (decision: "allow"), and authorize_tool_call all return DENIED / host_bound_override. Matching the host value is not an override.

3. Human allow — then authorize the merged args

Issue a receipt for the host-injected call. Authorize with the same arguments and _meta. Result: AUTHORIZED. Change query after approve → changed_arguments. Change index_name in model args → host_bound_override. Omit declared host fields → ambiguous.

4. Semantic Kernel / host wiring

Do the routing in the host, not the model: disable auto-invoke, then inject index/tenant (or put a gateway in front that writes _meta.cubiczan.host_bound) before evaluate_tool_approval / authorize_tool_call. Secrets such as API keys belong in the host or the gateway vault — not in the tool schema the LLM sees.

Cookbook — deny telemetry and receipts

MCP denials are usually a bare error string. That string is gone when the client disconnects. This server treats a refuse as a structured event that must hit a CHP-signed ledger before the caller sees it.

Finance tools (place_equity_order, wire_treasury_transfer, rebalance_portfolio) are synthetic — no live venue or bank rail — and use a separate kind: "authorization" receipt bound to tool, scope, and args hash. That is not the same object as a chp.tool_approval_receipt.

Reason codes

CodeWhen
policy_denyHard CHP rule failed (max_notional, daily cap, …)
expiredReceipt expires_at is in the past
replayReceipt already consumed by a successful execute
args_changedTool, scope, or args hash no longer matches the receipt
missing_receiptNo receipt, or the content hash does not verify
ambiguous_policyUnknown tool, scope mismatch, or incomplete policy

Signing is the existing Profile B primitives: contentHash on the receipt / ledger payload, chainHash between ledger rows. Set CHP_AUDIT_LEDGER to a JSONL path (default ./data/chp-audit.jsonl), or :memory: for tests.

1. Request a bound receipt

Under the HITL threshold the gate auto-locks and mints a receipt. At or above it, pass approver (or call approve_spend with tool + bound_args).

// tools/call request_authorization
{
  "tool": "place_equity_order",
  "args": {
    "symbol": "AAPL",
    "side": "BUY",
    "quantity": 10,
    "notional": 300,
    "confidence": 0.9
  },
  "approver": "cfo@example.com"
}

Treasury wires use hitl_threshold: 0. A request without approver returns HITL_REQUIRED and no receipt — that is the approval gate, not a weather-API demo.

2. Execute only with that receipt

receipt is optional on the wire so a missing token is a logged missing_receipt deny, not a schema 400 that never hits the ledger.

// tools/call place_equity_order
{
  "symbol": "AAPL",
  "side": "BUY",
  "quantity": 10,
  "notional": 300,
  "confidence": 0.9,
  "receipt": { "kind": "authorization", "receipt_id": "…", "content_hash": "…" }
}

Change notional or quantity after approve → args_changed, and the ledger has the deny. Call again with the same receipt → replay. Call with no receipt → missing_receipt. All three are durable.

3. Inspect the chain

// tools/call inspect_audit_ledger
{ "limit": 20 }

Each row carries content_hash and sig = chainHash(prev_sig, { seq, ts, event, content_hash }). chain.ok is false if anyone rewrote history.

Tests

npm test

This Cubiczan mirror may omit GitHub Actions; run the suite locally. npm test builds, then runs node --test dist/*.test.js (approval receipts + host-injected bindings) and node --import tsx --test test/**/*.test.ts (deny ledger). Invariants covered: an unlogged deny is impossible (ledger failure throws instead of returning a deny object); changed args after approve deny; a receipt is required for every gated reference tool; allowlist is not authorization; host-bound tenant/index cannot be overridden by the model; receipt args_hash covers host ∪ model args.

Related

Package / repoRole
@cubiczan/chpProfile B library (this server’s dependency)
consensus-hardening-protocolProfile A + normative spec
@cubiczan/agent-conductorFull MCP: contracts, skills, Profile A gates
@cubiczan/governed-mcp-gatewayHTTP MCP control plane
@cubiczan/codesentinel-mcpCodebase health MCP
cubiczan-resilience / @cubiczan/resilienceShared retry / timeout / audit primitives

Licence

MIT.

Reviews

No reviews yet

Be the first to review this server!