mcp-server-taxcode 0.1.1__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 (118) hide show
  1. mcp_server_taxcode-0.1.1/.github/dependabot.yml +47 -0
  2. mcp_server_taxcode-0.1.1/.github/workflows/ci.yml +179 -0
  3. mcp_server_taxcode-0.1.1/.github/workflows/dependabot-automerge.yml +39 -0
  4. mcp_server_taxcode-0.1.1/.github/workflows/drift-monitor.yml +45 -0
  5. mcp_server_taxcode-0.1.1/.github/workflows/release.yml +226 -0
  6. mcp_server_taxcode-0.1.1/.gitignore +66 -0
  7. mcp_server_taxcode-0.1.1/AGENTS.md +204 -0
  8. mcp_server_taxcode-0.1.1/CHANGELOG.md +4 -0
  9. mcp_server_taxcode-0.1.1/CODE_OF_CONDUCT.md +7 -0
  10. mcp_server_taxcode-0.1.1/CONTRIBUTING.md +10 -0
  11. mcp_server_taxcode-0.1.1/Dockerfile +50 -0
  12. mcp_server_taxcode-0.1.1/LICENSE +190 -0
  13. mcp_server_taxcode-0.1.1/PKG-INFO +307 -0
  14. mcp_server_taxcode-0.1.1/README.md +264 -0
  15. mcp_server_taxcode-0.1.1/SECURITY.md +14 -0
  16. mcp_server_taxcode-0.1.1/conformance-baseline.yml +31 -0
  17. mcp_server_taxcode-0.1.1/docs/ca/caccr_17014.md +17 -0
  18. mcp_server_taxcode-0.1.1/docs/ca/rtc_17014.md +17 -0
  19. mcp_server_taxcode-0.1.1/docs/ca/rtc_17041.md +17 -0
  20. mcp_server_taxcode-0.1.1/docs/ca/rtc_17951.md +17 -0
  21. mcp_server_taxcode-0.1.1/docs/fl/flconst_art__vii_sec__5.md +17 -0
  22. mcp_server_taxcode-0.1.1/docs/fl/flstat_196_031.md +17 -0
  23. mcp_server_taxcode-0.1.1/docs/fl/flstat_222_17.md +17 -0
  24. mcp_server_taxcode-0.1.1/docs/ga/gareg_560_7_3__01.md +17 -0
  25. mcp_server_taxcode-0.1.1/docs/ga/ocga_48_7_1.md +17 -0
  26. mcp_server_taxcode-0.1.1/docs/ga/ocga_48_7_20.md +17 -0
  27. mcp_server_taxcode-0.1.1/docs/ga/ocga_48_7_20_2024.md +17 -0
  28. mcp_server_taxcode-0.1.1/docs/ga/ocga_48_7_20_2025.md +17 -0
  29. mcp_server_taxcode-0.1.1/docs/ga/ocga_48_7_27.md +17 -0
  30. mcp_server_taxcode-0.1.1/docs/ga/ocga_48_7_30.md +17 -0
  31. mcp_server_taxcode-0.1.1/docs/il/ilcs_5_1501_a_20.md +17 -0
  32. mcp_server_taxcode-0.1.1/docs/il/ilcs_5_201.md +17 -0
  33. mcp_server_taxcode-0.1.1/docs/il/ilcs_5_301.md +17 -0
  34. mcp_server_taxcode-0.1.1/docs/llms-full.txt +460 -0
  35. mcp_server_taxcode-0.1.1/docs/llms.txt +68 -0
  36. mcp_server_taxcode-0.1.1/docs/ma/maconst_art__xliv.md +17 -0
  37. mcp_server_taxcode-0.1.1/docs/ma/mgl_62_1_f.md +17 -0
  38. mcp_server_taxcode-0.1.1/docs/ma/mgl_62_4.md +17 -0
  39. mcp_server_taxcode-0.1.1/docs/nj/njac_18_35_5_2.md +17 -0
  40. mcp_server_taxcode-0.1.1/docs/nj/njsa_54a_1_2.md +17 -0
  41. mcp_server_taxcode-0.1.1/docs/nj/njsa_54a_2_1.md +17 -0
  42. mcp_server_taxcode-0.1.1/docs/ny/nycrr_105_20.md +17 -0
  43. mcp_server_taxcode-0.1.1/docs/ny/nycrr_132_18.md +17 -0
  44. mcp_server_taxcode-0.1.1/docs/ny/nytax_601.md +17 -0
  45. mcp_server_taxcode-0.1.1/docs/ny/nytax_605.md +17 -0
  46. mcp_server_taxcode-0.1.1/docs/pr/cri_1010_01.md +17 -0
  47. mcp_server_taxcode-0.1.1/docs/pr/cri_1031_01.md +17 -0
  48. mcp_server_taxcode-0.1.1/docs/pr/cri_1031_02.md +17 -0
  49. mcp_server_taxcode-0.1.1/docs/pr/cri_1062_01.md +17 -0
  50. mcp_server_taxcode-0.1.1/docs/pr/ley22_3_2012.md +17 -0
  51. mcp_server_taxcode-0.1.1/docs/pr/ley60_2021_01.md +17 -0
  52. mcp_server_taxcode-0.1.1/docs/pr/ley60_2022_01.md +17 -0
  53. mcp_server_taxcode-0.1.1/docs/pr/ley60_2031_01.md +17 -0
  54. mcp_server_taxcode-0.1.1/docs/pr/ley60_2032_01.md +17 -0
  55. mcp_server_taxcode-0.1.1/docs/pr/ley60_6070_55.md +17 -0
  56. mcp_server_taxcode-0.1.1/docs/tx/txconst_art__viii_sec__24_a.md +17 -0
  57. mcp_server_taxcode-0.1.1/docs/tx/txtax_171_001.md +17 -0
  58. mcp_server_taxcode-0.1.1/docs/us/cfr_1_937_1.md +17 -0
  59. mcp_server_taxcode-0.1.1/docs/us/cfr_1_937_2.md +17 -0
  60. mcp_server_taxcode-0.1.1/docs/us/cfr_1_937_3.md +17 -0
  61. mcp_server_taxcode-0.1.1/docs/us/irc_1411.md +17 -0
  62. mcp_server_taxcode-0.1.1/docs/us/irc_6688.md +17 -0
  63. mcp_server_taxcode-0.1.1/docs/us/irc_865.md +17 -0
  64. mcp_server_taxcode-0.1.1/docs/us/irc_933.md +17 -0
  65. mcp_server_taxcode-0.1.1/docs/us/irc_937.md +17 -0
  66. mcp_server_taxcode-0.1.1/docs/us/irc_957.md +17 -0
  67. mcp_server_taxcode-0.1.1/docs/wa/rcw_82_87_020.md +17 -0
  68. mcp_server_taxcode-0.1.1/docs/wa/rcw_82_87_040.md +17 -0
  69. mcp_server_taxcode-0.1.1/docs/wa/waconst_art__vii_sec__1.md +17 -0
  70. mcp_server_taxcode-0.1.1/fastmcp.json +20 -0
  71. mcp_server_taxcode-0.1.1/pyproject.toml +117 -0
  72. mcp_server_taxcode-0.1.1/research.md +241 -0
  73. mcp_server_taxcode-0.1.1/scripts/check_conformance.sh +62 -0
  74. mcp_server_taxcode-0.1.1/scripts/check_openapi_drift.py +467 -0
  75. mcp_server_taxcode-0.1.1/scripts/check_tool_contract.py +64 -0
  76. mcp_server_taxcode-0.1.1/scripts/check_version.py +126 -0
  77. mcp_server_taxcode-0.1.1/scripts/ingest_statutes.py +1521 -0
  78. mcp_server_taxcode-0.1.1/scripts/release_notes.py +357 -0
  79. mcp_server_taxcode-0.1.1/scripts/setup_repo_governance.sh +52 -0
  80. mcp_server_taxcode-0.1.1/server.json +74 -0
  81. mcp_server_taxcode-0.1.1/skills/taxcode-mcp/SKILL.md +54 -0
  82. mcp_server_taxcode-0.1.1/src/taxcode_mcp/__init__.py +14 -0
  83. mcp_server_taxcode-0.1.1/src/taxcode_mcp/auth.py +65 -0
  84. mcp_server_taxcode-0.1.1/src/taxcode_mcp/client.py +218 -0
  85. mcp_server_taxcode-0.1.1/src/taxcode_mcp/config.py +95 -0
  86. mcp_server_taxcode-0.1.1/src/taxcode_mcp/db.py +1450 -0
  87. mcp_server_taxcode-0.1.1/src/taxcode_mcp/errors.py +399 -0
  88. mcp_server_taxcode-0.1.1/src/taxcode_mcp/middleware.py +169 -0
  89. mcp_server_taxcode-0.1.1/src/taxcode_mcp/profiles.py +180 -0
  90. mcp_server_taxcode-0.1.1/src/taxcode_mcp/server.py +526 -0
  91. mcp_server_taxcode-0.1.1/src/taxcode_mcp/ssrf.py +287 -0
  92. mcp_server_taxcode-0.1.1/src/taxcode_mcp/tools/__init__.py +41 -0
  93. mcp_server_taxcode-0.1.1/src/taxcode_mcp/tools/compliance.py +542 -0
  94. mcp_server_taxcode-0.1.1/src/taxcode_mcp/tools/statutes.py +156 -0
  95. mcp_server_taxcode-0.1.1/src/taxcode_mcp/tools/system.py +72 -0
  96. mcp_server_taxcode-0.1.1/tax-mcp-pr-and-docs.md +97 -0
  97. mcp_server_taxcode-0.1.1/tests/conftest.py +104 -0
  98. mcp_server_taxcode-0.1.1/tests/test_batch.py +147 -0
  99. mcp_server_taxcode-0.1.1/tests/test_check_version.py +159 -0
  100. mcp_server_taxcode-0.1.1/tests/test_client.py +308 -0
  101. mcp_server_taxcode-0.1.1/tests/test_code_mode_extra.py +66 -0
  102. mcp_server_taxcode-0.1.1/tests/test_config.py +32 -0
  103. mcp_server_taxcode-0.1.1/tests/test_conftest_auth_env.py +20 -0
  104. mcp_server_taxcode-0.1.1/tests/test_db.py +331 -0
  105. mcp_server_taxcode-0.1.1/tests/test_drift.py +141 -0
  106. mcp_server_taxcode-0.1.1/tests/test_e2e_live.py +213 -0
  107. mcp_server_taxcode-0.1.1/tests/test_errors.py +976 -0
  108. mcp_server_taxcode-0.1.1/tests/test_http_auth.py +392 -0
  109. mcp_server_taxcode-0.1.1/tests/test_ingest.py +179 -0
  110. mcp_server_taxcode-0.1.1/tests/test_layered.py +511 -0
  111. mcp_server_taxcode-0.1.1/tests/test_otel_spans.py +70 -0
  112. mcp_server_taxcode-0.1.1/tests/test_profiles.py +551 -0
  113. mcp_server_taxcode-0.1.1/tests/test_protocol.py +211 -0
  114. mcp_server_taxcode-0.1.1/tests/test_release_notes.py +574 -0
  115. mcp_server_taxcode-0.1.1/tests/test_server.py +848 -0
  116. mcp_server_taxcode-0.1.1/tests/test_ssrf.py +198 -0
  117. mcp_server_taxcode-0.1.1/tests/test_version.py +32 -0
  118. mcp_server_taxcode-0.1.1/uv.lock +3139 -0
@@ -0,0 +1,47 @@
1
+ version: 2
2
+ # The Dockerfile pulls its base from public.ecr.aws. Declaring that host
3
+ # under registries (no credentials; it allows anonymous pulls) lets the
4
+ # docker update job reach it. See "Configuring access to private registries
5
+ # for Dependabot" on docs.github.com.
6
+ registries:
7
+ public-ecr:
8
+ type: docker-registry
9
+ url: https://public.ecr.aws
10
+
11
+ updates:
12
+ - package-ecosystem: "uv"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
16
+ day: "sunday"
17
+ time: "04:30"
18
+ timezone: "Etc/UTC"
19
+ open-pull-requests-limit: 10
20
+ groups:
21
+ dependencies:
22
+ patterns:
23
+ - "*"
24
+
25
+ - package-ecosystem: "github-actions"
26
+ directory: "/"
27
+ schedule:
28
+ interval: "weekly"
29
+ day: "sunday"
30
+ time: "04:30"
31
+ timezone: "Etc/UTC"
32
+ open-pull-requests-limit: 10
33
+ groups:
34
+ actions:
35
+ patterns:
36
+ - "*"
37
+
38
+ - package-ecosystem: "docker"
39
+ directory: "/"
40
+ schedule:
41
+ interval: "weekly"
42
+ day: "sunday"
43
+ time: "04:30"
44
+ timezone: "Etc/UTC"
45
+ open-pull-requests-limit: 10
46
+ registries:
47
+ - public-ecr
@@ -0,0 +1,179 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ # Each push to main gets its own group, keyed on the commit SHA, so runs for
11
+ # different main commits never wait behind, replace or cancel each other.
12
+ # Pull request runs share one group per PR ref, and a new push to the PR cancels
13
+ # the run in progress.
14
+ group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
15
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ lint-and-types:
22
+ name: Lint and types
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ fetch-depth: 0
28
+ persist-credentials: false
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
32
+ with:
33
+ version: "latest"
34
+ enable-cache: true
35
+
36
+ - name: Set up Python
37
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
38
+ with:
39
+ python-version: "3.10"
40
+
41
+ - name: Install dependencies
42
+ run: uv sync --locked --extra dev --python 3.10
43
+
44
+ - name: Check code formatting
45
+ run: uv run ruff format --check .
46
+
47
+ - name: Run linter
48
+ run: uv run ruff check .
49
+
50
+ - name: Static type analysis
51
+ run: uv run mypy --strict src
52
+
53
+ test:
54
+ name: Tests (py${{ matrix.python-version }})
55
+ runs-on: ubuntu-latest
56
+ strategy:
57
+ fail-fast: false
58
+ matrix:
59
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
60
+ steps:
61
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
62
+ with:
63
+ fetch-depth: 0
64
+ persist-credentials: false
65
+
66
+ - name: Install uv
67
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
68
+ with:
69
+ version: "latest"
70
+ enable-cache: true
71
+
72
+ - name: Set up Python ${{ matrix.python-version }}
73
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
74
+ with:
75
+ python-version: ${{ matrix.python-version }}
76
+
77
+ - name: Install dependencies
78
+ # The code-mode extra is installed explicitly; with CI set, tests/conftest.py
79
+ # aborts the run if the Code Mode sandbox is missing instead of skipping its tests.
80
+ run: uv sync --locked --extra dev --extra code-mode --python "$PYTHON_VERSION"
81
+ env:
82
+ PYTHON_VERSION: ${{ matrix.python-version }}
83
+
84
+ - name: Run unit test suite
85
+ run: uv run pytest
86
+
87
+ tool-contract:
88
+ name: Tool contract assertions
89
+ runs-on: ubuntu-latest
90
+ steps:
91
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
92
+ with:
93
+ fetch-depth: 0
94
+ persist-credentials: false
95
+
96
+ - name: Install uv
97
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
98
+ with:
99
+ version: "latest"
100
+ enable-cache: true
101
+
102
+ - name: Set up Python
103
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
104
+ with:
105
+ python-version: "3.10"
106
+
107
+ - name: Install dependencies
108
+ run: uv sync --locked --extra dev --python 3.10
109
+
110
+ - name: Verify tool contracts
111
+ run: uv run python scripts/check_tool_contract.py
112
+
113
+ - name: Verify OpenAPI drift
114
+ run: uv run python scripts/check_openapi_drift.py
115
+
116
+ - name: Set up Node.js 22
117
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
118
+ with:
119
+ node-version: "22"
120
+
121
+ - name: Protocol conformance suite
122
+ run: ./scripts/check_conformance.sh
123
+
124
+ build-check:
125
+ name: Build and package check
126
+ runs-on: ubuntu-latest
127
+ steps:
128
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
129
+ with:
130
+ fetch-depth: 0
131
+ persist-credentials: false
132
+
133
+ - name: Install uv
134
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
135
+ with:
136
+ version: "latest"
137
+
138
+ - name: Set up Python
139
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
140
+ with:
141
+ python-version: "3.10"
142
+
143
+ - name: Install dependencies
144
+ run: uv sync --locked --extra dev --python 3.10
145
+
146
+ - name: Build wheel and sdist
147
+ run: uv build
148
+
149
+ - name: Reject fallback and untagged versions
150
+ run: uv run python scripts/check_version.py
151
+
152
+ - name: Validate distribution
153
+ run: uv run twine check dist/*
154
+
155
+ docker-build:
156
+ name: Container image build
157
+ runs-on: ubuntu-latest
158
+ steps:
159
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
160
+ with:
161
+ persist-credentials: false
162
+
163
+ - name: Build image (no push)
164
+ uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
165
+ with:
166
+ context: .
167
+ push: false
168
+ load: true
169
+ tags: mcp-server-ci:test
170
+
171
+ - name: Image label matches server.json name
172
+ run: |
173
+ EXPECTED="$(jq -r .name server.json)"
174
+ ACTUAL="$(docker inspect --format '{{ index .Config.Labels "io.modelcontextprotocol.server.name" }}' mcp-server-ci:test)"
175
+ if [ "$ACTUAL" != "$EXPECTED" ]; then
176
+ echo "::error::image label '${ACTUAL}' does not match server.json name '${EXPECTED}'"
177
+ exit 1
178
+ fi
179
+ echo "Image label matches ${EXPECTED}"
@@ -0,0 +1,39 @@
1
+ name: Dependabot Auto-Merge
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+
7
+ permissions: {}
8
+
9
+ # One run per PR; a newer push supersedes the pending run.
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ automerge:
16
+ name: Auto-merge Dependabot PRs
17
+ runs-on: ubuntu-latest
18
+ # The PR author, not github.actor (the last actor on the event, which can be spoofed),
19
+ # and only for branches in this repository.
20
+ if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == github.event.pull_request.head.repo.full_name
21
+ permissions:
22
+ contents: write # auto-merge squashes the PR into main once required checks pass
23
+ pull-requests: write # enable auto-merge on the PR
24
+ steps:
25
+ - name: Dependabot metadata
26
+ id: metadata
27
+ uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
28
+ with:
29
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
30
+
31
+ # Allowlist, not a major-version denylist: update-type is the highest change in the PR
32
+ # (a grouped PR with one major is skipped), and it is empty when the PR carries a
33
+ # non-Dependabot commit, so only minor and patch updates are auto-merged.
34
+ - name: Enable auto-merge for semver-minor and semver-patch updates
35
+ if: steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'
36
+ run: gh pr merge --auto --squash "$PR_URL"
37
+ env:
38
+ PR_URL: ${{ github.event.pull_request.html_url }}
39
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,45 @@
1
+ name: Weekly OpenAPI Drift Monitor
2
+
3
+ on:
4
+ schedule:
5
+ # Run every Sunday at 04:00 UTC
6
+ - cron: "0 4 * * 0"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ # One drift check at a time; a manual run waits for a scheduled one instead of racing it.
13
+ # queue: max lets up to 100 runs wait instead of cancelling them (GitHub does not
14
+ # guarantee their order).
15
+ concurrency:
16
+ group: ${{ github.workflow }}
17
+ cancel-in-progress: false
18
+ queue: max
19
+
20
+ jobs:
21
+ drift-check:
22
+ name: Check API Drift
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ fetch-depth: 0
28
+ persist-credentials: false
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
32
+ with:
33
+ version: "latest"
34
+ enable-cache: true
35
+
36
+ - name: Set up Python
37
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
38
+ with:
39
+ python-version: "3.10"
40
+
41
+ - name: Install dependencies
42
+ run: uv sync --locked --extra dev --python 3.10
43
+
44
+ - name: Run OpenAPI drift detection
45
+ run: uv run python scripts/check_openapi_drift.py
@@ -0,0 +1,226 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ concurrency:
9
+ group: release-${{ github.ref }}
10
+ cancel-in-progress: false
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build:
17
+ name: Build distributions
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ contents: read
21
+ steps:
22
+ - name: Checkout Code
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+ with:
25
+ fetch-depth: 0
26
+ persist-credentials: false
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
30
+ with:
31
+ version: "latest"
32
+ enable-cache: false
33
+
34
+ - name: Set up Python
35
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
36
+ with:
37
+ python-version: "3.10"
38
+
39
+ - name: Build Wheel & Source Dist
40
+ run: uv build
41
+
42
+ - name: Verify wheel version matches tag
43
+ run: |
44
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
45
+ shopt -s nullglob
46
+ wheels=(dist/*.whl)
47
+ if [ "${#wheels[@]}" -ne 1 ]; then
48
+ echo "::error::expected exactly one wheel in dist/, found ${#wheels[@]}"
49
+ exit 1
50
+ fi
51
+ WHEEL_VERSION="$(unzip -p "${wheels[0]}" '*.dist-info/METADATA' | sed -n 's/^Version: //p' | head -n 1)"
52
+ if [ "$WHEEL_VERSION" != "$TAG_VERSION" ]; then
53
+ echo "::error::wheel METADATA version '${WHEEL_VERSION}' does not match tag ${GITHUB_REF_NAME} (expected '${TAG_VERSION}')"
54
+ exit 1
55
+ fi
56
+ echo "Wheel version ${WHEEL_VERSION} matches tag ${GITHUB_REF_NAME}"
57
+
58
+ - name: Build release notes
59
+ run: python3 scripts/release_notes.py --to "$GITHUB_REF_NAME" > release-notes.md
60
+
61
+ - name: Verify Packages
62
+ run: |
63
+ uv pip install --system twine==7.0.0 cyclonedx-bom==7.5.0
64
+ twine check dist/*
65
+
66
+ - name: Generate CycloneDX SBOM
67
+ run: |
68
+ python -m venv --without-pip "$RUNNER_TEMP/sbom-env"
69
+ python -m pip --python "$RUNNER_TEMP/sbom-env/bin/python" install dist/*.whl
70
+ cyclonedx-py environment "$RUNNER_TEMP/sbom-env" -o sbom.cdx.json
71
+
72
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
73
+ with:
74
+ name: dist
75
+ path: |
76
+ dist/
77
+ sbom.cdx.json
78
+ release-notes.md
79
+ if-no-files-found: error
80
+
81
+ publish:
82
+ name: Publish to PyPI and GitHub Release
83
+ needs: [build]
84
+ runs-on: ubuntu-latest
85
+ environment: pypi
86
+ permissions:
87
+ id-token: write # OIDC for PyPI Trusted Publishing; no API token stored
88
+ contents: write # create the GitHub Release
89
+ steps:
90
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
91
+ with:
92
+ name: dist
93
+
94
+ - name: Publish to PyPI via OIDC
95
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
96
+
97
+ - name: Create GitHub Release
98
+ uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
99
+ with:
100
+ files: |
101
+ dist/*.whl
102
+ dist/*.tar.gz
103
+ sbom.cdx.json
104
+ body_path: release-notes.md
105
+
106
+ docker:
107
+ name: Publish GHCR image
108
+ needs: [publish]
109
+ runs-on: ubuntu-latest
110
+ permissions:
111
+ contents: read # checkout
112
+ packages: write # push the image to GHCR
113
+ outputs:
114
+ digest: ${{ steps.build.outputs.digest }}
115
+ image: ${{ steps.image.outputs.name }}
116
+ steps:
117
+ - name: Checkout Code
118
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
119
+ with:
120
+ persist-credentials: false
121
+
122
+ - name: Log in to GitHub Container Registry
123
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
124
+ with:
125
+ registry: ghcr.io
126
+ username: ${{ github.actor }}
127
+ password: ${{ secrets.GITHUB_TOKEN }}
128
+
129
+ - name: Image name (GHCR requires lowercase)
130
+ id: image
131
+ run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
132
+
133
+ - name: Extract version from tag
134
+ id: version
135
+ run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
136
+
137
+ - name: Build and Push Docker Image
138
+ id: build
139
+ uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
140
+ with:
141
+ context: .
142
+ push: true
143
+ provenance: false
144
+ sbom: false
145
+ tags: |
146
+ ${{ steps.image.outputs.name }}:${{ steps.version.outputs.version }}
147
+ ${{ steps.image.outputs.name }}:latest
148
+ build-args: |
149
+ UV_DYNAMIC_VERSIONING_BYPASS=${{ steps.version.outputs.version }}
150
+
151
+ attest:
152
+ name: Attest build provenance
153
+ needs: [build, docker]
154
+ runs-on: ubuntu-latest
155
+ permissions:
156
+ contents: read # required by attest-build-provenance
157
+ id-token: write # Sigstore signing certificate (OIDC)
158
+ attestations: write # store the attestation on GitHub
159
+ steps:
160
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
161
+ with:
162
+ name: dist
163
+
164
+ - name: Attest distribution provenance
165
+ uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
166
+ with:
167
+ subject-path: "dist/*"
168
+
169
+ - name: Attest container image provenance
170
+ uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
171
+ with:
172
+ subject-name: ${{ needs.docker.outputs.image }}
173
+ subject-digest: ${{ needs.docker.outputs.digest }}
174
+
175
+ registry:
176
+ name: Publish to MCP Registry
177
+ needs: [publish, docker, attest]
178
+ runs-on: ubuntu-latest
179
+ permissions:
180
+ contents: read # checkout (server.json)
181
+ id-token: write # mcp-publisher login github-oidc
182
+ steps:
183
+ - name: Checkout Code
184
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
185
+ with:
186
+ persist-credentials: false
187
+
188
+ - name: Install and Verify mcp-publisher
189
+ run: |
190
+ OS=$(uname -s | tr '[:upper:]' '[:lower:]')
191
+ ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
192
+ ARCHIVE="mcp-publisher_${OS}_${ARCH}.tar.gz"
193
+ curl -L -s -o "$ARCHIVE" "https://github.com/modelcontextprotocol/registry/releases/download/v1.8.1/$ARCHIVE"
194
+ curl -L -s -o checksums.txt "https://github.com/modelcontextprotocol/registry/releases/download/v1.8.1/registry_1.8.1_checksums.txt"
195
+ sha256sum --ignore-missing --check checksums.txt
196
+ tar xz -f "$ARCHIVE" mcp-publisher
197
+ rm "$ARCHIVE" checksums.txt
198
+
199
+ - name: Authenticate to MCP Registry
200
+ run: |
201
+ MAX_RETRIES=3
202
+ for ATTEMPT in $(seq 1 "$MAX_RETRIES"); do
203
+ if ./mcp-publisher login github-oidc; then
204
+ break
205
+ fi
206
+ if [ "$ATTEMPT" -eq "$MAX_RETRIES" ]; then
207
+ echo "mcp-publisher login failed after $MAX_RETRIES attempts" >&2
208
+ exit 1
209
+ fi
210
+ echo "mcp-publisher login failed (attempt $ATTEMPT/$MAX_RETRIES), retrying in 5s..."
211
+ sleep 5
212
+ done
213
+
214
+ - name: Set version in server.json
215
+ run: |
216
+ VERSION="${GITHUB_REF_NAME#v}"
217
+ jq --arg v "$VERSION" '
218
+ .version = $v
219
+ | .packages |= map(
220
+ if .registryType == "oci"
221
+ then .identifier = (.identifier | sub(":[^:/]+$"; "") + ":" + $v)
222
+ else .version = $v end)
223
+ ' server.json > server.tmp && mv server.tmp server.json
224
+
225
+ - name: Publish server to MCP Registry
226
+ run: ./mcp-publisher publish
@@ -0,0 +1,66 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # Environments
30
+ .env
31
+ .venv
32
+ env/
33
+ venv/
34
+ ENV/
35
+ env.bak/
36
+ venv.bak/
37
+
38
+ # Testing & Coverage
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ *.py,cover
46
+ .hypothesis/
47
+ .pytest_cache/
48
+ htmlcov/
49
+
50
+ # Type Checking
51
+ .mypy_cache/
52
+ .dmypy.json
53
+ dmypy.json
54
+
55
+ # IDEs
56
+ .vscode/
57
+ .idea/
58
+ *.swp
59
+ *.swo
60
+
61
+ # OS
62
+ .DS_Store
63
+ Thumbs.db
64
+
65
+ # Documentation / Runbooks
66
+ COOKBOOK.md