Imported from AlmaLinux/albs-mcp (
AGENTS.md). Install upstream withnpx skills add AlmaLinux/albs-mcp. Copyright stays with the author.
albs-mcp project rules
MCP server and CLI for AlmaLinux Build System. Five source files: constants.py (config values), client.py (HTTP/API logic), _commands.py (shared command functions and formatting), server.py (MCP tool wrappers and server instructions), cli.py (CLI alternative via argparse).
Project structure
src/albs_mcp/
constants.py — URLs, status maps, package lists, EPEL defaults
client.py — ALBSClient: all HTTP calls to ALBS API and log file I/O
_commands.py — shared command functions: client management, formatting, business logic
server.py — thin @mcp.tool() wrappers delegating to _commands.py, @mcp.prompt() workflow entry points, server instructions
cli.py — CLI (argparse), delegates to _commands.py functions
tests/
test_client_unit.py — unit tests for ALBSClient (mocked HTTP)
test_server_unit.py — unit tests for MCP tools (mocked client via _commands)
test_cli_unit.py — unit tests for CLI (mocked _commands functions)
test_integration.py — integration tests against real ALBS API (read-only)
Documentation
- Keep
README.mdin sync with the code. When adding, removing, or renaming tools, prompts, or parameters, update the corresponding sections: "What it can do", "Tools reference", "Prompts", examples, and environment variables. - MCP prompts (
@mcp.prompt()inserver.py) are user-invoked slash-command entry points. Keep them thin: parameterize and point at the workflow that already lives in the serverinstructions— do not duplicate the workflow rationale. Every prompt needs unit tests (direct call +mcp.get_promptrendering). - Update test counts in the "Tests" section of
README.mdwhen adding new tests. - Tool docstrings in
server.pyare exposed as MCP tool descriptions to the AI agent. Keep them accurate, concise, and up to date with the actual behavior. - Server instructions (the
instructionsparameter inFastMCP()) guide the AI agent's decision-making: when to ask the user, what defaults to apply, how to handle EPEL builds, signing workflow, etc. Update them whenever tool semantics or workflows change.
Testing
- Run
pytest tests/ -vand ensure all tests pass before considering any change complete. - Unit tests use mocked HTTP (
test_client_unit.py) and mocked ALBSClient (test_server_unit.py). They require no network. - Integration tests (
test_integration.py) hit the real ALBS API atbuild.almalinux.org. They are read-only and safe to run, but require network access. - Every new tool, parameter, or behavioral change must have corresponding unit tests.
- Async tests use
pytest-asynciowithasyncio_mode = "auto"(configured inpyproject.toml). No need to decorate with@pytest.mark.asyncioexplicitly.
Evals
- Eval definitions live in
.cursor/skills/albs-mcp-dev/evals/evals.json. They verify that an AI agent following the server instructions and tool descriptions would behave correctly in real workflows. - Run evals after every code change — not just instruction or tool changes. Any change to
client.py,server.py,constants.py, or tests can affect agent behavior. Evals must always pass. - Every new tool must have eval cases. At minimum: one
tool_selectioncase (the agent routes to this tool given a natural-language prompt) and one workflow case if the tool is part of a multi-step workflow (e.g. investigation, signing, EPEL builds). - A tool without evals is not done. Do not consider a new tool complete until its eval cases are added and all evals pass.
- To run evals: read
evals/evals.json, read the currentserver.pyinstructions and tool signatures, verify each criterion against the code, report pass/fail.
Code style
- Python 3.10+. Always use
from __future__ import annotationsat the top of each module. - Type hints on all public functions and method signatures.
- Follow the existing file separation: constants and config in
constants.py, all HTTP and file I/O inclient.py, command logic and formatting in_commands.py, thin MCP wrappers inserver.py, CLI incli.py. - No code duplication. Every HTTP call or piece of logic must live in exactly one place.
client.pyowns all HTTP calls;_commands.pyowns client management, formatting, and business logic;server.pyhas thin@mcp.tool()wrappers that delegate to_commands.py;cli.pydelegates to_commands.py(no MCP dependency). Follow theget_platforms/get_flavorspattern: client method fetches data,_commandsfunction formats it, other client methods reuse the same client method internally. - No hardcoded secrets or tokens anywhere in the code. Tokens are read from the
ALBS_JWT_TOKENenv var or~/.albs/credentialsfile at runtime.
Fail-fast, no silent errors
- Never silently skip invalid input. If a user passes an unknown flavor name, platform, arch, or any identifier that doesn't match what the API returns — raise an error immediately with the list of valid options. No
if x in dictfiltering that hides mistakes. - Never hardcode ALBS entity names (flavors, platforms, etc.) without verifying them against the live API. Names change — always validate dynamically.
- When values in
constants.pyare used as defaults (e.g.EPEL_PLATFORM_FLAVORS), they must match real ALBS data. After adding or changing default values, verify them against the API (e.g.get_flavors,get_platforms).
MCP server management
- The MCP server config lives in
~/.cursor/mcp.json. SeeREADME.mdfor the config snippet. - After changing any source code in
src/albs_mcp/, the package must be reinstalled and Cursor must be reloaded for changes to take effect. - Platform names in ALBS are case-sensitive (e.g.
AlmaLinux-Kitten-10, notalmalinux-kitten-10). Always useget_platformsto verify exact names.
Security
- Never log, print, or return JWT tokens in tool responses or error messages.
- Build deletion (
delete_build) is intentionally blocked for safety. Do not remove or bypass this guard. - Validate file paths in log operations (
_log_path,download_log,read_log_tail,read_log_range) to prevent path traversal. Enforced inALBSClient._log_path: the filename must match a strict basename whitelist ([A-Za-z0-9._+-]+, blocking/,\,%, spaces, NUL) — because the same name is interpolated into the download URL, an encoded separator could traverse on the remote host even when the local write is sandboxed — and the resolved path must additionally stay inside the build log directory (resolve()+is_relative_to, also rejecting./..). It raisesValueErroron any violation. Do not bypass this guard or build log URLs from unvalidated filenames. download_logwrites to a.parttemp file and atomically renames on success, so an interrupted download never leaves a partial file that later reads would treat as complete. Keep this atomic-write property.- Error messages returned to the MCP client should not expose internal filesystem paths, stack traces, or sensitive details.