Document external MCP registration for Jenkins and GlitchTip
This commit is contained in:
@@ -172,6 +172,14 @@ Recognized environment fields (see [`.env.example`](.env.example) for placeholde
|
||||
| `GITEA_MCP_CONFIG` | Optional path to a JSON file defining multiple named runtime profiles. Unset ⇒ pure env behaviour. |
|
||||
| `GITEA_MCP_PROFILE` | Name of the profile (from `GITEA_MCP_CONFIG`) to activate for this runtime. |
|
||||
|
||||
#### External MCP Control Plane servers
|
||||
|
||||
Jenkins and GlitchTip are separate MCP trust boundaries, not tools inside this
|
||||
Gitea MCP runtime. Register them as `jenkins-mcp` and `glitchtip-mcp` in the
|
||||
client that will use them, then reconnect or reload the client and verify the
|
||||
expected tools are visible before claiming readiness. See
|
||||
[`docs/mcp-client-registration.md`](docs/mcp-client-registration.md).
|
||||
|
||||
Notes:
|
||||
|
||||
- This provides **one token + one profile per process**. It does not implement
|
||||
|
||||
@@ -22,13 +22,13 @@ Strictly read-only, per ADR-0001:
|
||||
tools — never one dual-credential server).
|
||||
- **This server never holds Gitea write credentials.**
|
||||
|
||||
## 2. Boundary placement (namespace pending)
|
||||
## 2. Boundary placement
|
||||
|
||||
These tools belong to the GlitchTip observability boundary of the MCP Control
|
||||
Plane — `glitchtip-mcp` (ADR-0001's recommendation), `observability-mcp`, or
|
||||
folded into `ops-mcp`. **ADR-0001 open owner decision #2 picks the name; this
|
||||
design does not assume it.** Tool names below use the `glitchtip_` prefix for
|
||||
readability and rename mechanically with the decision.
|
||||
Plane. The canonical MCP server name is `glitchtip-mcp`; client registration
|
||||
and reload instructions live in
|
||||
[`../mcp-client-registration.md`](../mcp-client-registration.md). Tool names
|
||||
below use the `glitchtip_` prefix.
|
||||
|
||||
Fixed regardless of the name (per `tool-boundaries.md`,
|
||||
`credential-isolation.md`):
|
||||
@@ -160,11 +160,13 @@ Mocked-GlitchTip unit tests only, per `docs/developer-testing-guidelines.md`:
|
||||
|
||||
## 10. Implementation-readiness checklist
|
||||
|
||||
Ready to implement once:
|
||||
Ready to operate once:
|
||||
|
||||
1. ADR-0001 owner decision #2 (namespace/placement) is made — mechanical
|
||||
rename of the `glitchtip_` prefix if needed.
|
||||
2. ADR-0001 owner decision #1 (repo home) is made.
|
||||
1. The MCP client registers the external server under the exact `glitchtip-mcp`
|
||||
name and is reconnected/reloaded.
|
||||
2. Tool discoverability proves the expected `glitchtip_*` read tools are
|
||||
visible; if the server is enabled but exposes no usable tools, report
|
||||
`SKIPPED` and stop.
|
||||
3. #76 profile schema exists (or a minimal `glitchtip-readonly` profile is
|
||||
hand-rolled to the same rules).
|
||||
4. A pinned GlitchTip version is chosen for API-subset testing (§3).
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
- **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.
|
||||
Client registration and reload instructions live in
|
||||
[`../mcp-client-registration.md`](../mcp-client-registration.md).
|
||||
|
||||
## 1. Purpose and scope
|
||||
|
||||
@@ -141,12 +143,16 @@ repo's conventions (`docs/developer-testing-guidelines.md`):
|
||||
|
||||
## 10. Implementation-readiness checklist
|
||||
|
||||
Ready to implement in `jenkins-mcp` once:
|
||||
Ready to operate through `jenkins-mcp` once:
|
||||
|
||||
1. ADR-0001 owner decision #1 (where `jenkins-mcp` lives) is made.
|
||||
2. #76 profile schema exists (or a minimal `jenkins-readonly` profile is
|
||||
1. The MCP client registers the external server under the exact `jenkins-mcp`
|
||||
name and is reconnected/reloaded.
|
||||
2. Tool discoverability proves the expected `jenkins_*` read tools are visible;
|
||||
if the server is enabled but exposes no usable tools, report `SKIPPED` and
|
||||
stop.
|
||||
3. #76 profile schema exists (or a minimal `jenkins-readonly` profile is
|
||||
hand-rolled to the same rules).
|
||||
3. #77 mapping design is accepted (or tools ship path-addressed only, mapping
|
||||
4. #77 mapping design is accepted (or tools ship path-addressed only, mapping
|
||||
deferred).
|
||||
|
||||
Explicitly **not** unlocked by this document: build triggers, deploys,
|
||||
|
||||
@@ -30,6 +30,11 @@ audit logging). See [Related documents](#related-documents).
|
||||
> the standard rules; operator prompts still control task-specific scope.
|
||||
> See issue #129 for the skill registry design.
|
||||
|
||||
Jenkins and GlitchTip workflows use separate MCP servers, not this Gitea MCP
|
||||
runtime. Register them as `jenkins-mcp` and `glitchtip-mcp`, reconnect or
|
||||
reload the client, and verify visible tools before claiming either integration
|
||||
is usable. See [`mcp-client-registration.md`](mcp-client-registration.md).
|
||||
|
||||
For cross-project use, copy the portable workflow skill at
|
||||
[`../skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md).
|
||||
It extracts the issue-first, isolated-worktree, no-self-review, profile-safety,
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# MCP Client Registration for External Control Plane Servers
|
||||
|
||||
Issue #151 fixes the registration and naming contract for the Jenkins and
|
||||
GlitchTip MCP servers that live outside this Gitea MCP runtime.
|
||||
|
||||
## Canonical Server Names
|
||||
|
||||
Use these exact MCP server names in clients:
|
||||
|
||||
| Server name | Boundary | Default capability |
|
||||
|---|---|---|
|
||||
| `jenkins-mcp` | Jenkins CI inspection | Read-only build/job inspection |
|
||||
| `glitchtip-mcp` | GlitchTip observability inspection | Read-only issue/event inspection |
|
||||
|
||||
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.
|
||||
|
||||
## Registration Pattern
|
||||
|
||||
Register each external server as its own MCP entry. Do not add Jenkins or
|
||||
GlitchTip credentials to the Gitea MCP server. Also, do not add Gitea write
|
||||
credentials to the GlitchTip server.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"jenkins-mcp": {
|
||||
"command": "/path/to/mcp-control-plane/venv/bin/python3",
|
||||
"args": ["-m", "jenkins_mcp"],
|
||||
"env": {
|
||||
"JENKINS_MCP_CONFIG": "/path/to/mcp-control-plane/profiles.json",
|
||||
"JENKINS_MCP_PROFILE": "jenkins-readonly"
|
||||
}
|
||||
},
|
||||
"glitchtip-mcp": {
|
||||
"command": "/path/to/mcp-control-plane/venv/bin/python3",
|
||||
"args": ["-m", "glitchtip_mcp"],
|
||||
"env": {
|
||||
"GLITCHTIP_MCP_CONFIG": "/path/to/mcp-control-plane/profiles.json",
|
||||
"GLITCHTIP_MCP_PROFILE": "glitchtip-readonly"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Client-specific wrappers may differ, but the server names, trust boundaries,
|
||||
and profile separation must remain the same. After adding or changing either
|
||||
entry, reconnect or reload the MCP client before claiming the tools are usable.
|
||||
|
||||
## Discoverability Validation
|
||||
|
||||
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:
|
||||
|
||||
- `jenkins_whoami`
|
||||
- `jenkins_list_jobs`
|
||||
- `jenkins_latest_build`
|
||||
- `jenkins_build_status`
|
||||
- `jenkins_get_build`
|
||||
|
||||
Expected GlitchTip tools:
|
||||
|
||||
- `glitchtip_whoami`
|
||||
- `glitchtip_list_projects`
|
||||
- `glitchtip_list_unresolved`
|
||||
- `glitchtip_get_issue`
|
||||
- `glitchtip_recent_events`
|
||||
- `glitchtip_search`
|
||||
|
||||
If a client reports the server as enabled but exposes no usable tools, report
|
||||
`SKIPPED: server enabled but no usable tools visible`, then stop. Do not fall
|
||||
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.
|
||||
- `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.
|
||||
- Gitea credentials never enter Jenkins or GlitchTip runtimes.
|
||||
- Jenkins and GlitchTip credentials never enter the Gitea MCP runtime.
|
||||
+46
-19
@@ -2947,9 +2947,9 @@ _COMMON_WORKFLOWS = [
|
||||
]
|
||||
|
||||
# Skill registry (#128). status: 'available' = backed by tools in this
|
||||
# server; 'designed-not-implemented' = design exists but no MCP tools yet
|
||||
# (listed rather than omitted so sessions know the boundary); 'operator-only'
|
||||
# = never performed by an LLM session.
|
||||
# server; 'external-mcp' = implemented in another MCP server that must be
|
||||
# registered separately; 'designed-not-implemented' = design exists but no MCP
|
||||
# tools yet; 'operator-only' = never performed by an LLM session.
|
||||
_PROJECT_SKILLS = {
|
||||
"gitea-issue-authoring": {
|
||||
"description": "Create, view, label, claim, and close Gitea issues.",
|
||||
@@ -3115,28 +3115,55 @@ _PROJECT_SKILLS = {
|
||||
"jenkins-mcp": {
|
||||
"description": "Read-only Jenkins CI inspection (jobs, builds, "
|
||||
"logs).",
|
||||
"when_to_use": "Checking CI state once Jenkins MCP tools exist.",
|
||||
"when_to_use": "Checking CI state through a separately registered "
|
||||
"jenkins-mcp server.",
|
||||
"required_operations": ["jenkins.read"],
|
||||
"status": "designed-not-implemented",
|
||||
"notes": "Server code exists in mcp-control-plane as jenkins-mcp (read tools + gated trigger); registration pending. To register for discoverability in clients (Codex/Gemini/Grok/etc.): add to client MCP config under the jenkins-mcp name, reconnect/reload the client session after registration. Report SKIPPED if not connected. Do not substitute shell/API. Trigger requires dedicated profile (see #56).",
|
||||
"status": "external-mcp",
|
||||
"notes": "Server code lives in mcp-control-plane as the separate "
|
||||
"jenkins-mcp server. Register that server under the exact "
|
||||
"jenkins-mcp name in the MCP client, reconnect/reload the "
|
||||
"client, then verify Jenkins tools are visible before use. "
|
||||
"Report SKIPPED if the server is absent or exposes no usable "
|
||||
"tools. Do not substitute shell/API. Trigger requires a "
|
||||
"dedicated write profile and confirmation (see #56).",
|
||||
"steps": [
|
||||
"Confirm a Jenkins MCP server is connected (jenkins-mcp); if not, report "
|
||||
"SKIPPED.",
|
||||
"Use read-only operations only; never trigger unless using dedicated profile + confirmation.",
|
||||
"Confirm a Jenkins MCP server is connected under the exact "
|
||||
"jenkins-mcp name.",
|
||||
"Verify tool discoverability: jenkins_whoami, "
|
||||
"jenkins_list_jobs, jenkins_latest_build, jenkins_build_status, "
|
||||
"and jenkins_get_build must be visible before claiming Jenkins "
|
||||
"readiness.",
|
||||
"If the server is enabled but exposes no usable tools, report "
|
||||
"SKIPPED and stop.",
|
||||
"Use read-only operations only; never trigger unless using a "
|
||||
"dedicated write profile plus exact confirmation.",
|
||||
],
|
||||
},
|
||||
"glitchtip-mcp": {
|
||||
"description": "Read-only GlitchTip error/event inspection.",
|
||||
"when_to_use": "Investigating reported errors once GlitchTip MCP "
|
||||
"tools exist.",
|
||||
"when_to_use": "Investigating reported errors through a separately "
|
||||
"registered glitchtip-mcp server.",
|
||||
"required_operations": ["glitchtip.read"],
|
||||
"status": "designed-not-implemented",
|
||||
"notes": "Server code exists in mcp-control-plane as glitchtip-mcp (read-only tools); registration pending. To register for discoverability in clients (Codex/Gemini/Grok/etc.): add to client MCP config under the glitchtip-mcp name, reconnect/reload the client session after registration. Filing orchestrator is partial in mcp-control-plane (see #57). Report SKIPPED if not connected. Filing to Gitea is separate orchestrator, not in this server.",
|
||||
"status": "external-mcp",
|
||||
"notes": "Server code lives in mcp-control-plane as the separate "
|
||||
"glitchtip-mcp server. Register that server under the exact "
|
||||
"glitchtip-mcp name in the MCP client, reconnect/reload the "
|
||||
"client, then verify GlitchTip tools are visible before use. "
|
||||
"Report SKIPPED if the server is absent or exposes no usable "
|
||||
"tools. Filing to Gitea is a separate orchestrator tracked "
|
||||
"outside this server (see #57).",
|
||||
"steps": [
|
||||
"Confirm a GlitchTip MCP server is connected (glitchtip-mcp); if not, report "
|
||||
"SKIPPED.",
|
||||
"Use read-only operations only; never mutate issues or "
|
||||
"settings. Filing tracked in #57.",
|
||||
"Confirm a GlitchTip MCP server is connected under the exact "
|
||||
"glitchtip-mcp name.",
|
||||
"Verify tool discoverability: glitchtip_whoami, "
|
||||
"glitchtip_list_projects, glitchtip_list_unresolved, "
|
||||
"glitchtip_get_issue, glitchtip_recent_events, and "
|
||||
"glitchtip_search must be visible before claiming GlitchTip "
|
||||
"readiness.",
|
||||
"If the server is enabled but exposes no usable tools, report "
|
||||
"SKIPPED and stop.",
|
||||
"Use read-only operations only; never mutate issues or settings. "
|
||||
"Filing is a separate orchestrator tracked in #57.",
|
||||
],
|
||||
},
|
||||
"release-operator": {
|
||||
@@ -3271,8 +3298,8 @@ def mcp_list_project_skills() -> dict:
|
||||
|
||||
Read-only; makes no API calls. Each skill reports its name,
|
||||
description, when-to-use guidance, required operations, status
|
||||
('available', 'designed-not-implemented', or 'operator-only'), and
|
||||
whether the current profile's operations permit it. Use
|
||||
('available', 'external-mcp', 'designed-not-implemented', or
|
||||
'operator-only'), and whether the current profile's operations permit it. Use
|
||||
mcp_get_skill_guide(name) for the step-by-step guide.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Documentation checks for external Jenkins/GlitchTip MCP registration (#151)."""
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
DOC = REPO_ROOT / "docs" / "mcp-client-registration.md"
|
||||
|
||||
|
||||
def _doc_text():
|
||||
assert DOC.is_file(), "missing docs/mcp-client-registration.md"
|
||||
return DOC.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_registration_doc_names_canonical_servers():
|
||||
text = _doc_text()
|
||||
assert "jenkins-mcp" in text
|
||||
assert "glitchtip-mcp" in text
|
||||
assert "Historical names" in text
|
||||
assert "jenkins-readonly" in text
|
||||
assert "glitchtip-readonly" in text
|
||||
|
||||
|
||||
def test_registration_doc_lists_expected_tool_visibility():
|
||||
text = _doc_text()
|
||||
for tool in (
|
||||
"jenkins_whoami",
|
||||
"jenkins_list_jobs",
|
||||
"jenkins_latest_build",
|
||||
"jenkins_build_status",
|
||||
"jenkins_get_build",
|
||||
"glitchtip_whoami",
|
||||
"glitchtip_list_projects",
|
||||
"glitchtip_list_unresolved",
|
||||
"glitchtip_get_issue",
|
||||
"glitchtip_recent_events",
|
||||
"glitchtip_search",
|
||||
):
|
||||
assert tool in text
|
||||
|
||||
|
||||
def test_registration_doc_requires_reload_and_negative_case():
|
||||
text = _doc_text()
|
||||
lower = text.lower()
|
||||
assert "reconnect" in lower
|
||||
assert "reload" in lower
|
||||
assert "enabled but no usable tools" in lower
|
||||
assert "SKIPPED" in text
|
||||
|
||||
|
||||
def test_registration_doc_preserves_boundaries():
|
||||
text = " ".join(_doc_text().split())
|
||||
for phrase in (
|
||||
"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",
|
||||
"remains read-only",
|
||||
):
|
||||
assert phrase in text
|
||||
|
||||
|
||||
def test_registration_doc_has_no_secret_material_or_live_urls():
|
||||
text = _doc_text()
|
||||
for marker in (
|
||||
"https://",
|
||||
"http://",
|
||||
"Authorization:",
|
||||
"BEGIN PRIVATE KEY",
|
||||
"ghp_",
|
||||
"token-value",
|
||||
):
|
||||
assert marker not in text
|
||||
|
||||
|
||||
def test_related_docs_link_registration_doc():
|
||||
for name in (
|
||||
"README.md",
|
||||
"docs/llm-workflow-runbooks.md",
|
||||
"docs/architecture/jenkins-readonly-build-status-design.md",
|
||||
"docs/architecture/glitchtip-readonly-tools-design.md",
|
||||
):
|
||||
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")
|
||||
@@ -234,8 +234,8 @@ class TestProjectSkills(GuideTestBase):
|
||||
with patch.dict(os.environ, AUTHOR_ENV, clear=True):
|
||||
r = mcp_list_project_skills()
|
||||
by_name = {s["name"]: s for s in r["skills"]}
|
||||
self.assertNotEqual(by_name["jenkins-mcp"]["status"], "available")
|
||||
self.assertNotEqual(by_name["glitchtip-mcp"]["status"], "available")
|
||||
self.assertEqual(by_name["jenkins-mcp"]["status"], "external-mcp")
|
||||
self.assertEqual(by_name["glitchtip-mcp"]["status"], "external-mcp")
|
||||
|
||||
def test_no_urls_in_registry(self):
|
||||
with patch.dict(os.environ, AUTHOR_ENV, clear=True):
|
||||
@@ -246,16 +246,29 @@ class TestProjectSkills(GuideTestBase):
|
||||
self.assertNotIn("keychain:", blob)
|
||||
|
||||
def test_enabled_but_no_usable_tools_negative_assertion(self):
|
||||
"""Negative assertion for 'enabled but no usable tools' (per issue #146)."""
|
||||
"""Negative assertion for 'enabled but no usable tools' (per issue #151)."""
|
||||
with patch.dict(os.environ, AUTHOR_ENV, clear=True):
|
||||
r = mcp_list_project_skills()
|
||||
by_name = {s["name"]: s for s in r["skills"]}
|
||||
# jenkins-mcp is designed-not-implemented; even if "enabled" in config,
|
||||
# it should not be usable/available to current profile without tools.
|
||||
# jenkins-mcp is an external boundary; Gitea profile discovery must not
|
||||
# treat a local config entry as a usable Jenkins tool surface.
|
||||
self.assertIn("jenkins-mcp", by_name)
|
||||
self.assertEqual(by_name["jenkins-mcp"]["status"], "designed-not-implemented")
|
||||
self.assertEqual(by_name["jenkins-mcp"]["status"], "external-mcp")
|
||||
self.assertFalse(by_name["jenkins-mcp"].get("available_to_current_profile", False))
|
||||
|
||||
def test_external_mcp_skill_guides_name_expected_tools(self):
|
||||
with patch.dict(os.environ, AUTHOR_ENV, clear=True):
|
||||
jenkins = mcp_get_skill_guide("jenkins-mcp")
|
||||
glitchtip = mcp_get_skill_guide("glitchtip-mcp")
|
||||
self.assertEqual(jenkins["skill"]["status"], "external-mcp")
|
||||
self.assertIn("jenkins_whoami", " ".join(jenkins["steps"]))
|
||||
self.assertIn("jenkins_get_build", " ".join(jenkins["steps"]))
|
||||
self.assertIn("SKIPPED", " ".join(jenkins["steps"]))
|
||||
self.assertEqual(glitchtip["skill"]["status"], "external-mcp")
|
||||
self.assertIn("glitchtip_whoami", " ".join(glitchtip["steps"]))
|
||||
self.assertIn("glitchtip_search", " ".join(glitchtip["steps"]))
|
||||
self.assertIn("SKIPPED", " ".join(glitchtip["steps"]))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mcp_get_skill_guide
|
||||
|
||||
Reference in New Issue
Block a user