M6: implement ITWorx MCP Hub publication

Four read-only, service-token-protected MCP provider endpoints (operations summary, attention vehicles, vehicle details, knowledge search facade). Shared-secret auth reusing the M4 callback pattern. Service-request audit trail for every call. Extracted shared operations-summary logic out of the dashboard router to avoid duplicating retrieval logic. 66 backend tests passing, ruff clean. Verified all four endpoints and audit trail directly via curl against the live stack (no live MCP Hub instance available in this environment).
This commit is contained in:
NuklearRabbit
2026-08-01 23:05:04 +02:00
parent b511ba2dbc
commit c5b7e21f81
11 changed files with 368 additions and 29 deletions
+15 -3
View File
@@ -2,7 +2,7 @@
## Current milestone
M5 — complete. Starting M6 next.
M6 — complete. Starting M7 next.
## Locked decisions
@@ -108,10 +108,22 @@ M5 — complete. Starting M6 next.
- `npm run build` — clean.
- Full browser run of S6 end-to-end: asked "What must I do when a vehicle returns with damage?" on `/knowledge` → grounded answer citing "Vehicle return procedure" (2 sections) and "Damage handling procedure" with real excerpts. Also asked an unrelated question ("What is the weather forecast for tomorrow?") → correctly returned "Insufficient evidence" / "No matching procedure was found" with zero sources, confirming no fabrication.
### M6 — ITWorx MCP Hub publication
- `app/api/routers/mcp_integrations.py`: four read-only endpoints under `/api/v1/integrations/mcp/``GET operations-summary`, `GET attention-vehicles` (query params `minimum_severity`/`date`/`limit` matching `contracts/mcp-tools.json`'s `inputSchema` exactly), `GET vehicles/{vehicle_ref}`, `POST search-knowledge` (the "narrow façade" the doc calls for — wraps M5's `get_knowledge_provider()` rather than re-implementing retrieval; the contract's tool has no MobilityOps `endpoint` field, only `routing.preferred: ragcore`, so this façade path is MobilityOps's own addition for when the Hub needs a single provider boundary, not literally specified by the contract).
- Auth: new `require_mcp_service_token` dependency in `app/api/deps.py`, same shared-secret-header shape as the M4 n8n callback (`X-Service-Token` against `MCP_HUB_SERVICE_TOKEN`) plus an optional `X-Client-Id` header (defaults to `"unknown-mcp-client"`) used as the audit actor label — the Hub's actual client-identity header name is unknown (no live Hub to confirm against), so this is a reasonable guess documented here rather than assumed silently.
- `McpVehicleDetailOut` deliberately omits `registration_number` and all customer data — narrower than the browser-facing `VehicleOut`/`VehicleDetailOut`, matching "no customer or vehicle database access" and the read-only/summary intent of an AI-facing tool. Test `test_vehicle_details_known_ref` asserts the field's absence explicitly so a future change can't silently widen the exposed surface.
- Extracted `app/services/operations.py` (`compute_metrics`, `list_attention_vehicles`) out of `app/api/routers/dashboard.py` so the MCP operations-summary/attention-vehicles endpoints and the human dashboard share one query implementation instead of two copies that could drift — the same "do not duplicate retrieval logic" principle the doc states for the knowledge tool, applied here to the operational-summary tools too.
- Every provider call writes an `AuditEvent` (`actor_type="service"`, `actor_label=X-Client-Id`, `action="mcp_tool_request"`, `metadata={tool, status}`) — MobilityOps's own record that its provider APIs were reached, independent of whatever central tool-call audit the Hub itself keeps (per `docs/10-mcp-hub-integration.md`'s audit section, the Hub owns the central log; this is the local corroborating one).
- No write/mutation endpoints exist under the `/api/v1/integrations/mcp/` namespace at all (verified by `test_no_write_endpoints_exist_under_mcp_namespace` — POST/PUT/DELETE against the vehicle-details path all 404/405) — return registration, customer merge, and any booking/vehicle mutation are correctly absent, per the doc's explicit restriction list.
- Commands run and verified from this checkout:
- `docker compose run --rm api pytest -q`**66 passed** (new `tests/test_mcp_integrations.py`: token-required, wrong-token 401, all four tools' happy paths, severity/limit filtering, 404 for unknown vehicle, `max_sources` respected, audit actor/action verified, write-method rejection).
- `docker compose run --rm api ruff check .` — All checks passed.
- Live `curl` verification against the running stack (no browser needed — these are service-to-service endpoints, not UI): missing header → 422; wrong token → 401; correct token → all four endpoints return correct data (`operations-summary` metrics match the dashboard; `attention-vehicles?minimum_severity=high` returned `MO-016`×2 and `MO-031`, all severity `high`; `vehicles/MO-016` returned the narrow read-only shape; `search-knowledge` with `max_sources=2` returned exactly 2 grounded sources for the S6 question). Confirmed via `GET /api/v1/audit?action=mcp_tool_request` that all four calls were recorded with correct `actor_type=service`, tool name, and status.
## Known blockers
None. External service credentials may be absent; use the documented demo/degraded providers. The n8n workflow-activation steps from M4 are a one-time manual setup requirement in this environment, not a blocker — but not yet scripted; M7 should either automate it (e.g. a bootstrap script CI/compose can run) or document it clearly enough for `docs/14-testing-and-acceptance.md`'s clean-checkout criteria. RAGcore itself was never reachable this session — `RAGcoreKnowledgeProvider` is implemented and unit-tested for its unavailable-degradation path but its actual request/response contract against a real RAGcore instance is unverified; the demo provider is what M5's acceptance criteria are actually satisfied by.
None. External service credentials may be absent; use the documented demo/degraded providers. The n8n workflow-activation steps from M4 are a one-time manual setup requirement in this environment, not a blocker — but not yet scripted; M7 should either automate it (e.g. a bootstrap script CI/compose can run) or document it clearly enough for `docs/14-testing-and-acceptance.md`'s clean-checkout criteria. RAGcore and the ITWorx MCP Hub itself were never reachable this session — `RAGcoreKnowledgeProvider` and the M6 MCP provider endpoints are implemented and directly tested/curl-verified against MobilityOps's own API, but neither a real RAGcore instance nor a real Hub round trip was available to confirm end-to-end; M5's demo provider and M6's direct endpoint tests are what actually satisfy those milestones' acceptance criteria in this environment.
## Exact next action
Start M6 (ITWorx MCP Hub publication): read `docs/10-mcp-hub-integration.md`, `contracts/mcp-tools.json`. Implement the four read-only, service-token-protected provider endpoints under `/api/v1/integrations/mcp/` (`operations-summary`, `attention-vehicles`, `vehicles/{public_ref}`, and a knowledge-search façade wrapping the M5 `KnowledgeProvider` — per `contracts/mcp-tools.json`'s `mobilityops_search_knowledge` tool and `docs/10-mcp-hub-integration.md`'s note to route through a narrow façade rather than duplicate retrieval logic). Auth: reuse the same shared-secret-header pattern already built for the n8n callback in M4 (`X-Service-Token`, a new `MCP_HUB_SERVICE_TOKEN` setting — `.env.example` already has the env var name reserved) rather than inventing a second auth mechanism. Must not expose generic SQL, arbitrary fetch, write/mutation actions, or secrets — these are read-only summaries only. Add MobilityOps-side service-request audit events (the Hub owns its own central tool-call audit; MobilityOps only needs to record that its provider APIs were reached, with tool name/correlation ID/client identity/result status). No actual ITWorx MCP Hub instance is confirmed reachable in this environment (same situation as RAGcore in M5) — validate the four provider endpoints directly via authenticated `curl`/tests rather than a live Hub round trip, and note that gap explicitly rather than claiming an unverified integration works.
Start M7 (portfolio polish and final acceptance): read `docs/06-ui-ux.md`, `docs/14-testing-and-acceptance.md`, `docs/16-portfolio-case-study.md`. Work through `docs/14-testing-and-acceptance.md`'s clean-checkout acceptance list item by item from a genuinely fresh checkout (new clone, `cp .env.example .env`, documented bootstrap commands only) rather than this session's already-running/already-seeded stack — several manual one-time steps accumulated this session (n8n owner setup + workflow import/publish/restart, see M4 notes) that must either be scripted into the bootstrap or written up precisely in `docs/17-runbook.md`, since a true clean checkout won't have them done yet. Also needed: Playwright end-to-end test covering the full documented 5-minute demo script (login → dashboard → S1 return → S2 merge → S4 overlap already-visible → S6 knowledge question → audit → responsive nav at 360px — the ≤360px layout was flagged as visually unverified back in M1 and should finally be checked here), screenshots of the seven main pages, an architecture diagram, `artifacts/evidence/final-summary.md` per the doc's exact required contents (commit/tag, commands, test counts — currently 66 backend tests across M0M6 — screenshots, RAGcore success/unavailable evidence, n8n success/retry evidence, MCP sample calls, known limitations, truthful portfolio wording), and a final pass removing any rough edges/placeholders across the UI.