astro-checker 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Louis Perner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.4
2
+ Name: astro-checker
3
+ Version: 0.1.0
4
+ Summary: MCP server giving AI assistants direct access to public astronomy archives (TNS, SIMBAD, VSX, Gaia, ExoFOP, ALeRCE/ZTF, Fink/Rubin, SkyBoT)
5
+ Keywords: mcp,model-context-protocol,astronomy,astrophysics,transients,tns,simbad,gaia,tess,ztf,rubin,llm,claude
6
+ Author: Louis Perner
7
+ Author-email: Louis Perner <31986061+louisperner@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
21
+ Classifier: Typing :: Typed
22
+ Requires-Dist: mcp>=2.3.0
23
+ Requires-Python: >=3.11
24
+ Project-URL: Homepage, https://github.com/louisperner/AstroMCP
25
+ Project-URL: Repository, https://github.com/louisperner/AstroMCP
26
+ Project-URL: Issues, https://github.com/louisperner/AstroMCP/issues
27
+ Project-URL: Changelog, https://github.com/louisperner/AstroMCP/blob/main/CHANGELOG.md
28
+ Description-Content-Type: text/markdown
29
+
30
+ # AstroMCP
31
+
32
+ [![CI](https://github.com/louisperner/AstroMCP/actions/workflows/ci.yml/badge.svg)](https://github.com/louisperner/AstroMCP/actions/workflows/ci.yml)
33
+ [![PyPI](https://img.shields.io/pypi/v/astro-checker.svg)](https://pypi.org/project/astro-checker/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/astro-checker.svg)](https://pypi.org/project/astro-checker/)
35
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
36
+
37
+ <!-- mcp-name: io.github.louisperner/astro-checker -->
38
+
39
+ > The PyPI package is **`astro-checker`** (the name `astromcp` is too close to an unrelated existing package). The project is still called AstroMCP and both `astro-checker` and `astromcp` commands are installed.
40
+
41
+ An [MCP](https://modelcontextprotocol.io) server that gives AI assistants (Claude, Cursor, VS Code, ...)
42
+ direct, **read-only** access to public astronomy archives, so you can check a sky position in seconds and
43
+ answer the first question of any discovery: *is it already known?*
44
+
45
+ It queries the Transient Name Server (TNS), SIMBAD, AAVSO VSX, Gaia DR3, ExoFOP-TESS, ALeRCE/ZTF,
46
+ Fink/Rubin (LSST), IMCCE SkyBoT and several image surveys, and returns compact JSON the model can reason about.
47
+
48
+ > **Note:** answers depend on third-party services. Archives go down, rate-limit, or lag behind reality
49
+ > (for example, the public TNS archive is refreshed once a day). AstroMCP reports failed sources explicitly
50
+ > instead of pretending nothing is there, but always confirm important results at the source before
51
+ > reporting a discovery.
52
+
53
+ ## Tools (14)
54
+
55
+ | Tool | What it does |
56
+ |---|---|
57
+ | `whats_here(ra, dec, radius_arcsec=5, epoch=None)` | Queries all archives in parallel and returns a verdict (`known transient`, `known TESS object of interest`, `known variable`, `known asteroid`, `star`, `known object`, `nothing known`) with evidence, failed sources and image links |
58
+ | `tns_object(name)` | Looks up a transient on TNS by name (e.g. `2026agtq`) |
59
+ | `tns_search(ra, dec, radius_arcsec=5)` | TNS cone search |
60
+ | `ztf_lightcurve(oid)` | ZTF light curve from ALeRCE (detections with `magpsf`/`magpsf_corr`, non-detection summary) |
61
+ | `ztf_objects_near(ra, dec, radius_arcsec=5)` | ZTF alert objects near a position (ALeRCE) |
62
+ | `rubin_object(dia_object_id)` | Rubin/LSST object light curve and cutouts (Fink) |
63
+ | `rubin_recent(tag="most_likely_sn", days=3)` | Recent Rubin objects in a Fink classification tag |
64
+ | `simbad(ra, dec, radius_arcsec=10)` | SIMBAD cone search (TAP) |
65
+ | `vsx(ra, dec, radius_arcsec=10)` | AAVSO VSX variable-star cone search |
66
+ | `gaia(ra, dec, radius_arcsec=5)` | Gaia DR3 sources (via VizieR) |
67
+ | `tess_known(tic=None, ra=None, dec=None)` | TESS TOIs, CTOIs and eclipsing binaries (ExoFOP-TESS, VizieR, TIC v8.2) |
68
+ | `asteroids_at(ra, dec, epoch_iso, radius_arcsec=30)` | Known solar-system objects at a position and time (IMCCE SkyBoT) |
69
+ | `cutout_urls(ra, dec, size_arcsec=60)` | Image links: Legacy Survey DR10, Pan-STARRS, DSS2, Aladin |
70
+ | `observe_tonight(lat, lon, ...)` | Bright, recent, still-unclassified TNS transients observable from your site tonight, ranked |
71
+
72
+ Coordinates are ICRS decimal degrees; times are ISO 8601 UTC. Errors come back as `{"error": "..."}` and, in
73
+ `whats_here`, as `sources_failed` plus a `caveat`, so a missing archive is never mistaken for "nothing known".
74
+
75
+ ## Examples
76
+
77
+ These are real checks (also in `tests/test_integration.py`):
78
+
79
+ **"Is there anything known at RA 40.22898, Dec +12.601026?"**
80
+
81
+ ```text
82
+ whats_here(40.22898, 12.601026)
83
+ → verdict: "known transient"
84
+ evidence: ["TNS: AT 2026agtq at 0.0\"", "ZTF: ZTF26acatcla ndet=10 class=None"]
85
+ ```
86
+
87
+ **"What is at the position of TOI-2330?"**
88
+
89
+ ```text
90
+ whats_here(172.85872, -41.68953)
91
+ → verdict: "known TESS object of interest"
92
+ labels: ["known TESS object of interest", "known variable", "star"]
93
+ evidence: ["SIMBAD: WASP-128 [*] at 0.01\"", "SIMBAD: WASP-128b [BD?] at 0.01\"", "VSX: WASP-128 (EA/BD+ROT) at 0.04\"", ...]
94
+ ```
95
+
96
+ The assistant can then answer: *"That's TOI-2330, already known as WASP-128 b, a brown dwarf transiting WASP-128."*
97
+
98
+ **An empty patch of sky:**
99
+
100
+ ```text
101
+ whats_here(200.5, -20.3, 3)
102
+ → verdict: "nothing known", sources_failed: []
103
+ ```
104
+
105
+ **"What should I observe tonight from Curitiba?"**
106
+
107
+ ```text
108
+ observe_tonight(lat=-25.43, lon=-49.27)
109
+ → night_utc: ["2026-10-26T22:27Z", "2026-10-27T07:37Z"], moon illumination 0.99, 33 matching targets
110
+ 1. AT 2026afvp mag 10.3, 8.1 d old, max alt 40.9°, window 03:57–07:37 UTC
111
+ 2. AT 2026agsu mag 17.27, 2.2 d old, max alt 82.8°, window 22:27–02:37 UTC
112
+ ...
113
+ ```
114
+
115
+ `observe_tonight` selects transients up to 21 days old, TNS discovery mag ≤ 18.5, still unclassified, above 30°
116
+ while the Sun is below −12° and at least 20° from the Moon (all adjustable). Sun/Moon positions use the
117
+ Astronomical Almanac low-precision formulas (arcminute level). It is meant to help small telescopes and
118
+ spectroscopy groups pick classification targets.
119
+
120
+ ## Install
121
+
122
+ Requires Python 3.11+.
123
+
124
+ ```bash
125
+ uvx astro-checker # run without installing (recommended; needs uv)
126
+ pip install astro-checker # or install into an environment, then run `astro-checker` (alias: `astromcp`)
127
+ ```
128
+
129
+ Or with Docker (from a clone): `docker build -t astromcp . && docker run -i --rm astromcp`.
130
+ See [`.env.example`](.env.example) for the optional variables.
131
+
132
+ The server speaks MCP over stdio; you normally don't run it by hand, your MCP client launches it.
133
+
134
+ ## Configure your MCP client
135
+
136
+ ### Claude Code
137
+
138
+ ```bash
139
+ claude mcp add astromcp -- uvx astro-checker
140
+ # with TNS credentials:
141
+ claude mcp add astromcp -e TNS_BOT_ID=12345 -e TNS_BOT_NAME=my_bot -e TNS_API_KEY=... -- uvx astro-checker
142
+ ```
143
+
144
+ Add `--scope user` to make it available in every project.
145
+
146
+ ### Claude Desktop
147
+
148
+ Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`; Windows: `%APPDATA%\Claude\`):
149
+
150
+ ```json
151
+ {
152
+ "mcpServers": {
153
+ "astromcp": {
154
+ "command": "uvx",
155
+ "args": ["astromcp"],
156
+ "env": { "TNS_BOT_ID": "12345", "TNS_BOT_NAME": "my_bot", "TNS_API_KEY": "..." }
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ The `env` block is optional.
163
+
164
+ ### Other clients (Cursor, VS Code, Zed, Continue, ...)
165
+
166
+ Any client that launches stdio MCP servers works with command `uvx` and args `["astromcp"]`, or command
167
+ `astromcp` if you installed it with pip. Try it interactively with the MCP Inspector:
168
+
169
+ ```bash
170
+ npx @modelcontextprotocol/inspector uvx astro-checker
171
+ ```
172
+
173
+ ## Environment variables
174
+
175
+ | Variable | Default | Meaning |
176
+ |---|---|---|
177
+ | `TNS_BOT_ID`, `TNS_BOT_NAME`, `TNS_API_KEY` | unset | Optional TNS **bot** credentials ([create a bot](https://www.wis-tns.org/bots)) for live TNS queries |
178
+ | `TNS_PUBLIC_CSV` | unset | Path to a downloaded `tns_public_objects.csv.zip`, used when there are no credentials |
179
+ | `ASTROMCP_CACHE_DIR` | `~/.cache/astromcp` | Disk cache (HTTP responses for minutes to hours; ExoFOP tables 12 h; TNS archive 20 h) |
180
+
181
+ Without TNS credentials, TNS answers come from the daily public objects archive (cached copy or `TNS_PUBLIC_CSV`);
182
+ objects reported since 00:00 UTC are missing from it, and `whats_here` flags this instead of returning a clean
183
+ "nothing known". The API key is never returned in responses, written to logs, or stored in the cache.
184
+
185
+ ## Rate limits and good citizenship
186
+
187
+ These archives are free public services run by small teams. AstroMCP is built to be polite:
188
+
189
+ - TNS cone searches are serialized and spaced 6.5 s apart (TNS allows 10 per minute); `Retry-After` is honored on HTTP 429.
190
+ - Every request has a timeout, retries with backoff, and a per-host circuit breaker: when a host fails
191
+ repeatedly it is skipped for two minutes instead of stalling every query.
192
+ - Large tables (ExoFOP TOI/CTOI, TNS public archive) are downloaded once and cached on disk.
193
+
194
+ Please do the same: don't run `whats_here` over thousands of positions in a loop (use the archives' bulk
195
+ services for that), keep your own TNS bot for live queries, and credit the archives in publications
196
+ (TNS, SIMBAD/CDS, AAVSO VSX, ESA Gaia, ExoFOP-TESS, ALeRCE, Fink, IMCCE, Legacy Survey, Pan-STARRS).
197
+
198
+ ## Development
199
+
200
+ ```bash
201
+ git clone https://github.com/louisperner/AstroMCP && cd AstroMCP
202
+ uv sync
203
+ uv run pytest # offline tests (recorded responses in tests/fixtures)
204
+ uv run pytest -m integration # live queries against the real archives (TNS test needs credentials)
205
+ uv run ruff check . && uv run ruff format --check . && uv run mypy --strict
206
+ ```
207
+
208
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md).
209
+
210
+ ### Publishing to the MCP Registry (maintainers)
211
+
212
+ `server.json` describes the PyPI package for the official [MCP Registry](https://github.com/modelcontextprotocol/registry).
213
+ After the version is on PyPI (the `mcp-name` comment above must be in the published README):
214
+
215
+ ```bash
216
+ brew install mcp-publisher # or download a release binary
217
+ mcp-publisher login github # authenticates the io.github.louisperner namespace
218
+ mcp-publisher publish # reads ./server.json
219
+ ```
220
+
221
+ Bump `version` in `pyproject.toml`, `src/astromcp/__init__.py` and both places in `server.json` together.
222
+
223
+ ## Em português
224
+
225
+ O AstroMCP é um servidor MCP que dá a assistentes de IA acesso direto (somente leitura) a bases públicas de
226
+ astronomia — TNS, SIMBAD, VSX, Gaia DR3, ExoFOP-TESS, ALeRCE/ZTF, Fink/Rubin, SkyBoT e cutouts de imagem —
227
+ para verificar rapidamente se um objeto já é conhecido. Comece por `whats_here(ra, dec)`; para planejar a noite,
228
+ `observe_tonight(lat, lon)`. Instale com `uvx astro-checker` ou `pip install astro-checker` e, no Claude Code,
229
+ `claude mcp add astromcp -- uvx astro-checker`. Credenciais do bot do TNS (opcionais) vão nas variáveis
230
+ `TNS_BOT_ID`, `TNS_BOT_NAME` e `TNS_API_KEY` e nunca aparecem nas respostas. As respostas dependem de serviços
231
+ externos: confirme na fonte antes de anunciar uma descoberta.
232
+
233
+ ## License
234
+
235
+ [MIT](LICENSE) © 2026 Louis Perner
@@ -0,0 +1,206 @@
1
+ # AstroMCP
2
+
3
+ [![CI](https://github.com/louisperner/AstroMCP/actions/workflows/ci.yml/badge.svg)](https://github.com/louisperner/AstroMCP/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/astro-checker.svg)](https://pypi.org/project/astro-checker/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/astro-checker.svg)](https://pypi.org/project/astro-checker/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ <!-- mcp-name: io.github.louisperner/astro-checker -->
9
+
10
+ > The PyPI package is **`astro-checker`** (the name `astromcp` is too close to an unrelated existing package). The project is still called AstroMCP and both `astro-checker` and `astromcp` commands are installed.
11
+
12
+ An [MCP](https://modelcontextprotocol.io) server that gives AI assistants (Claude, Cursor, VS Code, ...)
13
+ direct, **read-only** access to public astronomy archives, so you can check a sky position in seconds and
14
+ answer the first question of any discovery: *is it already known?*
15
+
16
+ It queries the Transient Name Server (TNS), SIMBAD, AAVSO VSX, Gaia DR3, ExoFOP-TESS, ALeRCE/ZTF,
17
+ Fink/Rubin (LSST), IMCCE SkyBoT and several image surveys, and returns compact JSON the model can reason about.
18
+
19
+ > **Note:** answers depend on third-party services. Archives go down, rate-limit, or lag behind reality
20
+ > (for example, the public TNS archive is refreshed once a day). AstroMCP reports failed sources explicitly
21
+ > instead of pretending nothing is there, but always confirm important results at the source before
22
+ > reporting a discovery.
23
+
24
+ ## Tools (14)
25
+
26
+ | Tool | What it does |
27
+ |---|---|
28
+ | `whats_here(ra, dec, radius_arcsec=5, epoch=None)` | Queries all archives in parallel and returns a verdict (`known transient`, `known TESS object of interest`, `known variable`, `known asteroid`, `star`, `known object`, `nothing known`) with evidence, failed sources and image links |
29
+ | `tns_object(name)` | Looks up a transient on TNS by name (e.g. `2026agtq`) |
30
+ | `tns_search(ra, dec, radius_arcsec=5)` | TNS cone search |
31
+ | `ztf_lightcurve(oid)` | ZTF light curve from ALeRCE (detections with `magpsf`/`magpsf_corr`, non-detection summary) |
32
+ | `ztf_objects_near(ra, dec, radius_arcsec=5)` | ZTF alert objects near a position (ALeRCE) |
33
+ | `rubin_object(dia_object_id)` | Rubin/LSST object light curve and cutouts (Fink) |
34
+ | `rubin_recent(tag="most_likely_sn", days=3)` | Recent Rubin objects in a Fink classification tag |
35
+ | `simbad(ra, dec, radius_arcsec=10)` | SIMBAD cone search (TAP) |
36
+ | `vsx(ra, dec, radius_arcsec=10)` | AAVSO VSX variable-star cone search |
37
+ | `gaia(ra, dec, radius_arcsec=5)` | Gaia DR3 sources (via VizieR) |
38
+ | `tess_known(tic=None, ra=None, dec=None)` | TESS TOIs, CTOIs and eclipsing binaries (ExoFOP-TESS, VizieR, TIC v8.2) |
39
+ | `asteroids_at(ra, dec, epoch_iso, radius_arcsec=30)` | Known solar-system objects at a position and time (IMCCE SkyBoT) |
40
+ | `cutout_urls(ra, dec, size_arcsec=60)` | Image links: Legacy Survey DR10, Pan-STARRS, DSS2, Aladin |
41
+ | `observe_tonight(lat, lon, ...)` | Bright, recent, still-unclassified TNS transients observable from your site tonight, ranked |
42
+
43
+ Coordinates are ICRS decimal degrees; times are ISO 8601 UTC. Errors come back as `{"error": "..."}` and, in
44
+ `whats_here`, as `sources_failed` plus a `caveat`, so a missing archive is never mistaken for "nothing known".
45
+
46
+ ## Examples
47
+
48
+ These are real checks (also in `tests/test_integration.py`):
49
+
50
+ **"Is there anything known at RA 40.22898, Dec +12.601026?"**
51
+
52
+ ```text
53
+ whats_here(40.22898, 12.601026)
54
+ → verdict: "known transient"
55
+ evidence: ["TNS: AT 2026agtq at 0.0\"", "ZTF: ZTF26acatcla ndet=10 class=None"]
56
+ ```
57
+
58
+ **"What is at the position of TOI-2330?"**
59
+
60
+ ```text
61
+ whats_here(172.85872, -41.68953)
62
+ → verdict: "known TESS object of interest"
63
+ labels: ["known TESS object of interest", "known variable", "star"]
64
+ evidence: ["SIMBAD: WASP-128 [*] at 0.01\"", "SIMBAD: WASP-128b [BD?] at 0.01\"", "VSX: WASP-128 (EA/BD+ROT) at 0.04\"", ...]
65
+ ```
66
+
67
+ The assistant can then answer: *"That's TOI-2330, already known as WASP-128 b, a brown dwarf transiting WASP-128."*
68
+
69
+ **An empty patch of sky:**
70
+
71
+ ```text
72
+ whats_here(200.5, -20.3, 3)
73
+ → verdict: "nothing known", sources_failed: []
74
+ ```
75
+
76
+ **"What should I observe tonight from Curitiba?"**
77
+
78
+ ```text
79
+ observe_tonight(lat=-25.43, lon=-49.27)
80
+ → night_utc: ["2026-10-26T22:27Z", "2026-10-27T07:37Z"], moon illumination 0.99, 33 matching targets
81
+ 1. AT 2026afvp mag 10.3, 8.1 d old, max alt 40.9°, window 03:57–07:37 UTC
82
+ 2. AT 2026agsu mag 17.27, 2.2 d old, max alt 82.8°, window 22:27–02:37 UTC
83
+ ...
84
+ ```
85
+
86
+ `observe_tonight` selects transients up to 21 days old, TNS discovery mag ≤ 18.5, still unclassified, above 30°
87
+ while the Sun is below −12° and at least 20° from the Moon (all adjustable). Sun/Moon positions use the
88
+ Astronomical Almanac low-precision formulas (arcminute level). It is meant to help small telescopes and
89
+ spectroscopy groups pick classification targets.
90
+
91
+ ## Install
92
+
93
+ Requires Python 3.11+.
94
+
95
+ ```bash
96
+ uvx astro-checker # run without installing (recommended; needs uv)
97
+ pip install astro-checker # or install into an environment, then run `astro-checker` (alias: `astromcp`)
98
+ ```
99
+
100
+ Or with Docker (from a clone): `docker build -t astromcp . && docker run -i --rm astromcp`.
101
+ See [`.env.example`](.env.example) for the optional variables.
102
+
103
+ The server speaks MCP over stdio; you normally don't run it by hand, your MCP client launches it.
104
+
105
+ ## Configure your MCP client
106
+
107
+ ### Claude Code
108
+
109
+ ```bash
110
+ claude mcp add astromcp -- uvx astro-checker
111
+ # with TNS credentials:
112
+ claude mcp add astromcp -e TNS_BOT_ID=12345 -e TNS_BOT_NAME=my_bot -e TNS_API_KEY=... -- uvx astro-checker
113
+ ```
114
+
115
+ Add `--scope user` to make it available in every project.
116
+
117
+ ### Claude Desktop
118
+
119
+ Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`; Windows: `%APPDATA%\Claude\`):
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "astromcp": {
125
+ "command": "uvx",
126
+ "args": ["astromcp"],
127
+ "env": { "TNS_BOT_ID": "12345", "TNS_BOT_NAME": "my_bot", "TNS_API_KEY": "..." }
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ The `env` block is optional.
134
+
135
+ ### Other clients (Cursor, VS Code, Zed, Continue, ...)
136
+
137
+ Any client that launches stdio MCP servers works with command `uvx` and args `["astromcp"]`, or command
138
+ `astromcp` if you installed it with pip. Try it interactively with the MCP Inspector:
139
+
140
+ ```bash
141
+ npx @modelcontextprotocol/inspector uvx astro-checker
142
+ ```
143
+
144
+ ## Environment variables
145
+
146
+ | Variable | Default | Meaning |
147
+ |---|---|---|
148
+ | `TNS_BOT_ID`, `TNS_BOT_NAME`, `TNS_API_KEY` | unset | Optional TNS **bot** credentials ([create a bot](https://www.wis-tns.org/bots)) for live TNS queries |
149
+ | `TNS_PUBLIC_CSV` | unset | Path to a downloaded `tns_public_objects.csv.zip`, used when there are no credentials |
150
+ | `ASTROMCP_CACHE_DIR` | `~/.cache/astromcp` | Disk cache (HTTP responses for minutes to hours; ExoFOP tables 12 h; TNS archive 20 h) |
151
+
152
+ Without TNS credentials, TNS answers come from the daily public objects archive (cached copy or `TNS_PUBLIC_CSV`);
153
+ objects reported since 00:00 UTC are missing from it, and `whats_here` flags this instead of returning a clean
154
+ "nothing known". The API key is never returned in responses, written to logs, or stored in the cache.
155
+
156
+ ## Rate limits and good citizenship
157
+
158
+ These archives are free public services run by small teams. AstroMCP is built to be polite:
159
+
160
+ - TNS cone searches are serialized and spaced 6.5 s apart (TNS allows 10 per minute); `Retry-After` is honored on HTTP 429.
161
+ - Every request has a timeout, retries with backoff, and a per-host circuit breaker: when a host fails
162
+ repeatedly it is skipped for two minutes instead of stalling every query.
163
+ - Large tables (ExoFOP TOI/CTOI, TNS public archive) are downloaded once and cached on disk.
164
+
165
+ Please do the same: don't run `whats_here` over thousands of positions in a loop (use the archives' bulk
166
+ services for that), keep your own TNS bot for live queries, and credit the archives in publications
167
+ (TNS, SIMBAD/CDS, AAVSO VSX, ESA Gaia, ExoFOP-TESS, ALeRCE, Fink, IMCCE, Legacy Survey, Pan-STARRS).
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ git clone https://github.com/louisperner/AstroMCP && cd AstroMCP
173
+ uv sync
174
+ uv run pytest # offline tests (recorded responses in tests/fixtures)
175
+ uv run pytest -m integration # live queries against the real archives (TNS test needs credentials)
176
+ uv run ruff check . && uv run ruff format --check . && uv run mypy --strict
177
+ ```
178
+
179
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md).
180
+
181
+ ### Publishing to the MCP Registry (maintainers)
182
+
183
+ `server.json` describes the PyPI package for the official [MCP Registry](https://github.com/modelcontextprotocol/registry).
184
+ After the version is on PyPI (the `mcp-name` comment above must be in the published README):
185
+
186
+ ```bash
187
+ brew install mcp-publisher # or download a release binary
188
+ mcp-publisher login github # authenticates the io.github.louisperner namespace
189
+ mcp-publisher publish # reads ./server.json
190
+ ```
191
+
192
+ Bump `version` in `pyproject.toml`, `src/astromcp/__init__.py` and both places in `server.json` together.
193
+
194
+ ## Em português
195
+
196
+ O AstroMCP é um servidor MCP que dá a assistentes de IA acesso direto (somente leitura) a bases públicas de
197
+ astronomia — TNS, SIMBAD, VSX, Gaia DR3, ExoFOP-TESS, ALeRCE/ZTF, Fink/Rubin, SkyBoT e cutouts de imagem —
198
+ para verificar rapidamente se um objeto já é conhecido. Comece por `whats_here(ra, dec)`; para planejar a noite,
199
+ `observe_tonight(lat, lon)`. Instale com `uvx astro-checker` ou `pip install astro-checker` e, no Claude Code,
200
+ `claude mcp add astromcp -- uvx astro-checker`. Credenciais do bot do TNS (opcionais) vão nas variáveis
201
+ `TNS_BOT_ID`, `TNS_BOT_NAME` e `TNS_API_KEY` e nunca aparecem nas respostas. As respostas dependem de serviços
202
+ externos: confirme na fonte antes de anunciar uma descoberta.
203
+
204
+ ## License
205
+
206
+ [MIT](LICENSE) © 2026 Louis Perner
@@ -0,0 +1,74 @@
1
+ [project]
2
+ name = "astro-checker"
3
+ version = "0.1.0"
4
+ description = "MCP server giving AI assistants direct access to public astronomy archives (TNS, SIMBAD, VSX, Gaia, ExoFOP, ALeRCE/ZTF, Fink/Rubin, SkyBoT)"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Louis Perner", email = "31986061+louisperner@users.noreply.github.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "mcp>=2.3.0",
12
+ ]
13
+
14
+ license = "MIT"
15
+ license-files = ["LICENSE"]
16
+ keywords = ["mcp", "model-context-protocol", "astronomy", "astrophysics", "transients", "tns", "simbad", "gaia", "tess", "ztf", "rubin", "llm", "claude"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Science/Research",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Scientific/Engineering :: Astronomy",
29
+ "Typing :: Typed",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/louisperner/AstroMCP"
34
+ Repository = "https://github.com/louisperner/AstroMCP"
35
+ Issues = "https://github.com/louisperner/AstroMCP/issues"
36
+ Changelog = "https://github.com/louisperner/AstroMCP/blob/main/CHANGELOG.md"
37
+
38
+ [project.scripts]
39
+ astro-checker = "astromcp.server:main"
40
+ astromcp = "astromcp.server:main"
41
+
42
+ [build-system]
43
+ requires = ["uv_build>=0.11.7,<0.12.0"]
44
+ build-backend = "uv_build"
45
+
46
+ [dependency-groups]
47
+ dev = [
48
+ "mypy>=2.4.0",
49
+ "pytest>=9.1.1",
50
+ "pytest-asyncio>=1.4.0",
51
+ "ruff>=0.17.2",
52
+ ]
53
+
54
+ [tool.ruff]
55
+ line-length = 130
56
+ target-version = "py311"
57
+
58
+ [tool.ruff.lint]
59
+ select = ["E", "F", "W", "I", "B", "UP"]
60
+ ignore = ["E501", "B905"]
61
+
62
+ [tool.mypy]
63
+ strict = true
64
+ files = ["src"]
65
+
66
+ [tool.pytest.ini_options]
67
+ testpaths = ["tests"]
68
+ markers = ["integration: queries the real archives (run with: pytest -m integration)"]
69
+ addopts = "-m 'not integration'"
70
+ asyncio_mode = "auto"
71
+ pythonpath = ["tests"]
72
+
73
+ [tool.uv.build-backend]
74
+ module-name = "astromcp"
@@ -0,0 +1,3 @@
1
+ """AstroMCP: an MCP server giving AI assistants direct access to public astronomy archives."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from .server import main
2
+
3
+ main()