Appearance
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_toolsanswers "what's available?" — it is not authorization.execute_toolis where policy is enforced. Discovery ≠ authorization.
Auth
- Principal: Clerk session or API key → resolved to
{ tenant, principal_id, role, scopes }(reuses theplatform.whoamicontract). - 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_intentfor 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 scopes4. 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)
Registry — GET/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.
Approvals — GET /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.
Evidence — GET /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.