feat(mcp): add yt mcp allowlist to manage the serve allowlist without SQL #104

Merged
David merged 3 commits from feat/YT-47-mcp-allowlist-cli into main 2026-07-05 21:53:35 +02:00
Owner

What

Implements YT-47: a first-class yt mcp allowlist command group so operators never hand-write SQL to manage the yt mcp serve login allowlist. This is the recovery / bootstrap path when the browser admin UI at /admin is unreachable (no admin logged in yet).

yt mcp allowlist add <login-or-email>
yt mcp allowlist list
yt mcp allowlist remove <login-or-email>

Design

  • New youtrack_mcp::allowlist module: thin async wrappers (add / remove / list / format_timestamp) over the crate's db layer, each opening the pool at the given URL and delegating. yt-cli calls these and gains no sqlx dependency (tooling-gap rule).
  • remove returns whether an entry existed, so the CLI can print a real-removal vs a no-op message.
  • DB path resolves via --database-url (global, works before or after the subcommand), then $YOUTRACK_MCP_DATABASE_URL, then the installer default. That default path string is now single-sourced in commands/mcp.rs::default_database_url, shared with service install (refactored resolve_database_url to call it) so the two cannot drift.
  • add is idempotent (upsert), list prints each entry with a human YYYY-MM-DD HH:MM UTC timestamp and added_by (cli:<user> for CLI adds, distinct from the admin-UI login label).
  • docs/deployment.md gains a "Managing the login allowlist" section: edits the SQLite file directly, must run where the DB lives, container caveat (docker compose exec ... or point --database-url at the volume host path).

Invariant sweep (default DB path single-sourced; no sqlx in yt-cli)

Site Classification
mcp.rs::default_database_url source of truth for the default path
service.rs::resolve_database_url fixed: now calls super::default_database_url (was an inline literal)
mcp.rs::resolve_state_db_url new: flag -> env -> default, used by allowlist
Remaining path literals N/A: doc comments and test-expectation assertions, not logic
sqlx / SqlitePool in yt-cli none: only the word "sqlx" in doc comments; all DB work goes through youtrack_mcp::allowlist

Verification

  • New unit test add_list_remove_roundtrip_on_a_temp_db (idempotent add, presence/no-op remove, empty list) and format_timestamp_is_human_readable_utc.
  • Drove the built binary end-to-end against a temp DB: empty list, idempotent add, list with timestamps, remove (present -> no-op), and --database-url both before and after the subcommand. All correct.
  • Full just pre-commit green (fmt, clippy -D warnings, build, 84 tests).
  • YT-46 (first-admin bootstrap, #103): with that fix you often will not need this, but the CLI remains the general allowlist-management tool.
  • YT-45: docs rewrite will fold in the new section.
## What Implements YT-47: a first-class `yt mcp allowlist` command group so operators never hand-write SQL to manage the `yt mcp serve` login allowlist. This is the recovery / bootstrap path when the browser admin UI at `/admin` is unreachable (no admin logged in yet). ```nushell yt mcp allowlist add <login-or-email> yt mcp allowlist list yt mcp allowlist remove <login-or-email> ``` ## Design - New `youtrack_mcp::allowlist` module: thin async wrappers (`add` / `remove` / `list` / `format_timestamp`) over the crate's `db` layer, each opening the pool at the given URL and delegating. yt-cli calls these and gains **no** sqlx dependency (tooling-gap rule). - `remove` returns whether an entry existed, so the CLI can print a real-removal vs a no-op message. - DB path resolves via `--database-url` (global, works before or after the subcommand), then `$YOUTRACK_MCP_DATABASE_URL`, then the installer default. That default path string is now single-sourced in `commands/mcp.rs::default_database_url`, shared with `service install` (refactored `resolve_database_url` to call it) so the two cannot drift. - `add` is idempotent (upsert), `list` prints each entry with a human `YYYY-MM-DD HH:MM UTC` timestamp and `added_by` (`cli:<user>` for CLI adds, distinct from the admin-UI login label). - docs/deployment.md gains a "Managing the login allowlist" section: edits the SQLite file directly, must run where the DB lives, container caveat (`docker compose exec ...` or point `--database-url` at the volume host path). ## Invariant sweep (default DB path single-sourced; no sqlx in yt-cli) | Site | Classification | | --- | --- | | `mcp.rs::default_database_url` | source of truth for the default path | | `service.rs::resolve_database_url` | fixed: now calls `super::default_database_url` (was an inline literal) | | `mcp.rs::resolve_state_db_url` | new: flag -> env -> default, used by `allowlist` | | Remaining path literals | N/A: doc comments and test-expectation assertions, not logic | | sqlx / SqlitePool in yt-cli | none: only the word "sqlx" in doc comments; all DB work goes through `youtrack_mcp::allowlist` | ## Verification - New unit test `add_list_remove_roundtrip_on_a_temp_db` (idempotent add, presence/no-op remove, empty list) and `format_timestamp_is_human_readable_utc`. - Drove the built binary end-to-end against a temp DB: empty list, idempotent add, list with timestamps, remove (present -> no-op), and `--database-url` both before and after the subcommand. All correct. - Full `just pre-commit` green (fmt, clippy `-D warnings`, build, 84 tests). ## Related - YT-46 (first-admin bootstrap, #103): with that fix you often will not need this, but the CLI remains the general allowlist-management tool. - YT-45: docs rewrite will fold in the new section.
feat(mcp): add yt mcp allowlist to manage the serve allowlist without SQL
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 38s
03eea546d1
The MCP server allowlist could only be edited through the browser admin UI at /admin, which requires an admin already logged in. When no admin can log in (bootstrap or recovery), operators had to hand-write INSERT/DELETE SQL against the SQLite state file, against this repo's tooling-gap rule.

Add `yt mcp allowlist add/list/remove`, which open the state DB and delegate to a new thin youtrack_mcp::allowlist wrapper over the db layer, so yt-cli gains no sqlx dependency. The DB path resolves via --database-url, then YOUTRACK_MCP_DATABASE_URL, then the installer default, and that default path is now single-sourced in one helper shared with `service install`. add is idempotent, remove distinguishes a real removal from a no-op, list prints each entry with who added it and when. docs/deployment.md documents the command edits the SQLite file directly and must run where it lives, with the container caveat.

#YT-47

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge branch 'main' into feat/YT-47-mcp-allowlist-cli
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 38s
efb7749b7a
Merge branch 'main' into feat/YT-47-mcp-allowlist-cli
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 46s
Create release / Create release from merged PR (pull_request) Has been skipped
94fb2507fe
David scheduled this pull request to auto merge when all checks succeed 2026-07-05 21:52:52 +02:00
David merged commit b6bdd6c415 into main 2026-07-05 21:53:35 +02:00
David deleted branch feat/YT-47-mcp-allowlist-cli 2026-07-05 21:53:35 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-cli!104
No description provided.