Skip to content

AI Control Center — Gateway API Contract

Status: draft for reflection · Date: 2026-06-24 · Surface: DevPlane Standard: REST + MCP, declare-once (per portfolio service standard). Reuses DevPlane identity (Clerk), the credential vault, and the Grounded-Action policy engine.

Design rule: the gateway exposes a small orchestration surface, not the raw downstream tools. search_tools answers "what's available?" — it is not authorization. execute_tool is where policy is enforced. Discovery ≠ authorization.


Auth

  • Principal: Clerk session or API key → resolved to { tenant, principal_id, role, scopes } (reuses the platform.whoami contract).
  • Transport: Authorization: Bearer <token> (REST); MCP clients pass the key via the configured env/header. All calls are tenant-scoped server-side; the client never selects its own tenant.
  • Every gateway call carries session_id + user_intent for traceability.

Risk model

Each registry tool has risk_level ∈ {read, write, external, destructive} and confirm_required ∈ {never, sensitive, always}. Policy decisions are one of: allow · route (→ approval queue) · deny · abstain (no rule → default-deny + non-binding suggestion slot; reuses grounded-action semantics).


Endpoints (REST) + MCP twins

1. Open a session

POST /api/v1/gw/session · MCP gw_session

json
// → request
{ "user_intent": "Prepare Q3 comp adjustments for the sales org" }
// ← response
{ "session_id": "ses_8f2c…", "tenant": "acme", "expires_at": "2026-06-24T18:00:00Z" }

2. Discover tools (availability, NOT authorization)

POST /api/v1/gw/search-tools · MCP search_tools

json
// → request
{ "session_id": "ses_8f2c…", "query": "propose compensation change", "limit": 5 }
// ← response
{ "tools": [
  { "tool_id": "anycomp.propose_adjustment", "server": "comp", "risk_level": "write",
    "confirm_required": "always", "data_scope": ["comp:band","employee:comp"],
    "summary": "Propose a comp change for an employee within an approved band" }
] }

3. Describe a tool (schema before calling)

POST /api/v1/gw/describe-tools · MCP describe_tools

json
{ "session_id": "ses_8f2c…", "tool_ids": ["anycomp.propose_adjustment"] }
// ← returns input schema, output shape, risk_level, confirm_required, required scopes

4. Execute (policy is enforced HERE)

POST /api/v1/gw/execute · MCP execute_tool

json
// → request
{ "session_id": "ses_8f2c…", "tool_id": "anycomp.propose_adjustment",
  "args": { "employee_id": "e_123", "new_base": 142000, "rationale": "market + performance" } }

// ← allowed
{ "status": "allowed", "decided_by": "symbolic-policy", "result": { "proposal_id": "prop_77" },
  "evidence_id": "ev_a1b2" }

// ← routed to approval (sensitive write)
{ "status": "routed", "decided_by": "symbolic-policy", "approval_id": "apr_55",
  "reason": "comp write requires Total Rewards approval", "evidence_id": "ev_a1b3" }

// ← denied
{ "status": "denied", "decided_by": "symbolic-policy",
  "reason": "principal scope lacks comp:band:write", "evidence_id": "ev_a1b4" }

5. Confirm / resolve a routed action

POST /api/v1/gw/confirm · MCP confirm_operation

json
{ "session_id": "ses_8f2c…", "approval_id": "apr_55", "decision": "approve",
  "approver_note": "within range, fairness check clean" }
// ← on approve: credential vault injects downstream creds server-side, tool executes, evidence appended
{ "status": "executed", "result": { "proposal_id": "prop_77" }, "evidence_id": "ev_a1b5" }

Supporting surfaces (REST + MCP)

RegistryGET/PUT /api/v1/registry/tools List/curate radar entries with risk_level / confirm_required / data_scope. Unknown tools default-deny.

Policy (reuse grounded-action) — POST /api/v1/policy/decide · /explain Extend the typed action-policy-registry.json; do not rebuild the engine. decided_by = "symbolic-policy", llm_used = false.

ApprovalsGET /api/v1/approvals · POST /api/v1/approvals/:id/{approve,deny} The human-approval-queue as a first-class store; each resolution links to an evidence record.

EvidenceGET /api/v1/evidence/:id · GET /api/v1/evidence?session=… · POST /api/v1/evidence/legal-hold Control records: { actor, tool, args_summary, policy_decision, approver, result, source_refs, timing }. Legal hold freezes a set against deletion.

Error model

json
{ "error": { "code": "scope_denied|tool_unknown|session_expired|policy_abstain|downstream_error",
             "message": "human-readable", "evidence_id": "ev_…" } }

Even denials and errors write a control record — every capability resolves to a control record.

MCP tool list (declare-once twins)

gw_session · search_tools · describe_tools · execute_tool · confirm_operation · registry_list · policy_decide · policy_explain · approvals_list · approvals_resolve · evidence_get · evidence_legal_hold.