darc-dok-mcp 0.1.0__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 (30) hide show
  1. darc_dok_mcp-0.1.0/.github/workflows/ci.yml +90 -0
  2. darc_dok_mcp-0.1.0/.github/workflows/publish.yml +246 -0
  3. darc_dok_mcp-0.1.0/.gitignore +8 -0
  4. darc_dok_mcp-0.1.0/CHANGELOG.md +11 -0
  5. darc_dok_mcp-0.1.0/LICENSE +674 -0
  6. darc_dok_mcp-0.1.0/NOTICE +23 -0
  7. darc_dok_mcp-0.1.0/PKG-INFO +182 -0
  8. darc_dok_mcp-0.1.0/README.md +155 -0
  9. darc_dok_mcp-0.1.0/docs/TRANSCRIPTION.md +44 -0
  10. darc_dok_mcp-0.1.0/pyproject.toml +46 -0
  11. darc_dok_mcp-0.1.0/scripts/build.py +175 -0
  12. darc_dok_mcp-0.1.0/scripts/fetch_published.py +53 -0
  13. darc_dok_mcp-0.1.0/scripts/package_build.py +88 -0
  14. darc_dok_mcp-0.1.0/scripts/transcribe_dok_list.py +97 -0
  15. darc_dok_mcp-0.1.0/server.json +26 -0
  16. darc_dok_mcp-0.1.0/src/darc_dok_mcp/__init__.py +16 -0
  17. darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/SOURCE.json +29 -0
  18. darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/derived/by_district.json +1249 -0
  19. darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/facts/darc_dok.json +16696 -0
  20. darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/facts/darc_special_dok.json +99816 -0
  21. darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/load.sql +7488 -0
  22. darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/owner-list.schema.json +49 -0
  23. darc_dok_mcp-0.1.0/src/darc_dok_mcp/reference.py +135 -0
  24. darc_dok_mcp-0.1.0/src/darc_dok_mcp/server.py +171 -0
  25. darc_dok_mcp-0.1.0/tests/conftest.py +15 -0
  26. darc_dok_mcp-0.1.0/tests/test_data.py +99 -0
  27. darc_dok_mcp-0.1.0/tests/test_doks.py +108 -0
  28. darc_dok_mcp-0.1.0/tests/test_security.py +81 -0
  29. darc_dok_mcp-0.1.0/tests/test_tools.py +62 -0
  30. darc_dok_mcp-0.1.0/uv.lock +2312 -0
@@ -0,0 +1,90 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main, develop]
6
+ push:
7
+ branches: [main, develop]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ # main takes a release PR from develop, or a security branch, and nothing else:
18
+ # merging into main publishes. Required on main by its branch protection.
19
+ release-source:
20
+ name: Release PR source
21
+ if: github.event_name == 'pull_request' && github.base_ref == 'main'
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - run: |
25
+ case "${{ github.head_ref }}" in
26
+ develop|security/*) echo "ok: ${{ github.head_ref }} -> main" ;;
27
+ *) echo "main takes a release PR from develop, or a security/ branch"; exit 1 ;;
28
+ esac
29
+
30
+ test:
31
+ name: pytest (${{ matrix.python-version }})
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
37
+ steps:
38
+ - uses: actions/checkout@v5
39
+
40
+ - uses: astral-sh/setup-uv@v6
41
+ with:
42
+ python-version: ${{ matrix.python-version }}
43
+ enable-cache: true
44
+
45
+ - name: Install (locked)
46
+ run: uv sync --group dev --frozen
47
+
48
+ - name: Unit tests
49
+ run: uv run pytest tests --ignore=tests/test_security.py -v
50
+
51
+ - name: Security tests
52
+ run: uv run pytest tests/test_security.py -v
53
+
54
+ sources:
55
+ name: sources (download, then check against them)
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@v5
59
+
60
+ - uses: astral-sh/setup-uv@v6
61
+ with:
62
+ python-version: "3.12"
63
+ enable-cache: true
64
+
65
+ - name: Install (locked)
66
+ run: uv sync --group dev --frozen
67
+
68
+ - name: Download the sources (owner's documents, checked against their SHA-256)
69
+ run: uv run python scripts/fetch_published.py
70
+
71
+ - name: Tests against the sources (AD1C and ARRL fetched by the tests)
72
+ run: uv run pytest tests --live --ignore=tests/test_security.py -v
73
+
74
+ ci-all-green:
75
+ name: ci-all-green
76
+ needs: [test, sources]
77
+ runs-on: ubuntu-latest
78
+ if: always()
79
+ steps:
80
+ - name: Verify required jobs all succeeded
81
+ run: |
82
+ if [[ "${{ needs.test.result }}" != "success" ]]; then
83
+ echo "FAIL: test matrix did not all succeed (result=${{ needs.test.result }})"
84
+ exit 1
85
+ fi
86
+ if [[ "${{ needs.sources.result }}" != "success" ]]; then
87
+ echo "FAIL: checks against the sources did not succeed (result=${{ needs.sources.result }})"
88
+ exit 1
89
+ fi
90
+ echo "All required CI jobs passed."
@@ -0,0 +1,246 @@
1
+ name: Publish to PyPI
2
+
3
+ # The release flow (qso-graph/.github TEMPLATES.md): work lands on `develop`; a release is a
4
+ # PR from `develop` into `main`, and merging it publishes. `main` is always the released code.
5
+ on:
6
+ push:
7
+ branches: [ main ]
8
+
9
+ concurrency:
10
+ group: publish
11
+ cancel-in-progress: false
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ security:
18
+ name: Security gates
19
+ runs-on: ubuntu-latest
20
+ outputs:
21
+ version: ${{ steps.version.outputs.version }}
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ # main changes only by a release PR, so main's version must be new. PyPI publishes
26
+ # pyproject's version and the Registry server.json's, so they must agree too.
27
+ # Computed once; every later job uses this value (needs.security.outputs.version).
28
+ # The gate fails closed: anything it cannot confirm stops the release.
29
+ - name: The version on main is new and consistent
30
+ id: version
31
+ run: |
32
+ VERSION="$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)"
33
+ PACKAGE="$(jq -r '.packages[0].identifier' server.json)"
34
+ echo "pyproject=${VERSION} server.json=$(jq -r '.version' server.json)/$(jq -r '.packages[0].version' server.json)"
35
+ if [ "$(jq -r '.version' server.json)" != "$VERSION" ] || [ "$(jq -r '.packages[0].version' server.json)" != "$VERSION" ]; then
36
+ echo "FAIL: server.json does not match pyproject.toml version ${VERSION}"
37
+ exit 1
38
+ fi
39
+ # Only a 404 means "not published yet"; unreachable or 5xx is not a yes.
40
+ CODE="$(curl -s -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/${PACKAGE}/${VERSION}/json")"
41
+ case "$CODE" in
42
+ 404) echo "PyPI: ${PACKAGE} ${VERSION} is new" ;;
43
+ 200) echo "FAIL: ${PACKAGE} ${VERSION} is already on PyPI. main changes only by a release PR with a new version."; exit 1 ;;
44
+ *) echo "FAIL: PyPI returned ${CODE}; cannot confirm ${VERSION} is new"; exit 1 ;;
45
+ esac
46
+ # git ls-remote --exit-code: 2 means no such tag; anything else (0, 128) stops.
47
+ set +e
48
+ git ls-remote --exit-code --tags origin "refs/tags/v${VERSION}" > /dev/null
49
+ RC=$?
50
+ set -e
51
+ case "$RC" in
52
+ 2) echo "tag v${VERSION} does not exist yet" ;;
53
+ 0) echo "FAIL: tag v${VERSION} already exists"; exit 1 ;;
54
+ *) echo "FAIL: could not check tags (git ls-remote exited ${RC})"; exit 1 ;;
55
+ esac
56
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
57
+
58
+
59
+
60
+ - uses: actions/setup-python@v5
61
+ with:
62
+ python-version: "3.12"
63
+
64
+ - name: Install test dependencies
65
+ run: pip install pytest pip-audit
66
+
67
+ - name: Run security test suite
68
+ run: python -m pytest tests/test_security.py -v
69
+
70
+ - name: Audit dependencies for vulnerabilities
71
+ # Audits this package's declared dependencies, and can fail: a known CVE stops the release.
72
+ # A finding with no fix yet is excepted only explicitly: --ignore-vuln ID, with a reason and date.
73
+ run: pip-audit --strict --desc on .
74
+
75
+ - name: Grep check — no subprocess/shell
76
+ run: |
77
+ if grep -rn 'subprocess\.\|os\.system\|shell=True' src/; then
78
+ echo "FAIL: subprocess/shell found in source"
79
+ exit 1
80
+ fi
81
+
82
+ - name: Grep check — no plaintext http://
83
+ run: |
84
+ if grep -rn 'http://' src/ | grep -v 'localhost\|127\.0\.0\.1\|::1'; then
85
+ echo "FAIL: non-HTTPS URL found in source"
86
+ exit 1
87
+ fi
88
+
89
+ - name: Grep check — no credential logging
90
+ run: |
91
+ if grep -rn -i 'print.*password\|print.*api_key\|logging.*password\|logging.*api_key' src/; then
92
+ echo "FAIL: credential logging found in source"
93
+ exit 1
94
+ fi
95
+
96
+ publish:
97
+ name: Build and publish to PyPI
98
+ needs: security
99
+ runs-on: ubuntu-latest
100
+ environment: pypi
101
+ permissions:
102
+ id-token: write # Required for trusted publishing (OIDC)
103
+ steps:
104
+ - uses: actions/checkout@v4
105
+
106
+ - uses: actions/setup-python@v5
107
+ with:
108
+ python-version: "3.12"
109
+
110
+ - name: Install build dependencies
111
+ run: pip install build
112
+
113
+ - name: Build package
114
+ run: python -m build
115
+
116
+ - name: Publish to PyPI
117
+ uses: pypa/gh-action-pypi-publish@release/v1
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # Registry-publish — pushes server.json to the Official MCP Registry after
121
+ # PyPI publish succeeds, so discovery surfaces stay in sync with PyPI.
122
+ # Canonical template: https://github.com/qso-graph/.github/blob/main/TEMPLATES.md
123
+ # ---------------------------------------------------------------------------
124
+ registry-publish:
125
+ name: Publish to MCP Registry
126
+ needs: [security, publish] # waits for PyPI publish to succeed
127
+ runs-on: ubuntu-latest
128
+ permissions:
129
+ id-token: write # GitHub OIDC
130
+ contents: read
131
+
132
+ steps:
133
+ - uses: actions/checkout@v5
134
+
135
+ - name: Install jq (server.json bump)
136
+ run: sudo apt-get update && sudo apt-get install -y jq
137
+
138
+ - name: Install mcp-publisher
139
+ run: |
140
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
141
+ ./mcp-publisher --help > /dev/null # smoke test
142
+
143
+ - name: Set server.json to the released version
144
+ run: |
145
+ VERSION="${{ needs.security.outputs.version }}"
146
+ echo "Setting server.json to ${VERSION}"
147
+ jq --arg v "$VERSION" \
148
+ '.version = $v | .packages[0].version = $v' \
149
+ server.json > /tmp/server.json
150
+ mv /tmp/server.json server.json
151
+ cat server.json
152
+
153
+ # The Registry checks PyPI itself and refuses a version PyPI isn't
154
+ # serving yet, which can lag the upload by a minute or more.
155
+ - name: Wait until PyPI serves this version
156
+ run: |
157
+ VERSION="${{ needs.security.outputs.version }}"
158
+ PACKAGE="$(jq -r '.packages[0].identifier' server.json)"
159
+ for i in $(seq 1 40); do
160
+ if curl -fsS "https://pypi.org/pypi/${PACKAGE}/${VERSION}/json" > /dev/null; then
161
+ echo "PyPI serves ${PACKAGE} ${VERSION}"
162
+ exit 0
163
+ fi
164
+ echo "attempt ${i}: PyPI doesn't serve ${PACKAGE} ${VERSION} yet"
165
+ sleep 15
166
+ done
167
+ echo "FAIL: PyPI never served ${PACKAGE} ${VERSION}"
168
+ exit 1
169
+
170
+ - name: Authenticate to MCP Registry (GitHub OIDC)
171
+ run: ./mcp-publisher login github-oidc
172
+
173
+ # Retries ride out the Registry's own transient failures. Before each
174
+ # retry, check whether an earlier attempt landed despite the error.
175
+ - name: Publish to MCP Registry
176
+ run: |
177
+ VERSION="${{ needs.security.outputs.version }}"
178
+ NAME="$(jq -r '.name' server.json)"
179
+ REGISTRY="https://registry.modelcontextprotocol.io/v0/servers?search=${NAME}&version=latest"
180
+ for i in 1 2 3 4 5; do
181
+ if ./mcp-publisher publish; then
182
+ exit 0
183
+ fi
184
+ echo "attempt ${i} failed; retrying in $((i * 30)) s"
185
+ sleep $((i * 30))
186
+ REG="$(curl -fsS "$REGISTRY" | jq -r --arg n "$NAME" '[.servers[] | select(.server.name == $n) | .server.version][0] // empty' || true)"
187
+ if [ "$REG" = "$VERSION" ]; then
188
+ echo "The Registry already has ${NAME} ${VERSION}; an earlier attempt landed"
189
+ exit 0
190
+ fi
191
+ done
192
+ echo "FAIL: the MCP Registry refused the publish 5 times"
193
+ exit 1
194
+
195
+ # ---------------------------------------------------------------------------
196
+ # Verify — a release is complete only when PyPI AND the MCP Registry both
197
+ # serve the tagged version. If either doesn't within 10 minutes, this run
198
+ # fails: a green run means published everywhere, a red one means not done.
199
+ # ---------------------------------------------------------------------------
200
+ verify:
201
+ name: Verify PyPI and MCP Registry
202
+ needs: [security, registry-publish]
203
+ runs-on: ubuntu-latest
204
+ steps:
205
+ - uses: actions/checkout@v5
206
+
207
+ - name: PyPI and Registry serve the released version
208
+ run: |
209
+ VERSION="${{ needs.security.outputs.version }}"
210
+ PACKAGE="$(jq -r '.packages[0].identifier' server.json)"
211
+ NAME="$(jq -r '.name' server.json)"
212
+ REGISTRY="https://registry.modelcontextprotocol.io/v0/servers?search=${NAME}&version=latest"
213
+ for i in $(seq 1 40); do
214
+ PYPI="$(curl -fsS "https://pypi.org/pypi/${PACKAGE}/json" | jq -r '.info.version' || true)"
215
+ REG="$(curl -fsS "$REGISTRY" | jq -r --arg n "$NAME" '[.servers[] | select(.server.name == $n) | .server.version][0] // empty' || true)"
216
+ echo "attempt ${i}: PyPI=${PYPI:-none} Registry=${REG:-none} want=${VERSION}"
217
+ if [ "$PYPI" = "$VERSION" ] && [ "$REG" = "$VERSION" ]; then
218
+ echo "Published everywhere: ${PACKAGE} ${VERSION} on PyPI and ${NAME} ${VERSION} in the MCP Registry"
219
+ exit 0
220
+ fi
221
+ sleep 15
222
+ done
223
+ echo "FAIL: release incomplete. PyPI=${PYPI:-none} Registry=${REG:-none}, expected ${VERSION}"
224
+ exit 1
225
+
226
+ # ---------------------------------------------------------------------------
227
+ # Tag the release, once it is published everywhere. The tag marks the commit on
228
+ # main that was released; nothing is triggered by it. If this job fails after a
229
+ # green verify, the release IS published: re-running this job is safe.
230
+ # ---------------------------------------------------------------------------
231
+ tag:
232
+ name: Tag the release
233
+ needs: [security, verify]
234
+ runs-on: ubuntu-latest
235
+ permissions:
236
+ contents: write
237
+ steps:
238
+ - uses: actions/checkout@v5
239
+
240
+ - name: Create and push vX.Y.Z
241
+ run: |
242
+ VERSION="${{ needs.security.outputs.version }}"
243
+ git config user.name "github-actions[bot]"
244
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
245
+ git tag -a "v${VERSION}" -m "${GITHUB_REPOSITORY#*/} ${VERSION}" "$GITHUB_SHA"
246
+ git push origin "v${VERSION}"
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .pytest_cache/
8
+ published/
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to `darc-dok-mcp` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] — 2026-10-06
9
+
10
+ - First release: DARC's DOK lists, as published and credited, with the shared QG Reference tools
11
+ (`source_info`, `lookup`, `search`, `codes_for`, `valid_on`) and `get_version_info`.