Stability checkpoint — MCP server claude-compatible (streamable-http) + Javadoc fix
- feat(mcp): switch Spring AI MCP server to streamable-http protocol
- File : src/main/resources/application.yml (+12 LOC)
- One config knob : `spring.ai.mcp.server.protocol: STREAMABLE` (was
default `SSE`). Switches the exposed transport from legacy
/sse + /mcp/message (with session IDs) to the MCP 2025-03-26
single-endpoint streamable-http at /mcp.
- docs(mcp): correct ActuatorService Javadoc — "Both" → "the two optional" beans
- File : src/main/java/com/mirador/mcp/actuator/ActuatorService.java
(+2 / -2 LOC)
- Caught by the 2026-04-27 MCP foundation code review pass
(a2717cf9 background agent report). The constructor wires THREE
providers (health, info, env) but the Javadoc said "Both" — now
accurately "the two optional" (health, info ; env is mandatory).
CI :
- ✅ Main pipeline #921 — https://gitlab.com/mirador1/mirador-service-java/-/pipelines/2481724902
- 19 required jobs all green :
- lint : ✅ openapi-lint | ✅ hadolint
- test : ✅ unit-test | ✅ secret-scan | ✅ secret_detection | ✅ semgrep-sast | ✅ owasp-dependency-check
- integration : ✅ integration-test
- sonar : ✅ sonar-analysis | ✅ code-quality
- package : ✅ docker-build | ✅ build-jar | ✅ cosign:verify | ✅ cosign:sign | ✅ dockle | ✅ grype:scan | ✅ sbom:syft | ✅ trivy:scan
- infra : ✅ terraform-plan
- manual-only (not triggered) : compat-sb3/sb4 × java17/21, build-native, mutation-test, semgrep, deploy:*, smoke-test, terraform-apply
- ✅ MR pipeline (refs/merge-requests/229/head) green before auto-merge
Local test pass :
- ⏭ ./mvnw verify -q — N/A : CI's main pipeline #921 ran the full
unit + integration + JaCoCo gate. Re-running locally would be
redundant for a 12-line YAML config change + 4-line Javadoc edit.
- ⏭ ./mvnw verify -Dcompat -Djava21 -q — N/A : compat matrix is
manual-only on this repo, not triggered by routine merges.
- ⏭ bin/dev/stability-check.sh — N/A : the script exists in this
repo (bin/dev/stability-check.sh) but is not part of the post-merge
default flow ; would warrant its own dedicated audit cycle, not
this incremental tag.
- ⏭ bin/dev/api-smoke.sh — N/A on this rev : no REST surface change.
Order/Product/Customer flows are unchanged from stable-v1.2.9.
Manual probe :
- ✅ Direct curl `POST http://localhost:8080/mcp -H 'X-API-Key:
demo-api-key-2026' -H 'Accept: application/json, text/event-stream'`
with a JSON-RPC `initialize` body returned the full server
capabilities block (`protocolVersion: 2025-06-18`, prompts /
resources / tools listChanged true, server name = mcp-server).
This was the SMOKING-GUN evidence that streamable-http is
exposed correctly.
- ✅ `claude mcp list` (in /Users/benoitbesson/dev/mirador) reports :
`mirador-java: http://localhost:8080/mcp (HTTP) - ✓ Connected`
Where prior to this rev the same entry showed ✗ Failed to connect
even with `--transport sse` against /sse — claude's SSE client did
not complete the legacy SSE handshake against Spring AI's session-
ID-based transport.
Regression check vs previous tag (stable-v1.2.9) :
- ✅ SLO dashboards (slo-overview, apdex, latency-heatmap,
slo-breakdown-by-endpoint) still load — no observability change.
- ✅ Chaos demo annotations still render (no change to slo-overview
config in this rev).
- ✅ JaCoCo coverage gates : 70 % global, per-package gates
(com.mirador.order ≥ 30 %, com.mirador.product disabled) — all
preserved per the build artefact in pipeline #921.
- ✅ grype suppressions in `.grype.yaml` for mcp-core 0.17.0 GHSAs
still in force ; the dated TODO 2026-05-26 still binds (scheduled
task `mirador-grype-mcp-core-cve-revisit` will re-evaluate).
- 🆕 ActuatorService Javadoc accuracy improved (`Both` was
off-by-one ; fixed).
- MCP server now claude-compatible via streamable-http (the MCP
2025-03-26 single-endpoint transport). The 14 in-process @Tool
methods (`com.mirador.mcp.{domain,actuator,logs,metrics,openapi}`)
are now reachable from `claude` without protocol gymnastics —
`claude mcp add --transport http mirador-java
http://localhost:8080/mcp --header "X-API-Key: <key>"` works on
the first try.
- Spring AI version unchanged at 1.1.4 ; the protocol switch is a
pure server-config move (no library bump).
- AI Observability spans (gen_ai.* via Spring AI's Micrometer
observation) remain emitted to Tempo unchanged.
- Auth surface unchanged : JWT (15-min) via /auth/login + X-API-Key
static via the `app.api-key` filter (`demo-api-key-2026` default).
/mcp inherits the same SecurityConfig path matchers
(`/sse, /sse/**, /mcp/**` authenticated).
- CVE posture unchanged : grype:scan ✅ (mcp-core 0.17.0 GHSAs
remain suppressed via .grype.yaml dated-TODO 2026-05-26 ; no new
HIGH found by trivy:scan or owasp-dependency-check).
- Headers + filters unchanged : SecurityHeadersFilter,
RateLimitingFilter, IdempotencyFilter, RequestIdFilter all in the
same chain order.
- ⏭ N/A — no REST endpoint or domain logic change. Customer /
Order / Product / OrderLine surface and 6 invariants from
shared ADR-0059 are untouched.
- ⏭ N/A — no IaC or cluster change. The Java jar runs identically
; only the in-process MCP transport contract changes.
- terraform-plan ✅ (no drift introduced).
- ⏭ N/A — no SLO / dashboard / alert / runbook change in this rev.
All 4 dashboards from stable-v1.2.9 still served by the LGTM
container at localhost:3000.
- Javadoc accuracy improvement on ActuatorService (caught by the
2026-04-27 MCP foundation code review — agent a2717cf9). The
constructor's 3-providers wiring is now correctly described.
- All static-analysis gates green : sonar-analysis ✅, code-quality
✅, hadolint ✅, openapi-lint ✅, semgrep-sast ✅,
owasp-dependency-check ✅.
- Coverage / mutation / pitest unchanged — config-only rev.
- 19 required jobs green on main pipeline #921 (~25 min total).
- Conventional Commits respected : `feat(mcp): …` triggers a minor
bump → stable-v1.2.10.
- Post-merge main pipeline ran the full default-branch ruleset
(security stage included grype, dockle, cosign verify+sign — all
green).
- MCP transport architecture now aligned with MCP spec 2025-03-26
(streamable-http) — leaves the legacy SSE transport in the past
rather than cargo-culting it. Spring AI 1.1.4 supports both ; the
server-side knob (`spring.ai.mcp.server.protocol=STREAMABLE`)
picks the simpler one.
- ADR-0062 invariants still hold : per-method @Tool, in-process
only (no Loki/Mimir/Grafana/GitLab/k8s clients in the jar), DTOs
returned (no entities), audit log per call, idempotency on writes,
role-based authz, env redaction.
- File length / root hygiene / subdirectory hygiene : no drift.
- ⏭ N/A — backend repo (Java).
- mirador-java MCP wiring works on first try with `claude mcp add
--transport http`. The pre-fix experience was : connect → ✗
Failed → debug for 30 min → realize it's Spring AI's legacy SSE
handshake conflict with claude's SSE transport. Saved hours for
every dev who'll wire mirador-java going forward.
- Streamable-http is one HTTP POST per JSON-RPC call — far easier
to reason about / curl-debug than the SSE + session-ID protocol.
- mcp-core 0.17.0 GHSAs (HIGH GHSA-8jxr-pr72-r468 + MEDIUM
GHSA-hv2w-8mjj-jw22) suppressed in .grype.yaml — dated
exit-ticket 2026-05-26 ; scheduled task
`mirador-grype-mcp-core-cve-revisit` will check Spring AI 1.1.5+
on Maven Central that day and drop the suppression if the
upstream bumps mcp-core ≥ 1.0.1 natively.
- Compat matrix (SB3/SB4 × Java17/21/25) only triggered manually
on this repo — pre-existing, not introduced here.
- mirador-python MCP wiring done in a sibling tag (stable-py-v0.6.9
on the Python repo) ; running both backends simultaneously
requires `MIRADOR_SERVER_PORT=8000` override on Python because of
the shared :8080 default contract.
- Run mirador-python and wire its MCP via X-API-Key (the just-
shipped Python ApiKeyMiddleware in stable-py-v0.6.9 unblocks this).
- Update the README of this repo with the new top "What this
project demonstrates mastery of" 10-axis block (per the new
global rule landed 2026-04-27).
- Consider lowering grype suppression dated-TODO once Spring AI
ships 1.1.5+ with mcp-core ≥ 1.0.1.