* feat(mcp): speak spec revision 2026-07-28 (stateless core)
Adopt the 2026-07-28 MCP spec revision on the connector endpoint while
keeping every handshake-era client (2025-06-18 and earlier) byte-identical:
- Accept per-request _meta protocol negotiation
(io.modelcontextprotocol/protocolVersion); unsupported versions return
UnsupportedProtocolVersionError (-32022) with the supported list.
- Implement server/discover (spec MUST): supported revisions, capabilities
including the extensions field, identity, instructions, freshness hints.
- Decorate results for stateless clients: required resultType, serverInfo
in _meta, and CacheableResult ttlMs/cacheScope on tools/list,
prompts/list, resources/list, resources/read.
- Validate the standard Mcp-Method/Mcp-Name request headers when present
(HeaderMismatchError -32020); absence stays accepted.
- Declare the ratified MCP Apps extension (io.modelcontextprotocol/ui) in
capabilities; the widgets already use the ratified mime type and
_meta.ui.resourceUri shape, so no widget changes are needed.
- OAuth: include the RFC 9207 iss parameter on every authorization
response (success and error) and advertise
authorization_response_iss_parameter_supported in RFC 8414 metadata.
Resource-not-found already used -32602 and tools/list ordering was already
deterministic; both are covered by the new test file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp): Tasks extension (io.modelcontextprotocol/tasks)
Durable handles for long-running MCP tool calls, per the official Tasks
extension. A client that declares the extension in its per-request
capabilities gets a CreateTaskResult (resultType: "task") immediately;
the work completes after the response via after() and lands in the new
mcp_tasks table for tasks/get polling. Clients that did not declare the
extension are never handed a task (spec MUST).
- New mcp_tasks table (migration 20260729094000): company-scoped SELECT
RLS, service-role-only writes (mirrors pending_operations), 1-hour
expiry, status lifecycle CHECK. pg-real coverage included; triaged as
excluded in the full-archive backup contract (transient state).
- tasks/get (creator-scoped), tasks/cancel (cooperative, working-only
flip), tasks/update (ack no-op: no input_required flows yet).
- Tool opt-in via shouldRunAsTask predicate; first producer is
gnubok_audit_package, the one genuinely long-running blocking call
(multi-minute ZIP generation). estimate_only stays synchronous.
- Tool failures complete the task with the standard isError envelope,
exactly what the synchronous call would have returned; the failed
status stays reserved for infrastructure errors.
- server/discover and initialize now advertise the tasks extension.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(mcp): creator-only task RLS, enforced expiry sweep, RoPA entry
Compliance-swarm follow-ups on the mcp_tasks migration (editing the
migration is safe: it has not shipped beyond the ephemeral PR preview):
- SELECT RLS tightened from company-wide to auth.uid() = user_id so the
DB grant matches the creator-scoped tasks/get contract; task results
carry raw tool output (Art. 5(1)(c)). pg test now proves a same-company
colleague cannot read the row.
- The 1-hour retention is now enforced, not aspirational: createMcpTask
opportunistically deletes expired rows on every creation
(idx_mcp_tasks_expires), best-effort (Art. 5(1)(e)).
- RoPA entry mcp.async_task_handles added to .compliance/ropa.yaml
(Art. 30).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(mcp): literal terminal-update payload for the phantom-column guard
The conditional spreads in resolveMcpTask made the payload unresolvable
for the no-phantom-columns guard (362 > 360 ceiling). A literal payload
writing null for absent terminal fields is equivalent here: the terminal
transition sets the complete terminal state.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>