netlogger-mcp
MCP server and Python library for amateur-radio net logging: which nets are on the air, who has checked in, who's up now, and what past nets logged.
- Read-only. It never writes to a net.
- One contract, more than one source. Every answer uses the same net and check-in records
(
schema/contract.schema.json), whatever logging system is behind them. The first source is NetLogger's public XML Data Service (API 1.3). The next is the OM-Logger being built for OMISS. - A good neighbour. NetLogger is a donation-funded service. This server never exceeds NetLogger's published call limits, caches every answer, and backs off when told to.
- Private details stay out. Street addresses, ZIP codes and IP addresses in NetLogger's data have no place in the contract, so they never reach an AI, a program or a user.
Status: 0.1.0, first release.
Tools
| Tool | NetLogger call | Returns |
|---|---|---|
netlogger_active_nets |
GetActiveNets |
nets on the air: server, name, frequency, band, mode, net control, logger, opened, monitoring count |
netlogger_checkins |
GetCheckins |
a live net's check-ins, the count, and the pointer (the station up now) |
netlogger_past_nets |
GetPastNets |
closed nets over the last N days, with the net IDs past check-ins need |
netlogger_past_checkins |
GetPastNetCheckins |
a closed net's check-ins |
get_version_info |
none | server version, NetLogger API version, contract version |
These are all the calls NetLogger's API 1.3 documents. GetPointer is deprecated; the pointer comes
with GetCheckins, so it's never called.
Call limits
| Call | NetLogger's limit | Answers reused for |
|---|---|---|
GetActiveNets |
1 a minute | 60 s; the name filter is applied locally, so any number of filters cost one call |
GetCheckins |
3 a minute | 20 s per net |
GetPastNets |
1 a minute | 60 s per query |
GetPastNetCheckins |
10 a minute | an hour (a closed net's list doesn't change) |
A limit is checked before a request is sent, never after. Over the limit, the answer comes from
cache with its age (age_seconds, stale), or the result says when to try again. A
429 Too Many Requests on any call stops all calls to NetLogger for at least a minute, longer
if NetLogger's Retry-After asks. NetLogger's anti-flooding is aimed at the client, and every call
reaches the same server. Past nets older than 7 days need a name filter (NetLogger's rule).
One budget per user account. Every request carries your callsign, so to NetLogger all your
copies are one station: Claude Desktop and Claude Code each running the server, a script using the
library, and so on. They share one budget through a small state file (limits.json, beside the
settings file), updated under a lock the operating system enforces between processes. Another
account on the same computer has its own folder, and its own callsign. The rules:
- A 429 seen by any copy stops them all.
- It never fails open. If the file is unreadable, it assumes the whole budget was spent and waits a full minute. If the file can't be used at all, that copy keeps to the limits on its own, starting with a minute's pause, and logs why.
- One glitch doesn't strand a copy. A copy on its own tries the file again after a minute. Once the file works, it rejoins the shared budget and carries back the calls it made on its own.
- Clock changes don't help. If the clock goes back, recorded calls count as "now", so they stay in the window longer, not shorter.
Install
pip install netlogger-mcp
Claude Code / Claude Desktop:
"netlogger": { "command": "netlogger-mcp" }
No API key or password is needed. Your callsign is.
Your callsign
Every request tells NetLogger which station is asking, in the User-Agent:
netlogger-mcp/0.1.0 (KI7MT; +https://github.com/qso-graph/netlogger-mcp)
That way, NetLogger can tell users apart. Without it, every install would look like one client, and one misbehaving install could get everyone blocked. There is no anonymous mode.
- Nothing to configure. On first use, the server says it needs your callsign, the AI asks you,
and it's saved (
netlogger_set_callsign). You're asked once. - Saved in a small settings file:
~/.config/netlogger-mcp/settings.jsonon Linux,~/Library/Application Support/netlogger-mcp/on macOS,%APPDATA%\netlogger-mcp\on Windows. A callsign is public, not a password. - Or set it with
NETLOGGER_MCP_CALLSIGN=KI7MT, which overrides the file. - Changing the callsign never resets the call limits.
For testing without the network, set NETLOGGER_MCP_MOCK=1 to answer from bundled synthetic samples.
As a library
Programs that don't need an AI use the same code directly, with the same limits, cache and contract. A library can't ask anyone anything, so it requires the callsign: the program passes in the signed-in user's callsign, or the club's for a shared server.
from netlogger_mcp.netlogger import NetLoggerSource
# callsign: required (no valid callsign: NetLoggerError, nothing sent).
# program_id / program_version: your app, as in ADIF's PROGRAMID and PROGRAMVERSION (optional).
nl = NetLoggerSource(callsign="KI7MT", program_id="OM-Logger", program_version="0.3")
# User-Agent: OM-Logger/0.3 netlogger-mcp/0.1.0 (KI7MT; +https://github.com/qso-graph/netlogger-mcp)
for net in nl.active_nets(name_like="OMISS")["nets"]:
live = nl.checkins(net["server"], net["name"])
print(net["name"], "up now:", live["pointer"])
Programs in other languages can run the server and call its tools over MCP (JSON-RPC on stdio or HTTP).
Terms and privacy
NetLogger's terms allow API use "in direct support of Radio Communications". This server is for that.
It sends a User-Agent naming this project and the station using it. Parsing follows the spec: no assumptions about node order
or count, unknown elements ignored, <Warning> messages logged for the developer. XML is parsed with
defusedxml.
Development
pip install -e ".[test]"
pytest
Part of qso-graph. Licensed GPL-3.0-or-later.
Release files for netlogger-mcp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| netlogger_mcp-0.1.0.tar.gz | 29.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| netlogger_mcp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.9 kB
Release files / netlogger_mcp-0.1.0.tar.gz
| Download URL | netlogger_mcp-0.1.0.tar.gz |
|---|---|
| Size | 29.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3c5e2541150410de27c8661e8df9732e6fbb6abf24306b0eb2805856b16eb190
|
|
BLAKE2b-256 checksum How to use checksums |
6c67949ec19376f65110cafc9ca1c4de0514c6702e5db57803769e8164b36ba7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / netlogger_mcp-0.1.0-py3-none-any.whl
| Download URL | netlogger_mcp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
80897d411f582c33727f40aeeb65f80b14276af4293111747b25429ca112b87a
|
|
BLAKE2b-256 checksum How to use checksums |
bb727faf5752f6407a09c0e02b5933fa54ff43c45621522ee36a5a335a13b4bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency log