bigfix-root-mcp 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. bigfix_root_mcp-0.1.4/.editorconfig +23 -0
  2. bigfix_root_mcp-0.1.4/.github/dependabot.yml +42 -0
  3. bigfix_root_mcp-0.1.4/.github/workflows/pre-commit.yaml +130 -0
  4. bigfix_root_mcp-0.1.4/.github/workflows/tag_and_release.yaml +234 -0
  5. bigfix_root_mcp-0.1.4/.github/workflows/test.yaml +111 -0
  6. bigfix_root_mcp-0.1.4/.gitignore +225 -0
  7. bigfix_root_mcp-0.1.4/.mcp.json +13 -0
  8. bigfix_root_mcp-0.1.4/.pre-commit-config.yaml +224 -0
  9. bigfix_root_mcp-0.1.4/.yamllint.yaml +14 -0
  10. bigfix_root_mcp-0.1.4/LICENSE +21 -0
  11. bigfix_root_mcp-0.1.4/PKG-INFO +286 -0
  12. bigfix_root_mcp-0.1.4/README.md +262 -0
  13. bigfix_root_mcp-0.1.4/docs/besapi-notes.md +110 -0
  14. bigfix_root_mcp-0.1.4/docs/besapi-proposals.md +149 -0
  15. bigfix_root_mcp-0.1.4/docs/client-query.md +148 -0
  16. bigfix_root_mcp-0.1.4/docs/design-decisions.md +198 -0
  17. bigfix_root_mcp-0.1.4/docs/rest-endpoints.md +116 -0
  18. bigfix_root_mcp-0.1.4/docs/security-review.md +311 -0
  19. bigfix_root_mcp-0.1.4/fastmcp.json +17 -0
  20. bigfix_root_mcp-0.1.4/pyproject.toml +161 -0
  21. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/__init__.py +11 -0
  22. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/__main__.py +6 -0
  23. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/actions.py +42 -0
  24. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/besxml.py +85 -0
  25. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/clientquery.py +237 -0
  26. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/connection.py +147 -0
  27. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/content.py +268 -0
  28. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/errors.py +181 -0
  29. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/prompts.py +93 -0
  30. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/resources/client-cookbook.md +69 -0
  31. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/resources/session-cookbook.md +119 -0
  32. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/resources/tool-guide.md +68 -0
  33. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/response.py +93 -0
  34. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/server.py +861 -0
  35. bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/writes.py +108 -0
  36. bigfix_root_mcp-0.1.4/tests/conftest.py +108 -0
  37. bigfix_root_mcp-0.1.4/tests/test_actions.py +44 -0
  38. bigfix_root_mcp-0.1.4/tests/test_besxml.py +76 -0
  39. bigfix_root_mcp-0.1.4/tests/test_clientquery.py +244 -0
  40. bigfix_root_mcp-0.1.4/tests/test_connection.py +126 -0
  41. bigfix_root_mcp-0.1.4/tests/test_content.py +253 -0
  42. bigfix_root_mcp-0.1.4/tests/test_hints.py +47 -0
  43. bigfix_root_mcp-0.1.4/tests/test_resources.py +71 -0
  44. bigfix_root_mcp-0.1.4/tests/test_response.py +115 -0
  45. bigfix_root_mcp-0.1.4/tests/test_tools.py +412 -0
  46. bigfix_root_mcp-0.1.4/tests/test_writes.py +171 -0
  47. bigfix_root_mcp-0.1.4/uv.lock +2586 -0
@@ -0,0 +1,23 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+ # Requires plugin for VSCode:
3
+ # - https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig
4
+
5
+ # top-most EditorConfig file
6
+ root = true
7
+
8
+ [*]
9
+ charset = utf-8
10
+ trim_trailing_whitespace = true
11
+ insert_final_newline = true
12
+
13
+ [*.py]
14
+ # the following is required for compatibility with black:
15
+ indent_style = space
16
+ indent_size = 4
17
+
18
+ [*.bes]
19
+ indent_style = tab
20
+ # should tabs be 2, 3, or 4 spaces?
21
+ # indent_size = 4
22
+ # actionscript within the XML of BES files MUST have windows line endings:
23
+ end_of_line = crlf
@@ -0,0 +1,42 @@
1
+ ---
2
+ # Dependency-update alerts and PRs.
3
+ #
4
+ # The 7-day cooldown is the point, not a courtesy: a release published minutes
5
+ # ago is the likeliest to be a compromised one. It matches `exclude-newer` in
6
+ # pyproject.toml and is zizmor's minimum.
7
+ version: 2
8
+ updates:
9
+ # "uv", not "pip": uv.lock is what CI installs from (uv sync --locked), and
10
+ # "pip" would read pyproject.toml without updating the lock.
11
+ - package-ecosystem: "uv"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "weekly"
15
+ cooldown:
16
+ default-days: 7
17
+ open-pull-requests-limit: 5
18
+ labels:
19
+ - "dependencies"
20
+
21
+ - package-ecosystem: "github-actions"
22
+ directory: "/"
23
+ schedule:
24
+ interval: "weekly"
25
+ cooldown:
26
+ default-days: 7
27
+ open-pull-requests-limit: 5
28
+ labels:
29
+ - "dependencies"
30
+
31
+ # Scans .pre-commit-config.yaml and updates each hook's `rev:` (SHA-pinned
32
+ # revs get their version comment updated too, same as the github-actions
33
+ # entries above). No `cooldown` here: as of 2026-03, GitHub does not support
34
+ # the cooldown option for this ecosystem, so these PRs land without the
35
+ # 7-day delay the other two ecosystems get - review before merging.
36
+ - package-ecosystem: "pre-commit"
37
+ directory: "/"
38
+ schedule:
39
+ interval: "weekly"
40
+ open-pull-requests-limit: 5
41
+ labels:
42
+ - "dependencies"
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: pre-commit
3
+
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - main
9
+ workflow_dispatch:
10
+ # so a future release workflow can gate on this exact job rather than
11
+ # polling for other checks to go green
12
+ workflow_call:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ concurrency:
18
+ group: pre-commit-${{ github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ pre-commit:
23
+ runs-on: ubuntu-latest
24
+ # no-commit-to-branch guards a developer's local `git commit`; it has nothing
25
+ # to check on a runner. On a push to main, actions/checkout leaves HEAD on
26
+ # the main branch, so the hook fails the whole run on a merged PR. (On a
27
+ # pull_request event HEAD is detached at the merge ref, where it passes -
28
+ # which is why this only shows up after the merge.) SKIP is read by
29
+ # pre-commit itself, so it applies to both steps below. It is set even
30
+ # though that hook is currently commented out in .pre-commit-config.yaml,
31
+ # so uncommenting it there does not silently break CI.
32
+ #
33
+ # GH_TOKEN puts zizmor in online mode (offline is the default without it):
34
+ # it can then fetch the referenced actions themselves for the audits that
35
+ # need that - see the comment above the zizmor hook in
36
+ # .pre-commit-config.yaml. The runner's own GITHUB_TOKEN is enough for a
37
+ # public repo; this is read-only per the `permissions:` block above and
38
+ # never touches git credentials (actions/checkout below already sets
39
+ # persist-credentials: false).
40
+ env:
41
+ SKIP: no-commit-to-branch
42
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43
+ steps:
44
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
45
+ with:
46
+ # required to grab the history of the PR for pre-commit
47
+ fetch-depth: 0
48
+ persist-credentials: false
49
+
50
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
51
+ with:
52
+ python-version: "3.12"
53
+
54
+ # single source of truth for the uv version: the `rev` of the
55
+ # astral-sh/uv-pre-commit repo in .pre-commit-config.yaml, which
56
+ # `pre-commit autoupdate` already keeps current. Read with awk rather
57
+ # than a yaml parser so it needs nothing installed first. `rev` is
58
+ # SHA-pinned (Dependabot's pre-commit ecosystem keeps the trailing
59
+ # `# X.Y.Z` comment in sync when it bumps the SHA - same convention as
60
+ # the actions/* pins above), so the version comes from that comment;
61
+ # fall back to the bare `rev` value for a tag-pinned rev with no
62
+ # comment.
63
+ - name: read uv version from .pre-commit-config.yaml
64
+ run: |
65
+ UV_VERSION="$(awk '
66
+ /^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
67
+ found && $1 == "rev:" {
68
+ if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
69
+ ver = substr($0, RSTART, RLENGTH)
70
+ sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
71
+ } else {
72
+ ver = $2
73
+ gsub(/["'\'']/, "", ver)
74
+ sub(/^[vV]/, "", ver)
75
+ }
76
+ print ver
77
+ exit
78
+ }
79
+ ' .pre-commit-config.yaml)"
80
+ if [ -z "$UV_VERSION" ]; then
81
+ echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
82
+ exit 1
83
+ fi
84
+ echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
85
+ echo "uv version: $UV_VERSION"
86
+
87
+ # the local uv-lock-check / uv-build-wheel / pytest / mypy hooks are
88
+ # language: system and shell out to `uv`, which is not on a runner by
89
+ # default - without this they all fail
90
+ - name: install uv (needed by the local pre-commit hooks)
91
+ run: python -m pip install "uv==${UV_VERSION}"
92
+
93
+ # the local hooks run `uv run --frozen`, which needs the project's
94
+ # environment to already exist
95
+ - name: uv sync --locked
96
+ run: uv sync --locked
97
+
98
+ - name: pre-commit local action test
99
+ if: github.event_name == 'pull_request' || github.event_name == 'push'
100
+ uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
101
+ with:
102
+ extra_args: >-
103
+ --color=always
104
+ --from-ref ${{ github.event.pull_request.base.sha || github.event.before }}
105
+ --to-ref ${{ github.event.pull_request.head.sha || github.sha }}
106
+ --hook-stage manual
107
+
108
+ # manual dispatch, and a workflow_call: run every hook (including the
109
+ # manual-stage ones - validate-pyproject, pyroma, the pytest and mypy
110
+ # wrappers, the check-useless-excludes/check-hooks-apply meta hooks)
111
+ # against the full tree, not just the diff range a PR/push would use.
112
+ # workflow_call has no pull_request/before ref to diff against anyway -
113
+ # github.event there is the caller's payload, not a diff range.
114
+ - name: pre-commit local action test (manual dispatch)
115
+ if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call'
116
+ uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
117
+ with:
118
+ extra_args: --color=always --all-files --hook-stage manual
119
+
120
+ # `restore-keys` in the shared action means a config change restores the
121
+ # previous cache and then saves it back under the new key - so every hook
122
+ # rev bump would otherwise leave its old environment in the cache forever.
123
+ # gc deletes exactly the environments no live config references. Last step
124
+ # in the job on purpose: actions/cache saves in its post step, which runs
125
+ # after this, so what gets uploaded is the pruned store. No `if:` - the
126
+ # default success() matches the cache action's own `post-if: success()`,
127
+ # so this runs in precisely the cases where a save follows. `pre-commit`
128
+ # is on PATH from the shared action's `python -m pip install pre-commit`.
129
+ - name: prune unused hook environments before the cache is saved
130
+ run: pre-commit gc
@@ -0,0 +1,234 @@
1
+ ---
2
+ # Tags and releases on a version bump. Nothing here bumps the version: the flow
3
+ # is to change `[project] version` in pyproject.toml in a PR, merge it to main,
4
+ # and this workflow does the rest (tag, build, checksums, GitHub Release, and -
5
+ # once deliberately enabled, see the publish step - PyPI).
6
+ name: Tag and Release
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - main
12
+ paths:
13
+ - "pyproject.toml"
14
+ - "uv.lock"
15
+ - "src/**"
16
+ - ".github/workflows/tag_and_release.yaml"
17
+ - ".github/workflows/test.yaml"
18
+ - ".github/workflows/pre-commit.yaml"
19
+ # a re-run path for when a release run failed after the version was already
20
+ # merged; the tag check below still makes it a no-op if the tag exists
21
+ workflow_dispatch:
22
+
23
+ permissions:
24
+ contents: read
25
+
26
+ concurrency:
27
+ # deliberately no cancel-in-progress: never kill a release mid-flight
28
+ group: release-${{ github.ref }}
29
+
30
+ jobs:
31
+ # Decides whether there is anything to do, so the expensive jobs below can be
32
+ # skipped outright. A push that touches src/** without bumping the version
33
+ # gets a green, near-instant run and no release.
34
+ version:
35
+ name: Check for an untagged version
36
+ runs-on: ubuntu-latest
37
+ # fork guard: a fork that pushes to its own main should not try to release
38
+ if: github.repository == 'jgstew/bigfix-root-mcp'
39
+ outputs:
40
+ version: ${{ steps.getversion.outputs.version }}
41
+ release_needed: ${{ steps.tagged.outputs.release_needed }}
42
+ steps:
43
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
+ with:
45
+ # full history + tags: the tag check below needs every existing tag
46
+ fetch-depth: 0
47
+ fetch-tags: true
48
+ persist-credentials: false
49
+
50
+ # `[project] version` in pyproject.toml is the single source of truth -
51
+ # src/bigfix_root_mcp/__init__.py reads __version__ back out of the
52
+ # installed package metadata, so there is no second place to bump.
53
+ # tomllib is stdlib on the 3.11+ this package requires, and the runner's
54
+ # preinstalled python is new enough, so no setup-python step is needed.
55
+ - name: Read version from pyproject.toml
56
+ id: getversion
57
+ run: |
58
+ version="$(python3 -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
59
+ if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-]?[0-9A-Za-z.]+)?$'; then
60
+ echo "::error::unexpected version string in pyproject.toml: '$version'"
61
+ exit 1
62
+ fi
63
+ echo "version=$version" >> "$GITHUB_OUTPUT"
64
+ echo "version: $version"
65
+
66
+ # Release only if this version has no tag yet. This is what makes the
67
+ # workflow idempotent and safe to re-run.
68
+ - name: Check whether v<version> is already tagged
69
+ id: tagged
70
+ env:
71
+ VERSION: ${{ steps.getversion.outputs.version }}
72
+ run: |
73
+ if git show-ref --tags --verify --quiet -- "refs/tags/v${VERSION}"; then
74
+ echo "release_needed=false" >> "$GITHUB_OUTPUT"
75
+ echo "v${VERSION} is already tagged - nothing to release"
76
+ else
77
+ echo "release_needed=true" >> "$GITHUB_OUTPUT"
78
+ echo "v${VERSION} has no tag yet - will release"
79
+ fi
80
+
81
+ # The gates. Reusing the test and pre-commit workflows means the release is
82
+ # blocked by the exact same jobs that guard PRs, with no third-party
83
+ # wait-for-checks action and no polling. Both run in parallel off `version`
84
+ # so a release is no slower than the slower of the two.
85
+ test:
86
+ name: Test
87
+ needs: version
88
+ if: needs.version.outputs.release_needed == 'true'
89
+ uses: ./.github/workflows/test.yaml
90
+
91
+ pre-commit:
92
+ name: pre-commit
93
+ needs: version
94
+ if: needs.version.outputs.release_needed == 'true'
95
+ uses: ./.github/workflows/pre-commit.yaml
96
+
97
+ release:
98
+ name: Tag and Release
99
+ needs: [version, test, pre-commit]
100
+ runs-on: ubuntu-latest
101
+ # Deliberately gates the whole release - tag, GitHub release, and PyPI
102
+ # upload - not just the publish step, so an environment protection rule
103
+ # (required reviewer, wait timer, branch restriction) stops a release
104
+ # before the tag is cut rather than after. The name must match the
105
+ # `Environment name` field of the PyPI trusted publisher exactly: PyPI
106
+ # checks the `environment` claim in the OIDC token, and a mismatch fails
107
+ # the publish. Create it under Settings > Environments; an environment
108
+ # with no protection rules is valid and simply records the deployment.
109
+ environment: pypi
110
+ permissions:
111
+ # contents: write - create the tag and the release, and upload assets.
112
+ # id-token: write - OIDC for the PyPI trusted-publishing step at the end
113
+ # of this job. Both live here rather than in a separate publish job so
114
+ # the built dist/ does not have to be handed between jobs through
115
+ # upload-artifact/download-artifact.
116
+ contents: write
117
+ id-token: write
118
+ steps:
119
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
120
+ with:
121
+ persist-credentials: false
122
+
123
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
124
+ with:
125
+ python-version: "3.13"
126
+
127
+ # same awk-from-.pre-commit-config.yaml source of truth as
128
+ # .github/workflows/test.yaml and pre-commit.yaml
129
+ - name: read uv version from .pre-commit-config.yaml
130
+ run: |
131
+ UV_VERSION="$(awk '
132
+ /^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
133
+ found && $1 == "rev:" {
134
+ if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
135
+ ver = substr($0, RSTART, RLENGTH)
136
+ sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
137
+ } else {
138
+ ver = $2
139
+ gsub(/["'\'']/, "", ver)
140
+ sub(/^[vV]/, "", ver)
141
+ }
142
+ print ver
143
+ exit
144
+ }
145
+ ' .pre-commit-config.yaml)"
146
+ if [ -z "$UV_VERSION" ]; then
147
+ echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
148
+ exit 1
149
+ fi
150
+ echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
151
+ echo "uv version: $UV_VERSION"
152
+
153
+ - name: install uv
154
+ run: python -m pip install "uv==${UV_VERSION}"
155
+
156
+ - name: uv build (wheel + sdist)
157
+ run: uv build
158
+
159
+ # `sha256sum -c SHA256SUMS.txt`-verifiable, and with no dist/ prefix in
160
+ # it, so it checks out wherever the assets are downloaded to. Globs are
161
+ # listed explicitly rather than `./*` so the output file can never end up
162
+ # listing itself.
163
+ - name: Compute SHA-256 checksums
164
+ working-directory: dist
165
+ run: |
166
+ sha256sum ./*.whl ./*.tar.gz > SHA256SUMS.txt
167
+ cat SHA256SUMS.txt
168
+
169
+ # Release notes: GitHub's own generated notes (same content the Releases
170
+ # UI would produce) with the checksums appended, so the hashes are
171
+ # readable in the release body as well as downloadable as an asset. The
172
+ # notes are built here instead of passing --generate-notes to `gh release
173
+ # create` so the appended block is not at the mercy of how that flag
174
+ # combines with --notes.
175
+ - name: Build release notes
176
+ env:
177
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
178
+ VERSION: ${{ needs.version.outputs.version }}
179
+ run: |
180
+ notes_file="${RUNNER_TEMP}/release-notes.md"
181
+ if ! gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \
182
+ --method POST \
183
+ -f "tag_name=v${VERSION}" \
184
+ -f "target_commitish=${GITHUB_SHA}" \
185
+ --jq '.body' > "$notes_file"; then
186
+ echo "::warning::could not generate release notes; falling back to a plain body"
187
+ printf 'Release v%s\n' "${VERSION}" > "$notes_file"
188
+ fi
189
+ {
190
+ printf '\n## SHA-256\n\n'
191
+ printf 'Verify with `sha256sum -c SHA256SUMS.txt`:\n\n'
192
+ printf '```\n'
193
+ cat dist/SHA256SUMS.txt
194
+ printf '```\n'
195
+ } >> "$notes_file"
196
+ echo "NOTES_FILE=$notes_file" >> "$GITHUB_ENV"
197
+ cat "$notes_file"
198
+
199
+ # `gh release create --target` creates the tag itself, so this needs no
200
+ # git push and the checkout above can stay credential-less. Using the gh
201
+ # CLI (preinstalled on the runner) also means one less third-party action
202
+ # to pin and keep updated.
203
+ - name: Create the GitHub release
204
+ env:
205
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
206
+ VERSION: ${{ needs.version.outputs.version }}
207
+ run: |
208
+ gh release create "v${VERSION}" \
209
+ --target "${GITHUB_SHA}" \
210
+ --title "v${VERSION}" \
211
+ --notes-file "${NOTES_FILE}" \
212
+ dist/*.whl dist/*.tar.gz dist/SHA256SUMS.txt
213
+
214
+ # Inert until all of these are true, so nothing can publish by accident:
215
+ # 1. a PyPI trusted publisher exists for this repo with workflow name
216
+ # `tag_and_release.yaml` and environment name `pypi`, matching the
217
+ # `environment:` above (https://docs.pypi.org/trusted-publishers/)
218
+ # 2. the repository variable PYPI_PUBLISH is set to `true`
219
+ # (Settings > Secrets and variables > Actions > Variables)
220
+ # 3. whatever protection rules the `pypi` environment carries have been
221
+ # satisfied - those gate this entire job, tagging included
222
+ # `uv publish` speaks OIDC natively given the id-token: write permission
223
+ # above, so there is no long-lived API token to store and no
224
+ # pypa/gh-action-pypi-publish pin to maintain. The release is already
225
+ # created at this point: a failure here leaves the GitHub release intact
226
+ # and is re-run by hand, not by re-running the whole workflow.
227
+ #
228
+ # NOTE: bigfix-root-mcp is not on PyPI yet, so the very first publish also
229
+ # needs the project name to exist there or a pending publisher configured.
230
+ - name: Publish to PyPI (trusted publishing)
231
+ if: vars.PYPI_PUBLISH == 'true'
232
+ # the explicit globs keep SHA256SUMS.txt (which lives in dist/ too) out
233
+ # of the upload; uv publish would otherwise take everything in dist/
234
+ run: uv publish --trusted-publishing always dist/*.whl dist/*.tar.gz
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: test
3
+
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - main
9
+ workflow_dispatch:
10
+ # so a future release workflow can gate on this exact job rather than
11
+ # polling for other checks to go green
12
+ workflow_call:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ concurrency:
18
+ group: test-${{ github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ test:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ persist-credentials: false
28
+
29
+ # Single bump point for the version this package is tested against; keep
30
+ # it in step with the `classifiers` list in pyproject.toml. Pinned rather
31
+ # than "3" so a new CPython release does not silently change what CI runs.
32
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
33
+ with:
34
+ python-version: "3.13"
35
+
36
+ # single source of truth for the uv version: the `rev` of the
37
+ # astral-sh/uv-pre-commit repo in .pre-commit-config.yaml, which
38
+ # `pre-commit autoupdate` already keeps current. Read with awk rather
39
+ # than a yaml parser so it needs nothing installed first. `rev` is
40
+ # SHA-pinned (Dependabot's pre-commit ecosystem keeps the trailing
41
+ # `# X.Y.Z` comment in sync when it bumps the SHA - same convention as
42
+ # the actions/* pins above), so the version comes from that comment;
43
+ # fall back to the bare `rev` value for a tag-pinned rev with no
44
+ # comment. Same step as in .github/workflows/pre-commit.yaml.
45
+ - name: read uv version from .pre-commit-config.yaml
46
+ run: |
47
+ UV_VERSION="$(awk '
48
+ /^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
49
+ found && $1 == "rev:" {
50
+ if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
51
+ ver = substr($0, RSTART, RLENGTH)
52
+ sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
53
+ } else {
54
+ ver = $2
55
+ gsub(/["'\'']/, "", ver)
56
+ sub(/^[vV]/, "", ver)
57
+ }
58
+ print ver
59
+ exit
60
+ }
61
+ ' .pre-commit-config.yaml)"
62
+ if [ -z "$UV_VERSION" ]; then
63
+ echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
64
+ exit 1
65
+ fi
66
+ echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
67
+ echo "uv version: $UV_VERSION"
68
+
69
+ - name: install uv
70
+ run: python -m pip install "uv==${UV_VERSION}"
71
+
72
+ # no --all-extras: this package declares no optional extras, and the dev
73
+ # tooling lives in [dependency-groups] dev, which uv syncs by default
74
+ - name: uv sync --locked
75
+ run: uv sync --locked
76
+
77
+ # flags live in [tool.pytest.ini_options] in pyproject.toml, not here
78
+ - name: pytest
79
+ run: uv run --frozen pytest
80
+
81
+ - name: uv build (wheel + sdist)
82
+ run: uv build
83
+
84
+ # Exercise the artifact a release would actually ship, not just the
85
+ # source tree: install the wheel into a throwaway venv and import it.
86
+ # The resource read is the point - the MCP resources are markdown files
87
+ # loaded at runtime via importlib.resources, so a packaging change that
88
+ # drops them breaks the server without breaking a source-tree test run.
89
+ # There is no --help smoke check because the one console script
90
+ # (bigfix-root-mcp -> server:main) takes no arguments and goes straight
91
+ # to serving MCP over stdio.
92
+ - name: install the built wheel and smoke-import it
93
+ run: |
94
+ uv venv "${RUNNER_TEMP}/wheeltest"
95
+ VIRTUAL_ENV="${RUNNER_TEMP}/wheeltest" uv pip install dist/*.whl
96
+ "${RUNNER_TEMP}/wheeltest/bin/python" - <<'PY'
97
+ import importlib.resources as r
98
+
99
+ import bigfix_root_mcp
100
+ from bigfix_root_mcp import server # noqa: F401 (import side effects register the tools)
101
+
102
+ print("version:", bigfix_root_mcp.__version__)
103
+ for name in ("tool-guide.md", "session-cookbook.md", "client-cookbook.md"):
104
+ text = (r.files("bigfix_root_mcp") / "resources" / name).read_text()
105
+ assert len(text) > 500, name
106
+ print(name, len(text), "bytes")
107
+ PY
108
+
109
+ # cheap, and makes a test run's hashes comparable to any released ones
110
+ - name: sha256 of the built artifacts
111
+ run: sha256sum dist/*