diff --git a/docs/architecture/jenkins-readonly-build-status-design.md b/docs/architecture/jenkins-readonly-build-status-design.md index bc5eff1..eef28ae 100644 --- a/docs/architecture/jenkins-readonly-build-status-design.md +++ b/docs/architecture/jenkins-readonly-build-status-design.md @@ -5,7 +5,7 @@ - **Related:** #77 (repo/branch/PR → job mapping, designed separately) - **Date:** 2026-07-02 -Note on naming: This design used historical `jenkins-readonly` skill name in Gitea-Tools. Actual package/server is `jenkins-mcp` (see mcp-control-plane registration in #55). The server boundary now contains gated trigger (see #56), but read tools remain as designed. +Note on naming: This design used historical `jenkins-readonly` skill name in Gitea-Tools. Actual package/server is `jenkins-mcp` (see mcp-control-plane registration in #55). The read server boundary remains read-only; gated triggers live on the separate `jenkins-write-mcp` / `jenkins_mcp.write_server` boundary (see #56 / #152). Client registration and reload instructions live in [`../mcp-client-registration.md`](../mcp-client-registration.md). @@ -18,7 +18,9 @@ detail (build URL, number, timing, result) to report or investigate. Phase 1 is **primarily read-only**, per ADR-0001 ([`adr-0001-mcp-control-plane-boundaries.md`](adr-0001-mcp-control-plane-boundaries.md)): -- Build triggers are gated behind dedicated profile + exact confirmation (landed in #4, boundary correction in #56). +- Build triggers are outside this read-only surface and require the separate + `jenkins-write-mcp` boundary, a dedicated profile, exact confirmation, and + fail-closed mutation audit (landed in #4, boundary correction in #56 / #152). - **Excluded: deploy triggers.** - **Excluded: parameterized job launches.** - Excluded: job creation/deletion/config changes, queue manipulation, node @@ -109,7 +111,7 @@ by #76): `forbidden_operations: ["jenkins.build.trigger", "jenkins.deploy", "jenkins.job.configure"]` as belt-and-braces even though no mutating tool exists. - Missing URL/user/token/profile ⇒ **fail closed** with a clear message. -- Since every tool is read-only, no confirmation gates are needed — but +- Since every tool on `jenkins-mcp` is read-only, no confirmation gates are needed — but identity (`jenkins_whoami`) must still work so workflows can prove which Jenkins account they act as. diff --git a/docs/mcp-client-registration.md b/docs/mcp-client-registration.md index 049e57b..e83d5f3 100644 --- a/docs/mcp-client-registration.md +++ b/docs/mcp-client-registration.md @@ -9,9 +9,14 @@ Use these exact MCP server names in clients: | Server name | Boundary | Default capability | |---|---|---| -| `jenkins-mcp` | Jenkins CI inspection | Read-only build/job inspection | +| `jenkins-mcp` | Jenkins CI inspection (read) | Read-only build/job inspection | +| `jenkins-write-mcp` | Jenkins build trigger (write) | Gated `jenkins_trigger_build` only | | `glitchtip-mcp` | GlitchTip observability inspection | Read-only issue/event inspection | +The write boundary (`jenkins-write-mcp`) is **not** registered by default (#152). +It exposes a single mutating tool and requires operator approval of a dedicated +trigger profile before any client config references it. + Historical names such as `jenkins-readonly` and `glitchtip-readonly` are descriptive profile labels only. They are not the canonical MCP server names unless an operator intentionally creates aliases and documents them. @@ -54,7 +59,7 @@ entry, reconnect or reload the MCP client before claiming the tools are usable. Before using either server in a task, prove the expected tools are visible in the client. It is not enough for the config entry to exist. -Expected Jenkins tools: +Expected Jenkins read tools (`jenkins-mcp` only): - `jenkins_whoami` - `jenkins_list_jobs` @@ -62,6 +67,10 @@ Expected Jenkins tools: - `jenkins_build_status` - `jenkins_get_build` +`jenkins_trigger_build` must **not** appear on `jenkins-mcp`. When an operator +explicitly enables the write boundary, the only expected tool on +`jenkins-write-mcp` is `jenkins_trigger_build`. + Expected GlitchTip tools: - `glitchtip_whoami` @@ -78,8 +87,13 @@ back to shell commands, raw service APIs, or unrelated MCP servers. ## Boundary Rules - `jenkins-mcp` read profiles must not expose build trigger tools. -- Jenkins build triggers require a separately named write profile, exact - confirmation, and fail-closed mutation audit. +- Build triggers live on the separate `jenkins-write-mcp` server + (`jenkins_mcp.write_server`), not on `jenkins-mcp`. +- Jenkins build triggers require a dedicated trigger profile with + `jenkins.build.trigger` allowed, exact confirmation + (`TRIGGER BUILD `), and fail-closed mutation audit. +- Do not register `jenkins-write-mcp` until an operator approves a trigger + profile; no shipped profile carries trigger capability by default. - `glitchtip-mcp` remains read-only. It must not file or mutate Gitea issues. - GlitchTip-to-Gitea filing is a separate orchestrator that composes GlitchTip read tools with Gitea issue-write tools. diff --git a/docs/safety-model.md b/docs/safety-model.md index fb9b92b..1f80b51 100644 --- a/docs/safety-model.md +++ b/docs/safety-model.md @@ -21,5 +21,10 @@ Note on naming: Historical design docs used `jenkins-readonly` / `glitchtip-read ## 5. Mutation Gating Any mutating action (e.g., Gitea issue creation from GlitchTip, or Jenkins builds) must be explicitly allowed by the execution profile. -- **Jenkins build triggers** exist in jenkins-mcp (landed #4) but require dedicated profile/identity and exact confirmation; not on standard reader profiles. See #56 for boundary correction. -- **GlitchTip to Gitea issue filing** is documented as a gated, orchestrated workflow (not in glitchtip-mcp), currently partial (mocked, dedup not wired, audit missing). See #57. +- **Jenkins build triggers** are gated on a separate write boundary + (`jenkins-write-mcp` / `jenkins_mcp.write_server`), not on the read-only + `jenkins-mcp` surface. Triggers require a dedicated profile with + `jenkins.build.trigger`, exact confirmation, and fail-closed mutation audit. + No default profile carries trigger capability (#152 / mcp-control-plane #56). +- **GlitchTip to Gitea issue filing** is a library-only orchestrator in + mcp-control-plane (not on `glitchtip-mcp`). See #153 / mcp-control-plane #57. diff --git a/tests/test_external_mcp_registration_docs.py b/tests/test_external_mcp_registration_docs.py index c14bc52..40c8eba 100644 --- a/tests/test_external_mcp_registration_docs.py +++ b/tests/test_external_mcp_registration_docs.py @@ -1,4 +1,4 @@ -"""Documentation checks for external Jenkins/GlitchTip MCP registration (#151).""" +"""Documentation checks for external Jenkins/GlitchTip MCP registration (#151/#152).""" from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent @@ -52,11 +52,20 @@ def test_registration_doc_preserves_boundaries(): "Do not add Jenkins or GlitchTip credentials to the Gitea MCP server", "do not add Gitea write credentials to the GlitchTip server", "must not expose build trigger tools", + "jenkins-write-mcp", + "jenkins_mcp.write_server", "remains read-only", ): assert phrase in text +def test_registration_doc_separates_jenkins_trigger_from_read_surface(): + text = _doc_text() + assert "jenkins_trigger_build" in text + assert "must **not** appear on `jenkins-mcp`" in text + assert "not" in text.lower() and "registered by default" in text.lower() + + def test_registration_doc_has_no_secret_material_or_live_urls(): text = _doc_text() for marker in ( @@ -80,3 +89,14 @@ def test_related_docs_link_registration_doc(): text = (REPO_ROOT / name).read_text(encoding="utf-8") assert "mcp-client-registration.md" in text, ( f"{name} does not link docs/mcp-client-registration.md") + + +def test_related_docs_keep_jenkins_trigger_off_read_surface(): + for name in ( + "docs/safety-model.md", + "docs/architecture/jenkins-readonly-build-status-design.md", + ): + text = (REPO_ROOT / name).read_text(encoding="utf-8") + assert "jenkins-write-mcp" in text + assert "jenkins_mcp.write_server" in text + assert "jenkins-mcp" in text