Skip to content

Commit 9db8d23

Browse files
committed
Couchbase Admin MCP Server
1 parent c9f6191 commit 9db8d23

143 files changed

Lines changed: 10642 additions & 1578 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

100755100644
File mode changed.

.env.example

100755100644
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ CB_PASSWORD=change-me
2727
# opt IN to writes explicitly. This is the outermost safety layer.
2828
CB_ADMIN_READ_ONLY_MODE=true
2929

30+
# ── Dry run / preview mode ───────────────────────────────────────────────────
31+
# DEFAULT: false. When true, every WRITE is authorized, audited and then NOT
32+
# performed: the response says which tool would have run, against which target,
33+
# with which arguments. Reads still execute, because a read changes nothing and
34+
# a plan is worth nothing without one.
35+
#
36+
# A caller may also pass `dry_run: true` with any write tool's arguments. This
37+
# variable OVERRIDES that: `dry_run: false` cannot escape preview mode, because a
38+
# control a caller can switch off is not a control.
39+
#
40+
# Use it for the first run of an unattended pipeline against a new organization,
41+
# and keep the output as the artifact you attach to the change request. Audit
42+
# records the decision as `dry_run`, not `allowed`, so previews do not inflate
43+
# your count of privileged writes.
44+
# CB_ADMIN_DRY_RUN=false
45+
3046
# ─────────────────────────────────────────────────────────────────────────────
3147
# CONFIRMATION AND AUTOMATION
3248
#

.github/workflows/ci.yml

100755100644
Lines changed: 187 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
name: CI
2-
32
# Nothing ran automatically before this. 641 tests and 45 mutation entries existed and
43
# were only ever verified by hand, which makes them a snapshot rather than a guarantee.
54
on:
65
push:
76
branches: [main]
87
pull_request:
98
workflow_dispatch:
10-
119
concurrency:
1210
group: ${{ github.workflow }}-${{ github.ref }}
1311
cancel-in-progress: true
14-
1512
permissions:
1613
contents: read
17-
1814
jobs:
1915
lint:
2016
runs-on: ubuntu-latest
@@ -28,7 +24,6 @@ jobs:
2824
run: uv run ruff check .
2925
- name: ruff format
3026
run: uv run ruff format --check .
31-
3227
test:
3328
runs-on: ubuntu-latest
3429
strategy:
@@ -37,7 +32,8 @@ jobs:
3732
# The floor and the current release. requires-python is >=3.10,<3.15, and the
3833
# floor matters: this code uses `X | None` annotations behind
3934
# `from __future__ import annotations`, which is easy to break on 3.10 only.
40-
python-version: ["3.10", "3.13"]
35+
# 3.14 is included because requires-python claims <3.15 and it was never built.
36+
python-version: ["3.10", "3.13", "3.14"]
4137
steps:
4238
- uses: actions/checkout@v4
4339
- uses: astral-sh/setup-uv@v5
@@ -53,13 +49,34 @@ jobs:
5349
- uses: actions/setup-node@v4
5450
with:
5551
node-version: "20"
52+
# Node alone was NOT enough. The render assertions sat behind
53+
# `try { require("react") } catch {}` with NODE_PATH=/tmp/node_modules and nothing
54+
# ever installing react, so they were skipped everywhere including here — the exact
55+
# "skipped frontend test" the comment above says this job exists to prevent. The
56+
# test now FAILS rather than skips when CI is set, so this step is load-bearing.
57+
- name: Install React for the console render tests
58+
run: npm install --prefix /tmp react react-dom
5659
- name: Tests, in randomised order
57-
run: uv run pytest -q
60+
# pytest-randomly is declared in the [dev] extra. It was NOT installed before, so
61+
# this step was byte-identical to the one below and the order-dependence check the
62+
# repo says it relies on did not exist. The import assertion makes a silent
63+
# regression impossible — a missing plugin fails here rather than quietly
64+
# degrading to file order. It found a real defect on its first real run: a
65+
# sys.modules.pop that disabled six tests, two of them enterprise-profile
66+
# security refusals.
67+
run: |
68+
uv run python -c "import pytest_randomly"
69+
uv run pytest -q
5870
- name: Tests, in file order
5971
# Both orders, because several modules read environment variables at import time
6072
# and order-dependent tests have hidden real bugs here before.
6173
run: uv run pytest -q -p no:randomly
62-
74+
# The [tool.coverage] block and tests/test_coverage_config.py constrained a
75+
# measurement nothing performed: no job ran --cov and there was no fail_under, so
76+
# the exclusions were pinned while the number they shape was never produced or
77+
# checked. fail_under = 89 is now set in pyproject.toml; measured is 90.5%.
78+
- name: Coverage, gated
79+
run: uv run pytest -q -p no:randomly --cov --cov-report=term-missing
6380
mutation:
6481
# The tests that prove the tests. A control whose mutation SURVIVES has no test
6582
# behind it, however green the suite looks.
@@ -83,7 +100,6 @@ jobs:
83100
# back to short-circuiting the SQL++ handlers, the routing checks for three
84101
# modules silently stop proving anything, and that is only detectable this way.
85102
run: uv run python scripts/mutation_round_5.py
86-
87103
package:
88104
# The image and the wheel both listed their top-level modules by hand and both once
89105
# omitted three, so `import server` failed at startup with none of the security
@@ -107,7 +123,165 @@ jobs:
107123
docker build -t couchbase-admin-mcp:ci .
108124
docker run --rm -e CB_ADMIN_PROFILE=workstation couchbase-admin-mcp:ci \
109125
python -c "import server; from gui import gui_server; print(f'{len(server._TOOLS)} tools')"
126+
capella-paths:
127+
# Verifies the Capella v4 paths against a live organization. Skipped unless the
128+
# secret is configured, because a fork's PR cannot have it — and a job that fails for
129+
# everyone without credentials gets disabled, taking the check with it.
130+
#
131+
# Read-only: GETs run for real, writes are probed with OPTIONS.
132+
#
133+
# The script's exit status is now honest, which it was not. It exited 0 whenever
134+
# nothing was MISSING, so ERROR and SKIPPED — the two states that mean "nothing was
135+
# checked" — read as a clean pass: a dead endpoint gave {'SKIPPED': 56, 'ERROR': 5}
136+
# and exit 0, and a key with no project access gave 5 "VERIFIED" that were verified
137+
# by a 401. It now exits non-zero when any path ERRORs or when zero were VERIFIED,
138+
# and 401 (which Capella returns BEFORE routing) and 429 no longer count as proof a
139+
# route exists.
140+
runs-on: ubuntu-latest
141+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
142+
steps:
143+
- uses: actions/checkout@v4
144+
- uses: astral-sh/setup-uv@v5
145+
with:
146+
enable-cache: true
147+
- run: uv sync --extra dev
148+
- name: Verify v4 paths
149+
env:
150+
CB_CAPELLA_API_KEY: ${{ secrets.CB_CAPELLA_API_KEY }}
151+
run: |
152+
if [ -z "$CB_CAPELLA_API_KEY" ]; then
153+
echo "::warning title=Capella paths not verified::CB_CAPELLA_API_KEY is not configured, so no v4 path was checked. This job is green because it did not run, not because it passed."
154+
{
155+
echo "### Capella path verification SKIPPED"
156+
echo ""
157+
echo "\`CB_CAPELLA_API_KEY\` is not configured, so **no path was verified**."
158+
echo "Set it to a read-only Capella API key SECRET to enable this job."
159+
} >> "$GITHUB_STEP_SUMMARY"
160+
exit 0
161+
fi
162+
uv run python scripts/verify_capella_paths.py
163+
What I changed and why
164+
165+
1. React install in the test job. This one would have broken your first CI run, not just left a gap. I made test_gui_frontend.py fail instead of skip when CI is set — and GitHub Actions sets CI=true. Without this step, that test now fails in CI. The step is what makes the change safe.
166+
167+
2. import pytest_randomly assertion. The plugin is now in [dev], so uv sync installs it and your existing "randomised order" step becomes real. The one-line import check means a future dependency change fails loudly rather than silently reverting the step to file order — which is how it came to be a no-op in the first place.
168+
169+
3. Coverage gate step. fail_under = 89 is in pyproject.toml; measured 90.5%.
170+
171+
4. capella-paths — visible skip, still exit 0. I kept your reasoning: your comment is right that a job failing for everyone without credentials gets disabled. So the skip stays, but it now emits a ::warning and a job-summary line, so a green tick can't be mistaken for a verified one. Note your if: already excludes pull_request, so the fork case is handled by that condition — if you'd rather a missing secret fail on push to main, change exit 0 to exit 1; that's a one-word edit and I've deliberately left the decision with you.
110172

173+
Two things I did not change: the mutation job needs no React (I checked — no mutation round runs test_gui_frontend.py), and I left the job order and permissions: contents: read alone.
174+
175+
Separately, while checking that: mutation_round_4.py and mutation_round_5.py had the same timeout-as-caught bug I'd only fixed in rounds_1_3 — a pytest timeout returned "caught" with no assertion having failed, in the one tool whose job is to say which controls are untested. Both now raise. Fixed, lint clean, 3061 tests still passing, and both files are on your disk.
176+
177+
Mutation round 4
178+
PY 
179+
Mutation round 5
180+
PY 
181+
182+
name: CI
183+
# Nothing ran automatically before this. 641 tests and 45 mutation entries existed and
184+
# were only ever verified by hand, which makes them a snapshot rather than a guarantee.
185+
on:
186+
push:
187+
branches: [main]
188+
pull_request:
189+
workflow_dispatch:
190+
concurrency:
191+
group: ${{ github.workflow }}-${{ github.ref }}
192+
cancel-in-progress: true
193+
permissions:
194+
contents: read
195+
jobs:
196+
lint:
197+
runs-on: ubuntu-latest
198+
steps:
199+
- uses: actions/checkout@v4
200+
- uses: astral-sh/setup-uv@v5
201+
with:
202+
enable-cache: true
203+
- run: uv sync --extra dev
204+
- name: ruff check
205+
run: uv run ruff check .
206+
- name: ruff format
207+
run: uv run ruff format --check .
208+
test:
209+
runs-on: ubuntu-latest
210+
strategy:
211+
fail-fast: false
212+
matrix:
213+
# The floor and the current release. requires-python is >=3.10,<3.15, and the
214+
# floor matters: this code uses X | None annotations behind
215+
# from __future__ import annotations, which is easy to break on 3.10 only.
216+
# 3.14 is included because requires-python claims <3.15 and it was never built.
217+
python-version: ["3.10", "3.13", "3.14"]
218+
steps:
219+
- uses: actions/checkout@v4
220+
- uses: astral-sh/setup-uv@v5
221+
with:
222+
enable-cache: true
223+
- uses: actions/setup-python@v5
224+
with:
225+
python-version: ${{ matrix.python-version }}
226+
- run: uv sync --extra dev
227+
# Node is only needed by the console's compile-and-render tests. They skip cleanly
228+
# without it, and a skipped frontend test is how the console came to render a blank
229+
# page unnoticed — so CI installs it.
230+
- uses: actions/setup-node@v4
231+
with:
232+
node-version: "20"
233+
- name: Tests, in randomised order
234+
run: uv run pytest -q
235+
- name: Tests, in file order
236+
# Both orders, because several modules read environment variables at import time
237+
# and order-dependent tests have hidden real bugs here before.
238+
run: uv run pytest -q -p no:randomly
239+
mutation:
240+
# The tests that prove the tests. A control whose mutation SURVIVES has no test
241+
# behind it, however green the suite looks.
242+
runs-on: ubuntu-latest
243+
steps:
244+
- uses: actions/checkout@v4
245+
- uses: astral-sh/setup-uv@v5
246+
with:
247+
enable-cache: true
248+
- run: uv sync --extra dev
249+
- uses: actions/setup-node@v4
250+
with:
251+
node-version: "20"
252+
- name: Mutations, rounds 1-3
253+
run: uv run python scripts/mutation_rounds_1_3.py
254+
- name: Mutations, round 4
255+
run: uv run python scripts/mutation_round_4.py
256+
- name: Mutations, round 5
257+
# The session-cookie and mcp-version-compat guards, plus the cross-handler
258+
# contract harness. Round 5 also mutates a TEST fixture: if the fixture goes
259+
# back to short-circuiting the SQL++ handlers, the routing checks for three
260+
# modules silently stop proving anything, and that is only detectable this way.
261+
run: uv run python scripts/mutation_round_5.py
262+
package:
263+
# The image and the wheel both listed their top-level modules by hand and both once
264+
# omitted three, so import server failed at startup with none of the security
265+
# controls present. A green test suite said nothing, because it runs from the source
266+
# tree where every file exists.
267+
runs-on: ubuntu-latest
268+
steps:
269+
- uses: actions/checkout@v4
270+
- uses: astral-sh/setup-uv@v5
271+
with:
272+
enable-cache: true
273+
- run: uv sync --extra dev
274+
- name: Build the wheel and import from the INSTALLED copy
275+
run: |
276+
uv build --wheel
277+
python -m venv /tmp/fresh
278+
/tmp/fresh/bin/pip install --quiet dist/*.whl
279+
CB_ADMIN_PROFILE=workstation /tmp/fresh/bin/python -c "import server; print(f'{len(server._TOOLS)} tools')"
280+
- name: Build the image and import from inside it
281+
run: |
282+
docker build -t couchbase-admin-mcp:ci .
283+
docker run --rm -e CB_ADMIN_PROFILE=workstation couchbase-admin-mcp:ci \
284+
python -c "import server; from gui import gui_server; print(f'{len(server._TOOLS)} tools')"
111285
capella-paths:
112286
# Verifies the Capella v4 paths against a live organization. Skipped unless the
113287
# secret is configured, because a fork's PR cannot have it — and a job that fails for
@@ -133,3 +307,7 @@ jobs:
133307
exit 0
134308
fi
135309
uv run python scripts/verify_capella_paths.py
310+
311+
this is my ci.yml file.... edit and give me back what it needs to change to
312+
313+
Working on it… · 50s

0 commit comments

Comments
 (0)