omiss-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.
- omiss_mcp-0.1.0/.github/workflows/ci.yml +60 -0
- omiss_mcp-0.1.0/.github/workflows/publish.yml +189 -0
- omiss_mcp-0.1.0/.gitignore +7 -0
- omiss_mcp-0.1.0/CHANGELOG.md +16 -0
- omiss_mcp-0.1.0/LICENSE +22 -0
- omiss_mcp-0.1.0/PKG-INFO +245 -0
- omiss_mcp-0.1.0/README.md +216 -0
- omiss_mcp-0.1.0/pyproject.toml +50 -0
- omiss_mcp-0.1.0/server.json +24 -0
- omiss_mcp-0.1.0/src/omiss_mcp/__init__.py +20 -0
- omiss_mcp-0.1.0/src/omiss_mcp/html.py +165 -0
- omiss_mcp-0.1.0/src/omiss_mcp/omiss.py +417 -0
- omiss_mcp-0.1.0/src/omiss_mcp/pages.py +539 -0
- omiss_mcp-0.1.0/src/omiss_mcp/paths.py +26 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/GenAwardReport.html +61 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/__init__.py +0 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/awardRecipients.html +37 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/awardRules.html +84 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/displayCheckinHistory.html +53 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/displayCheckinHistory_none.html +38 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/index.html +126 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/listCheckinHistory.html +34 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/searchResults.html +79 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/searchResults_none.html +53 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/statehoodSchedule.html +49 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/statistics.html +35 -0
- omiss_mcp-0.1.0/src/omiss_mcp/samples/vipListing.html +87 -0
- omiss_mcp-0.1.0/src/omiss_mcp/schema/contract.schema.json +304 -0
- omiss_mcp-0.1.0/src/omiss_mcp/server.py +350 -0
- omiss_mcp-0.1.0/tests/conftest.py +26 -0
- omiss_mcp-0.1.0/tests/test_pages.py +130 -0
- omiss_mcp-0.1.0/tests/test_security.py +217 -0
- omiss_mcp-0.1.0/tests/test_source.py +120 -0
- omiss_mcp-0.1.0/tests/test_tools.py +120 -0
- omiss_mcp-0.1.0/uv.lock +2065 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Cancel superseded runs on the same ref (push or PR) so we don't waste
|
|
10
|
+
# minutes when commits land in rapid succession.
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: pytest (${{ matrix.os }}, ${{ matrix.python-version }})
|
|
18
|
+
runs-on: ${{ matrix.os }}
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
os: [ubuntu-latest]
|
|
23
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
24
|
+
include:
|
|
25
|
+
- os: windows-latest
|
|
26
|
+
python-version: "3.10"
|
|
27
|
+
- os: windows-latest
|
|
28
|
+
python-version: "3.13"
|
|
29
|
+
- os: macos-latest
|
|
30
|
+
python-version: "3.13"
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v5
|
|
33
|
+
|
|
34
|
+
- uses: astral-sh/setup-uv@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: ${{ matrix.python-version }}
|
|
37
|
+
enable-cache: true
|
|
38
|
+
|
|
39
|
+
- name: Install (locked)
|
|
40
|
+
run: uv sync --group dev --frozen
|
|
41
|
+
|
|
42
|
+
- name: Unit tests
|
|
43
|
+
run: uv run pytest tests --ignore=tests/test_security.py -v
|
|
44
|
+
|
|
45
|
+
- name: Security tests
|
|
46
|
+
run: uv run pytest tests/test_security.py -v
|
|
47
|
+
|
|
48
|
+
ci-all-green:
|
|
49
|
+
name: ci-all-green
|
|
50
|
+
needs: [test]
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
if: always()
|
|
53
|
+
steps:
|
|
54
|
+
- name: Verify required jobs all succeeded
|
|
55
|
+
run: |
|
|
56
|
+
if [[ "${{ needs.test.result }}" != "success" ]]; then
|
|
57
|
+
echo "FAIL: test matrix did not all succeed (result=${{ needs.test.result }})"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
echo "All required CI jobs passed."
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
security:
|
|
10
|
+
name: Security gates
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
# PyPI publishes pyproject's version; the Registry publishes the tag's.
|
|
16
|
+
# They must be the same, or the two would disagree about what was released.
|
|
17
|
+
- name: Tag matches pyproject.toml version
|
|
18
|
+
run: |
|
|
19
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
20
|
+
PKG_VERSION="$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)"
|
|
21
|
+
echo "tag=${TAG_VERSION} pyproject=${PKG_VERSION}"
|
|
22
|
+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
23
|
+
echo "FAIL: tag v${TAG_VERSION} does not match pyproject.toml version ${PKG_VERSION}"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
|
|
31
|
+
- name: Install test dependencies
|
|
32
|
+
run: pip install -e ".[test]" pip-audit
|
|
33
|
+
|
|
34
|
+
- name: Run security test suite
|
|
35
|
+
run: python -m pytest tests/test_security.py -v
|
|
36
|
+
|
|
37
|
+
- name: Audit dependencies for vulnerabilities
|
|
38
|
+
run: pip-audit --strict --desc 2>&1 || true
|
|
39
|
+
|
|
40
|
+
- name: Grep check — no subprocess/shell
|
|
41
|
+
run: |
|
|
42
|
+
if grep -rn 'subprocess\.\|os\.system\|shell=True' src/; then
|
|
43
|
+
echo "FAIL: subprocess/shell found in source"
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
- name: Grep check — no plaintext http://
|
|
48
|
+
run: |
|
|
49
|
+
if grep -rn 'http://' src/ | grep -v 'localhost\|127\.0\.0\.1\|::1'; then
|
|
50
|
+
echo "FAIL: non-HTTPS URL found in source"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
- name: Grep check — no credential logging
|
|
55
|
+
run: |
|
|
56
|
+
if grep -rn -i 'print.*password\|print.*api_key\|logging.*password\|logging.*api_key' src/; then
|
|
57
|
+
echo "FAIL: credential logging found in source"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
publish:
|
|
62
|
+
name: Build and publish to PyPI
|
|
63
|
+
needs: security
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
environment: pypi
|
|
66
|
+
permissions:
|
|
67
|
+
id-token: write # Required for trusted publishing (OIDC)
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
|
|
71
|
+
- uses: actions/setup-python@v5
|
|
72
|
+
with:
|
|
73
|
+
python-version: "3.12"
|
|
74
|
+
|
|
75
|
+
- name: Install build dependencies
|
|
76
|
+
run: pip install build
|
|
77
|
+
|
|
78
|
+
- name: Build package
|
|
79
|
+
run: python -m build
|
|
80
|
+
|
|
81
|
+
- name: Publish to PyPI
|
|
82
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Registry-publish — pushes server.json to the Official MCP Registry after
|
|
86
|
+
# PyPI publish succeeds, so discovery surfaces stay in sync with PyPI.
|
|
87
|
+
# Canonical template: https://github.com/qso-graph/.github/blob/main/TEMPLATES.md
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
registry-publish:
|
|
90
|
+
name: Publish to MCP Registry
|
|
91
|
+
needs: publish # waits for PyPI publish to succeed
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
permissions:
|
|
94
|
+
id-token: write # GitHub OIDC
|
|
95
|
+
contents: read
|
|
96
|
+
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v5
|
|
99
|
+
|
|
100
|
+
- name: Install jq (server.json bump)
|
|
101
|
+
run: sudo apt-get update && sudo apt-get install -y jq
|
|
102
|
+
|
|
103
|
+
- name: Install mcp-publisher
|
|
104
|
+
run: |
|
|
105
|
+
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
|
|
106
|
+
./mcp-publisher --help > /dev/null # smoke test
|
|
107
|
+
|
|
108
|
+
- name: Bump server.json to tag version
|
|
109
|
+
run: |
|
|
110
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
111
|
+
echo "Setting server.json to ${TAG_VERSION}"
|
|
112
|
+
jq --arg v "$TAG_VERSION" \
|
|
113
|
+
'.version = $v | .packages[0].version = $v' \
|
|
114
|
+
server.json > /tmp/server.json
|
|
115
|
+
mv /tmp/server.json server.json
|
|
116
|
+
cat server.json
|
|
117
|
+
|
|
118
|
+
# The Registry checks PyPI itself and refuses a version PyPI isn't
|
|
119
|
+
# serving yet, which can lag the upload by a minute or more.
|
|
120
|
+
- name: Wait until PyPI serves this version
|
|
121
|
+
run: |
|
|
122
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
123
|
+
PACKAGE="$(jq -r '.packages[0].identifier' server.json)"
|
|
124
|
+
for i in $(seq 1 40); do
|
|
125
|
+
if curl -fsS "https://pypi.org/pypi/${PACKAGE}/${TAG_VERSION}/json" > /dev/null; then
|
|
126
|
+
echo "PyPI serves ${PACKAGE} ${TAG_VERSION}"
|
|
127
|
+
exit 0
|
|
128
|
+
fi
|
|
129
|
+
echo "attempt ${i}: PyPI doesn't serve ${PACKAGE} ${TAG_VERSION} yet"
|
|
130
|
+
sleep 15
|
|
131
|
+
done
|
|
132
|
+
echo "FAIL: PyPI never served ${PACKAGE} ${TAG_VERSION}"
|
|
133
|
+
exit 1
|
|
134
|
+
|
|
135
|
+
- name: Authenticate to MCP Registry (GitHub OIDC)
|
|
136
|
+
run: ./mcp-publisher login github-oidc
|
|
137
|
+
|
|
138
|
+
# Retries ride out the Registry's own transient failures. Before each
|
|
139
|
+
# retry, check whether an earlier attempt landed despite the error.
|
|
140
|
+
- name: Publish to MCP Registry
|
|
141
|
+
run: |
|
|
142
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
143
|
+
NAME="$(jq -r '.name' server.json)"
|
|
144
|
+
REGISTRY="https://registry.modelcontextprotocol.io/v0/servers?search=${NAME}&version=latest"
|
|
145
|
+
for i in 1 2 3 4 5; do
|
|
146
|
+
if ./mcp-publisher publish; then
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
echo "attempt ${i} failed; retrying in $((i * 30)) s"
|
|
150
|
+
sleep $((i * 30))
|
|
151
|
+
REG="$(curl -fsS "$REGISTRY" | jq -r --arg n "$NAME" '[.servers[] | select(.server.name == $n) | .server.version][0] // empty' || true)"
|
|
152
|
+
if [ "$REG" = "$TAG_VERSION" ]; then
|
|
153
|
+
echo "The Registry already has ${NAME} ${TAG_VERSION}; an earlier attempt landed"
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
done
|
|
157
|
+
echo "FAIL: the MCP Registry refused the publish 5 times"
|
|
158
|
+
exit 1
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Verify — a release is complete only when PyPI AND the MCP Registry both
|
|
162
|
+
# serve the tagged version. If either doesn't within 10 minutes, this run
|
|
163
|
+
# fails: a green run means published everywhere, a red one means not done.
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
verify:
|
|
166
|
+
name: Verify PyPI and MCP Registry
|
|
167
|
+
needs: registry-publish
|
|
168
|
+
runs-on: ubuntu-latest
|
|
169
|
+
steps:
|
|
170
|
+
- uses: actions/checkout@v5
|
|
171
|
+
|
|
172
|
+
- name: PyPI and Registry serve the tagged version
|
|
173
|
+
run: |
|
|
174
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
175
|
+
PACKAGE="$(jq -r '.packages[0].identifier' server.json)"
|
|
176
|
+
NAME="$(jq -r '.name' server.json)"
|
|
177
|
+
REGISTRY="https://registry.modelcontextprotocol.io/v0/servers?search=${NAME}&version=latest"
|
|
178
|
+
for i in $(seq 1 40); do
|
|
179
|
+
PYPI="$(curl -fsS "https://pypi.org/pypi/${PACKAGE}/json" | jq -r '.info.version' || true)"
|
|
180
|
+
REG="$(curl -fsS "$REGISTRY" | jq -r --arg n "$NAME" '[.servers[] | select(.server.name == $n) | .server.version][0] // empty' || true)"
|
|
181
|
+
echo "attempt ${i}: PyPI=${PYPI:-none} Registry=${REG:-none} want=${TAG_VERSION}"
|
|
182
|
+
if [ "$PYPI" = "$TAG_VERSION" ] && [ "$REG" = "$TAG_VERSION" ]; then
|
|
183
|
+
echo "Published everywhere: ${PACKAGE} ${TAG_VERSION} on PyPI and ${NAME} ${TAG_VERSION} in the MCP Registry"
|
|
184
|
+
exit 0
|
|
185
|
+
fi
|
|
186
|
+
sleep 15
|
|
187
|
+
done
|
|
188
|
+
echo "FAIL: release incomplete. PyPI=${PYPI:-none} Registry=${REG:-none}, expected ${TAG_VERSION}"
|
|
189
|
+
exit 1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-09-28)
|
|
4
|
+
|
|
5
|
+
- Tools for OMISS's public pages: the net schedule and holidays, member lookup, check-in history,
|
|
6
|
+
one past net's check-ins, the Statehood schedule, officers, awards, award rules, award recipients
|
|
7
|
+
and net statistics; OMISS nets on the air from NetLogger; plus `get_version_info`.
|
|
8
|
+
- Records follow `schema/contract.schema.json` (0.1); check-in fields share netlogger-mcp's names.
|
|
9
|
+
- omiss.net is read one request at a time, at most one every 2 seconds, shared by every copy for
|
|
10
|
+
the user account (a locked state file). Answers cached; a stale answer stands in when the site is
|
|
11
|
+
down; a 429 or 503 stops all requests for the back-off.
|
|
12
|
+
- Every value is checked before it is sent; award IDs must come from the site's list. No free text
|
|
13
|
+
ever reaches omiss.net.
|
|
14
|
+
- Postal addresses and email addresses are never returned. Members are looked up one at a time.
|
|
15
|
+
- A page whose layout has changed is reported as changed, not guessed at.
|
|
16
|
+
- `OmissSource` usable as a plain Python library.
|
omiss_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
6
|
+
of this license document, but changing it is not allowed.
|
|
7
|
+
|
|
8
|
+
Preamble
|
|
9
|
+
|
|
10
|
+
The GNU General Public License is a free, copyleft license for
|
|
11
|
+
software and other kinds of works.
|
|
12
|
+
|
|
13
|
+
The licenses for most software and other practical works are designed
|
|
14
|
+
to take away your freedom to share and change the works. By contrast,
|
|
15
|
+
the GNU General Public License is intended to guarantee your freedom to
|
|
16
|
+
share and change all versions of a program--to make sure it remains free
|
|
17
|
+
software for all its users. We, the Free Software Foundation, use the
|
|
18
|
+
GNU General Public License for most of our software; it applies also to
|
|
19
|
+
any other work released this way by its authors. You can apply it to
|
|
20
|
+
your programs, too.
|
|
21
|
+
|
|
22
|
+
For the full license text, see <https://www.gnu.org/licenses/gpl-3.0.html>
|
omiss_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: omiss-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for OMISS: net schedule, nets on the air, members, check-in history, Statehood, officers and awards
|
|
5
|
+
Project-URL: Homepage, https://qso-graph.io
|
|
6
|
+
Project-URL: Repository, https://github.com/qso-graph/omiss-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/qso-graph/omiss-mcp/issues
|
|
8
|
+
Author-email: "Greg Beam, KI7MT" <ki7mt@yahoo.com>
|
|
9
|
+
License: GPL-3.0-or-later
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: amateur-radio,awards,check-ins,ham-radio,mcp,model-context-protocol,nets,omiss,sideband
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Communications :: Ham Radio
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: fastmcp>=3.0
|
|
24
|
+
Requires-Dist: netlogger-mcp>=0.1.1
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: jsonschema; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest; extra == 'test'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<!-- mcp-name: io.github.qso-graph/omiss-mcp -->
|
|
31
|
+
# omiss-mcp
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/omiss-mcp/)
|
|
34
|
+
[](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.qso-graph/omiss-mcp&version=latest)
|
|
35
|
+
|
|
36
|
+
MCP server for [OMISS](https://www.omiss.net/), the Old Man International Sideband Society: the net schedule, OMISS nets on the air now, member lookup, past nets and who checked in, the Statehood schedule, officers, awards and net statistics, through any MCP-compatible AI assistant.
|
|
37
|
+
|
|
38
|
+
Data from omiss.net's public pages; nets on the air from NetLogger. Part of the [qso-graph](https://qso-graph.io/) project. **No login or API key needed.**
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uvx omiss-mcp # run it; nothing to install
|
|
44
|
+
pip install omiss-mcp # or install it into your own environment
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Tools
|
|
48
|
+
|
|
49
|
+
| Tool | Description | Key Parameters |
|
|
50
|
+
|------|-------------|----------------|
|
|
51
|
+
| `omiss_net_schedule` | Every net's band, UTC time, frequency, days and band coordinator; holiday dates | — |
|
|
52
|
+
| `omiss_nets_on_air` | OMISS nets on the air now, from NetLogger | — |
|
|
53
|
+
| `omiss_member_lookup` | One member: OM number, call, name, status, grid, state, county, last check-in | callsign or om_number |
|
|
54
|
+
| `omiss_checkin_history` | Past nets, newest first, optionally only a member's, a band's or a date's | callsign, om_number, band, date, net_control |
|
|
55
|
+
| `omiss_net_checkins` | One past net: net control, relays, notes, and the check-in list | net_id |
|
|
56
|
+
| `omiss_statehood_schedule` | 40m net dates and their free-call states, and the next one | — |
|
|
57
|
+
| `omiss_officers` | Officers, band coordinators, committees, appointees, past presidents | — |
|
|
58
|
+
| `omiss_awards` | The awards, with the IDs the other award tools take | — |
|
|
59
|
+
| `omiss_award_rules` | An award's rules, or every award's summary | award_id |
|
|
60
|
+
| `omiss_award_recipients` | Who holds an award, newest first, or check one member | award_id, callsign, om_number |
|
|
61
|
+
| `omiss_net_statistics` | Nets per band, last net per band, top check-ins and net controls, last check-in by state | state, top |
|
|
62
|
+
| `omiss_set_callsign` | Save your callsign, for `omiss_nets_on_air` (asked once) | callsign |
|
|
63
|
+
| `get_version_info` | Service version + upstream spec version (fleet identity attestation) | — |
|
|
64
|
+
|
|
65
|
+
## What is OMISS?
|
|
66
|
+
|
|
67
|
+
OMISS is an amateur-radio society that runs SSB nets on HF, from 10 to 160 meters, every day of the week. Members exchange OM numbers on the nets and work toward the society's awards. Its website publishes the net schedule, the member roster, check-in history for every net, and the award rules and recipients.
|
|
68
|
+
|
|
69
|
+
## Your Callsign
|
|
70
|
+
|
|
71
|
+
Only `omiss_nets_on_air` needs it: it asks NetLogger, which is told which station is asking. On first use the assistant asks for your callsign and saves it. You're asked once. The other tools don't need it.
|
|
72
|
+
|
|
73
|
+
## Good Neighbour Policy
|
|
74
|
+
|
|
75
|
+
omiss.net is a volunteer-run club website, not an API. We read it gently:
|
|
76
|
+
|
|
77
|
+
| Measure | Detail |
|
|
78
|
+
|---------|--------|
|
|
79
|
+
| **One request at a time** | At most one request every 2 seconds, shared by every copy you run (Claude Desktop, Claude Code, a script) through a locked file in your settings folder. |
|
|
80
|
+
| **Response caching** | Schedule, officers, Statehood, members and award recipients 24 hours; awards and rules 7 days; history and statistics 1 hour. |
|
|
81
|
+
| **Stale answers over errors** | When the site is down or busy, the last answer comes back with its age rather than an error. |
|
|
82
|
+
| **Back-off** | A "too many requests" or "unavailable" answer stops all requests for at least a minute, longer if the site asks. |
|
|
83
|
+
| **Request timeout** | 20-second timeout. |
|
|
84
|
+
| **User-Agent header** | Every request names this project, so the site's operators can see who is asking. |
|
|
85
|
+
| **NetLogger** | `omiss_nets_on_air` keeps to NetLogger's call limits, shared with [netlogger-mcp](https://github.com/qso-graph/netlogger-mcp) if you run it too. |
|
|
86
|
+
|
|
87
|
+
## Security and Privacy
|
|
88
|
+
|
|
89
|
+
- **Every value is checked before it is sent**: callsigns, OM numbers, bands from a fixed list, `YYYY`, `YYYY-MM` or `YYYY-MM-DD` dates, numeric net IDs, and award IDs from the site's own list. Free text is never sent to omiss.net.
|
|
90
|
+
- **No postal addresses or email addresses are ever returned**, even where a page prints one.
|
|
91
|
+
- **Members are looked up one at a time**, by callsign or OM number. There's no search by state, county or grid, and no roster download.
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
### Configure your MCP client
|
|
96
|
+
|
|
97
|
+
omiss-mcp works with any MCP-compatible client. Add the server config and restart. The tools appear automatically.
|
|
98
|
+
|
|
99
|
+
#### Claude Desktop
|
|
100
|
+
|
|
101
|
+
Add to `claude_desktop_config.json` (`~/Library/Application Support/Claude/` on macOS, `%APPDATA%\Claude\` on Windows):
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"omiss": {
|
|
107
|
+
"command": "uvx",
|
|
108
|
+
"args": ["omiss-mcp"]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Claude Code
|
|
115
|
+
|
|
116
|
+
Add to `.claude/settings.json`:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"omiss": {
|
|
122
|
+
"command": "uvx",
|
|
123
|
+
"args": ["omiss-mcp"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### ChatGPT Desktop
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"mcpServers": {
|
|
134
|
+
"omiss": {
|
|
135
|
+
"command": "uvx",
|
|
136
|
+
"args": ["omiss-mcp"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Cursor
|
|
143
|
+
|
|
144
|
+
Add to `.cursor/mcp.json` (project-level) or `~/.cursor/mcp.json` (global):
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"omiss": {
|
|
150
|
+
"command": "uvx",
|
|
151
|
+
"args": ["omiss-mcp"]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### VS Code / GitHub Copilot
|
|
158
|
+
|
|
159
|
+
Add to `.vscode/mcp.json` in your workspace:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"servers": {
|
|
164
|
+
"omiss": {
|
|
165
|
+
"command": "uvx",
|
|
166
|
+
"args": ["omiss-mcp"]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### Gemini CLI
|
|
173
|
+
|
|
174
|
+
Add to `~/.gemini/settings.json` (global) or `.gemini/settings.json` (project):
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"mcpServers": {
|
|
179
|
+
"omiss": {
|
|
180
|
+
"command": "uvx",
|
|
181
|
+
"args": ["omiss-mcp"]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Installed with pip instead? Use `"command": "omiss-mcp"` in any config above.
|
|
188
|
+
|
|
189
|
+
### Ask questions
|
|
190
|
+
|
|
191
|
+
> "When is the OMISS 40m net, and on what frequency?"
|
|
192
|
+
|
|
193
|
+
> "Is KI7MT an OMISS member? What's their OM number?"
|
|
194
|
+
|
|
195
|
+
> "Which OMISS nets are on the air right now?"
|
|
196
|
+
|
|
197
|
+
> "Which nets did OM 7212 check in to this year?"
|
|
198
|
+
|
|
199
|
+
> "Who checked in to last night's 80m net?"
|
|
200
|
+
|
|
201
|
+
> "What are the free-call states on the next 40m Statehood net?"
|
|
202
|
+
|
|
203
|
+
> "What does the Alphabet Soup award require, and have I earned it?"
|
|
204
|
+
|
|
205
|
+
> "Who ran the most OMISS nets as net control in the last 90 days?"
|
|
206
|
+
|
|
207
|
+
## As a Python Library
|
|
208
|
+
|
|
209
|
+
The same code works without an AI, with the same spacing and cache:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from omiss_mcp.omiss import OmissSource
|
|
213
|
+
|
|
214
|
+
om = OmissSource()
|
|
215
|
+
me = om.member_lookup(callsign="KI7MT")["members"][0]
|
|
216
|
+
for net in om.checkin_history(om_number_=me["om_number"], date_="2026")["nets"]:
|
|
217
|
+
print(net["time"], net["name"], net["checkin_count"])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Apps can name themselves, using ADIF's `PROGRAMID` and `PROGRAMVERSION`: `OmissSource(program_id="MyLogger", program_version="1.0")`.
|
|
221
|
+
|
|
222
|
+
## Testing Without Network
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
OMISS_MCP_MOCK=1 omiss-mcp
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## MCP Inspector
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
omiss-mcp --transport streamable-http --port 8015
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Development
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
git clone https://github.com/qso-graph/omiss-mcp.git
|
|
238
|
+
cd omiss-mcp
|
|
239
|
+
uv sync --group dev
|
|
240
|
+
uv run pytest
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
GPL-3.0-or-later
|