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.
- darc_dok_mcp-0.1.0/.github/workflows/ci.yml +90 -0
- darc_dok_mcp-0.1.0/.github/workflows/publish.yml +246 -0
- darc_dok_mcp-0.1.0/.gitignore +8 -0
- darc_dok_mcp-0.1.0/CHANGELOG.md +11 -0
- darc_dok_mcp-0.1.0/LICENSE +674 -0
- darc_dok_mcp-0.1.0/NOTICE +23 -0
- darc_dok_mcp-0.1.0/PKG-INFO +182 -0
- darc_dok_mcp-0.1.0/README.md +155 -0
- darc_dok_mcp-0.1.0/docs/TRANSCRIPTION.md +44 -0
- darc_dok_mcp-0.1.0/pyproject.toml +46 -0
- darc_dok_mcp-0.1.0/scripts/build.py +175 -0
- darc_dok_mcp-0.1.0/scripts/fetch_published.py +53 -0
- darc_dok_mcp-0.1.0/scripts/package_build.py +88 -0
- darc_dok_mcp-0.1.0/scripts/transcribe_dok_list.py +97 -0
- darc_dok_mcp-0.1.0/server.json +26 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/__init__.py +16 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/SOURCE.json +29 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/derived/by_district.json +1249 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/facts/darc_dok.json +16696 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/facts/darc_special_dok.json +99816 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/load.sql +7488 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/data/owner-list.schema.json +49 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/reference.py +135 -0
- darc_dok_mcp-0.1.0/src/darc_dok_mcp/server.py +171 -0
- darc_dok_mcp-0.1.0/tests/conftest.py +15 -0
- darc_dok_mcp-0.1.0/tests/test_data.py +99 -0
- darc_dok_mcp-0.1.0/tests/test_doks.py +108 -0
- darc_dok_mcp-0.1.0/tests/test_security.py +81 -0
- darc_dok_mcp-0.1.0/tests/test_tools.py +62 -0
- 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,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`.
|