Skip to content

Releases: jgstew/bigfix-root-mcp

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 16:31

Full Changelog: v0.1.4...v1.0.0

Changes

  • New: advisory relevance analysis. analyze_relevance and search_inspectors are offline tools (no server contact) backed by the new bigfix-relevance-analyzer dependency; three analyzer reference documents are exposed as resources, loaded lazily.
  • Preflight wired into query tools. session_relevance_query, client_query_submit, client_query, and target_relevance now run an advisory relevance check before sending — it never blocks the request (even a parse error still goes to the server), and results surface only when there are findings, with server-side relevance errors gaining a "Static analysis:" appendix with suggestions. Duplicate suggestions already present in the analyzer message are filtered out.
  • New: optional qna fast-evaluation tools, gated behind the [qna] extra. evaluate_client_relevance_qna runs client relevance via the qna binary against admin-inventoried hosts and caller-named container images only — never against managed endpoints or caller-supplied SSH parameters. These tools register only when bigfix-remote-client-relevance is importable and a target source is allowed; the base install never pulls in docker/asyncssh. A qna_version parameter lets callers forward a specific version for provisioning.
  • New: HTTP transport support via BIGFIX_MCP_TRANSPORT. stdio remains the default (banner off, since stdout belongs to the transport); http serves streamable HTTP on FastMCP's defaults (banner on). No auth layer of its own — see README.
  • Documentation updated for relevance analysis and qna evaluation; test suite expanded accordingly (analysis, qna, tools, resources) and de-env-gated so it passes whether or not the [qna] extra is installed.
  • Version bumped to 1.0.0 to reflect the new tool surface.

SHA-256

Verify with sha256sum -c SHA256SUMS.txt:

c854a6f166b6d236712c85ed4b9634f4261082682e3739900098bad36c087590  ./bigfix_root_mcp-1.0.0-py3-none-any.whl
3b0152da24587b1178b25b8d3c3b9a78c25fdecaf8224a8abc3e4d8a2c45e95d  ./bigfix_root_mcp-1.0.0.tar.gz

v0.1.4

Choose a tag to compare

@github-actions github-actions released this 30 Aug 15:17

Full Changelog: v0.1.3...v0.1.4

Changes

  • First real publish of the package to PyPI.
  • Refreshed README and design-decisions docs to match current behavior.
  • Reworked the security review doc for clarity.

SHA-256

Verify with sha256sum -c SHA256SUMS.txt:

d6a951b38639d3b8e20d5ea9a16ce5d7ebfe3582981e8634e370f8af44a0f9eb  ./bigfix_root_mcp-0.1.4-py3-none-any.whl
9a6725710cb2f4400c93a7d6d2c9e92e5b09915d1579d073c6bdf11323d3f304  ./bigfix_root_mcp-0.1.4.tar.gz

v0.1.3 - Automated releases, and the version moves to pyproject.toml

Choose a tag to compare

@github-actions github-actions released this 30 Aug 14:55

Release automation, and the package version moves into pyproject.toml. No change to the tool surface.

Releases are now cut by CI

This release is the first one produced by the new tag_and_release.yaml workflow rather than by hand.

A push to main touching pyproject.toml, uv.lock, src/**, or the workflows themselves starts a version job that reads [project] version and checks whether v<version> is already tagged. If it is, the run is a near-instant no-op — so a source change without a version bump costs nothing. If it isn't, the existing test and pre-commit workflows are invoked as gates through workflow_call, meaning a release is blocked by exactly the same jobs that guard a pull request, with no third-party wait-for-checks action and no polling. Only then does the release job build, checksum, tag, and publish.

Nothing in the workflow bumps the version. The flow is to change [project] version in a PR, merge it, and let the workflow do the rest.

The tag itself is created by gh release create --target, so no git push is involved and the checkout stays credential-less. Every action is pinned to a full commit SHA, permissions are read-only except in the release job, and the workflows are audited by zizmor in CI.

Build artifacts

First release to carry them: the wheel, the sdist, and a SHA256SUMS.txt you can check with sha256sum -c SHA256SUMS.txt. The checksums are also reproduced in this release body, below.

The version now lives in pyproject.toml

[project] version is a literal string again; dynamic = ["version"] and the [tool.hatch.version] block are gone. src/bigfix_root_mcp/__init__.py reads __version__ back out of the installed package metadata, falling back to 0.0.0 for a checkout where the package is not installed, so there is still exactly one place to bump.

This is what makes the release workflow's version step possible: it reads pyproject.toml with tomllib directly, which a dynamic version sourced from __init__.py could not support without executing the package.

One consequence worth knowing: __version__ now reflects the installed distribution's metadata rather than the source tree, so a bump that has not been re-synced into the environment will report the older value until it is.

PyPI

Publishing is wired into the workflow but deliberately inert. It requires a PyPI trusted publisher for this repository, the PYPI_PUBLISH repository variable set to true, and the pypi environment. bigfix-root-mcp is not on PyPI yet, so installation is still from source or from the artifacts attached here.

Full Changelog: v0.1.2...v0.1.3

SHA-256

Verify with sha256sum -c SHA256SUMS.txt:

1c3eebb1513cbdb6690cce5a6cf91c311f058860acc9b85fff908d6bddc32f13  ./bigfix_root_mcp-0.1.3-py3-none-any.whl
aea3b9cf45269880b5d1cb72950059177e9c483ef1383255c227bf1125e2a7d6  ./bigfix_root_mcp-0.1.3.tar.gz

v0.1.2 - uv, hatchling, ruff, and CI

Choose a tag to compare

@jgstew jgstew released this 30 Aug 14:43

Toolchain and packaging release. No change to the tool surface.

uv + hatchling + ruff, Python 3.11+

  • Build backend — setuptools to hatchling. The version stays a single source of truth in src/bigfix_root_mcp/__init__.py, read via [tool.hatch.version]. The old package-data rule is gone: hatchling ships everything inside the package directory, so the MCP resource markdown (loaded at runtime via importlib.resources) is included without one.
  • Environmentuv, with a committed uv.lock. uv sync replaces pip install -e ".[dev]"; dev dependencies moved to a PEP 735 [dependency-groups] group. Resolution is delayed 7 days via exclude-newer, so a release published minutes ago — the likeliest to be a compromised one — is not picked up immediately.
  • Python floor — now 3.11+ (was 3.10).
  • Lint and formatruff replaces black, flake8, and pydocstringformatter, with 22 rule families enabled. There is deliberately no pylint hook: ruff's PL* families are a port of pylint and report the same findings on this codebase, and pylint's one unique finding here is a false positive on a load-bearing except ToolError: raise.
  • Typesmypy now runs over src/.

Two real defects, found by the new checks

  • _snippet() in errors.py was annotated as taking str but is called with exception objects throughout the error-translation decorator. Its body already coerced with str(text), so this was a lie in the signature rather than a crash — now typed as object.
  • build_target_xml() in clientquery.py ended in an implicit fallthrough, so it could have returned None instead of a (str, int | None) tuple if the exactly-one-targeting-mode guard above it were ever changed. The final branch is now explicit.

Also fixed: lxml, pydantic, and requests are imported directly by src/ but were only arriving transitively through besapi and fastmcp. They are now declared dependencies.

Continuous integration

New test and pre-commit workflows, plus a Dependabot configuration covering the uv, github-actions, and pre-commit ecosystems on a weekly schedule with a 7-day cooldown.

The test workflow installs the built wheel into a throwaway environment and reads the packaged MCP resources back out of it, so a packaging change that drops them fails CI rather than silently breaking the server at runtime.

Every action is pinned to a full commit SHA, checkouts run with persist-credentials: false, and the workflows are audited by zizmor in CI. The pre-commit stack also gained bandit, slyp, pyupgrade, pygrep-hooks, pyroma, check-jsonschema, and the uv lock/audit/build hooks. Hook revisions are SHA-pinned with the tag in a trailing comment.

Dependency resolution fix

[tool.uv] constraint-dependencies pinned fastmcp-slim==4.0.0b1 while [project.dependencies] separately pinned fastmcp==4.0.0b1. Since fastmcp pins its own matching fastmcp-slim[client] at the same version, the two could never be bumped together — the first Dependabot PR to try (4.0.0b1 to 4.0.0b3) failed with dependency_file_not_resolvable. The constraint exists only to name fastmcp-slim, because uv permits prereleases just for explicitly named packages, so it is now >=4.0.0b1 and the exact version follows the fastmcp== pin.

Documentation

The README now recommends the official BigFix Platform MCP server and states plainly that this server's capabilities are intentionally limited where the official one's are not.

191 tests, still running entirely offline.

v0.1.1 - Content, actions, resources, and an opt-in write surface

Choose a tag to compare

@jgstew jgstew released this 30 Aug 14:43

Adds the content, computer and action tool surface, MCP resources and prompts, and a small opt-in write surface. The read-only default is unchanged: nothing that mutates a root server is registered unless it is explicitly enabled.

New read tools

Computersget_computer, find_computers
Contentget_content, find_content, applicable_fixlets
Actionslist_actions, get_action, get_action_status
Operators and roleslist_operators, list_roles
XMLvalidate_bes_xml

Action support is new code rather than a besapi wrapper: besapi has no action support at all. It is written in the upstreamable shape (conn first, no fastmcp imports, besapi-style exceptions), with paths confirmed against a live BigFix 11 root server via /api/help/action.

Resources and prompts

Three MCP resources the model can pull on demand — bigfix://relevance/session-cookbook, bigfix://relevance/client-cookbook, and bigfix://guide/tools — and four prompts: diagnose_computer, patch_status, find_stale_agents, troubleshoot_relevance.

Opt-in write surface

Registered only when BIGFIX_ALLOW_WRITES is set. Three tools: stop_action, set_dashboard_variable, import_bes_content. Each defaults to dry_run=true and emits a structured audit line to stderr on every attempt.

The scope rule is that only operations whose blast radius is reversible or nil qualify. Creating content is not the same as running it — an imported fixlet does nothing until somebody takes an action on it in the console, which is why import is included and deploying an action is not.

Deliberately absent, and not to be added without their own design round: POST /api/actions (arbitrary code as root, fleet-wide), any DELETE, operator creation, uploads, and site creation.

XML handling

New besxml module, used instead of besapi's RESTResult.besdict, which has two defects confirmed against a live BigFix 11 root server: it crashes on repeated sibling elements holding text (elem2dict calls .copy() on a str), and it discards attributes. Since GET /api/computer/{id} returns repeated <Property Name="...">value</Property> elements, besdict raises AttributeError on any real computer record — and for a computer record the dropped Name attribute is what identifies which property each value belongs to.

Bounded responses

Every tool that can return an unbounded amount of data now routes its payload through a single bounding layer, so a large fleet produces a truncated answer that says it was truncated rather than megabytes of JSON in the client's context window. Nothing rewrites the caller's query to bound it server-side: silently answering a different question than the one asked is worse than returning a big answer and admitting it was cut.

New documentation

  • docs/security-review.md — threat model and findings for the tool surface.
  • docs/rest-endpoints.md — live-verified REST paths, site-path rules, and relevance findings, including the operators that don't exist.

Development

Adds a pre-commit configuration along with .editorconfig, .yamllint.yaml, and linter configs.

190 tests, running entirely offline against a scripted fake BESConnection, including in-memory end-to-end MCP calls via fastmcp.Client.

v0.0.2 - Operator scope clarity and reference docs

Choose a tag to compare

@jgstew jgstew released this 03 Aug 20:41

Documentation and correctness refinements. No change to the tool surface.

Operator scope is now explicit

Every BigFix result is limited to what the configured operator can see; only a master operator has full visibility, and a regular operator cannot distinguish "does not exist" from "outside my scope". That caveat is now carried in the server instructions, the session_relevance_query and whoami descriptions, and the client query targeting description, so MCP clients don't report scoped counts as absolute totals.

Not-found errors say so too: computer group and operator lookups now report that the item "may not exist, or may not be visible to the configured operator."

New documentation

  • docs/client-query.md — client fast query protocol reference: request/response XML, the result row schema captured from a live BigFix 11 root server (computerID, computerName, subQueryID, isFailure, result, ResponseTime), confirmation that the results envelope has no completion flag, and the termination heuristics with their failure modes.
  • docs/besapi-notes.md — besapi behaviors this wrapper depends on or works around, verified against source: uneven error surfacing (only 403 and login raise), connection lifecycle gotchas (__enter__ without __exit__, __bool__ triggering a login, logout() not resetting last_connected), inconsistent method return shapes, and the site-path state.
  • docs/design-decisions.md — rationale for the read-only-by-construction approach, the lazy connection instead of a lifespan, escaping over CDATA, and FastMCP 4 beta specifics.

Fixes

Corrected a test fixture that included a totalResults key the real server does not return — the live envelope contains only results.

v0.0.1 - Initial MCP server

Choose a tag to compare

@jgstew jgstew released this 03 Aug 20:41

Initial implementation of a minimal, read-only MCP server around besapi.

Tools

Session relevancesession_relevance_query, using besapi's JSON variant so relevance errors surface as real errors rather than in-band "ERROR: ..." strings.

Client fast queryclient_query_submit, client_query_results, and a blocking client_query that polls with progress notifications. besapi has no built-in clientquery support, so this is implemented over the REST API with XML-escaped query text, an int-coerced query ID, and bounded polling that stops on expected-count-reached, results-stable, or timeout.

Read-only helperswhoami, get_server_info, list_sites, get_computer_group (explicit site_path required), get_operator, get_dashboard_variable, and api_get as a GET-only escape hatch.

Design constraints

  • Read-only by construction — no mutating besapi call exists in the package; the registered tool list is the boundary.
  • Explicit site paths — never uses besapi's mutable current-site-path state (set_current_site_path / get_current_site_path), so every tool call is reproducible in isolation.
  • Clean stdout — config reading is re-implemented rather than using besapi's helper, which prints to stdout and would corrupt the MCP stdio transport. All logging goes to stderr.
  • Upstreamable shape — generic BigFix logic takes conn first and imports nothing from fastmcp, so it can move into besapi with a mechanical connself change. See docs/besapi-proposals.md.

Requirements

Python >= 3.10, besapi>=4.1.5, fastmcp==4.0.0b1 (pinned exactly; the 4.0 beta advises exact pinning).

53 tests run entirely offline against a scripted fake connection, including in-memory end-to-end MCP calls.