Task — engineering-spec@1

"MCP elicitation store with confirmation round-trip"

doneTASK-MCP-008
module mcp · class product · priority p0 · created 2026-05-17 · shipped null
depends on TASK-MCP-001, TASK-MCP-004 · blocks none

TASK-MCP-008: MCP elicitation store with confirmation round-trip

Summary

Ship the MCP elicitation primitive in services/mcp-gateway/: closed 5-type / 5-status enums, fixed per-type response schemas, in-memory ElicitationStore for the no-DB path, Postgres store-of-record via elicitation_pg.rs + migration 0016_mcp_elicitations.sql, and REST poll/respond/cancel. The load-bearing use is confirmation round-trip for TASK-MCP-006 destructive gating; enums/schemas for other types (including file_upload) exist, but full infra for NATS, LISTEN/NOTIFY, S3 upload, rate limit, timeout sweeper, and prune is deferred.

Problem

Server-initiated mid-call prompts are part of MCP 2025-11-25. Without them, missing args force fail-and-retry, and destructive tools cannot present a structured confirm/decline. The original engineering-spec targeted services/mcp/src/elicitation/ with NATS push, LISTEN/NOTIFY wakeups, S3 presign, timeout jobs, and twelve elicitation_* integration test files — that tree was never built. As-built lives under mcp-gateway as two modules + one migration, with real in-crate and router tests.

Proposed Solution

In-memory (elicitation.rs): ElicitationType / ElicitationStatus (cardinality 5 each), response_schema, validate_response, ElicitationStore with create / create_confirmation / respond / cancel / pending / confirmation_state / is_confirmed, retry cap MAX_RETRIES=3, idempotent resubmit.

Postgres (elicitation_pg.rs + 0016_mcp_elicitations.sql): write-through store-of-record when a pool + authenticated caller exist — create_confirmation, confirmation_state, pending, respond (KMS-sealed payload + denormalized confirmed), cancel. Caller-scoped (DEC-1159) in the handler; append-only GRANT model (GUC RLS deferred).

HTTP (router.rs): GET /v1/mcp/elicitations, POST /v1/mcp/elicitations/:id/respond, POST /v1/mcp/elicitations/:id/cancel. Destructive tools/call (TASK-MCP-006) creates a confirmation elicitation and consults confirmation_state on re-invoke.

Audit (oauth/audit.rs): emitters for requested / responded / cancelled / validation_failed; elicitation_timeout defined for the deferred sweeper.

Alternatives Considered

Success Metrics

Scope

In scope (as-built under services/mcp-gateway/):

Out of scope / Non-Goals

Dependencies

depends_on: [TASK-MCP-001, TASK-MCP-004] — gateway router + authenticated callers. Soft: TASK-MCP-006 consumes confirmation elicitation for destructive gating (reciprocal integration). Related: TASK-MCP-007 (task-bound elicitations / worker wait deferred).

1. Description (normative)

Acceptance criteria

Verification

cd services && cargo test -p cyberos-mcp-gateway elicitation
cd services && cargo test -p cyberos-mcp-gateway --lib router::tests::elicitation_poll_respond_validate_and_404
cd services && cargo test -p cyberos-mcp-gateway --lib router::tests::destructive_
# Postgres-gated (ignored without DATABASE_URL / local pool):
cd services && cargo test -p cyberos-mcp-gateway --lib db_slice_test::elicitation_persists_seals_and_is_caller_scoped -- --ignored

Real tests to cite (do not cite non-existent elicitation_* integration filenames):

PathCovers
services/mcp-gateway/src/elicitation.rs unit tests listed in ACs 1–8Enums, schemas, store semantics
services/mcp-gateway/src/router.rs::elicitation_poll_respond_validate_and_404REST surface
services/mcp-gateway/src/router.rs destructive_* testsTASK-MCP-006 confirmation integration
services/mcp-gateway/src/db_slice_test.rs::elicitation_persists_seals_and_is_caller_scopedPG seal + caller scope + idempotency (#[ignore] without Postgres)
services/mcp-gateway/migrations/0016_mcp_elicitations.sqlSchema / enums (applied by migrate path)

AI Authorship Disclosure


batch/9a-mcp adopt — TASK-MCP-008 re-spec against as-built mcp-gateway elicitation.