astrodynamics-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 (151) hide show
  1. astrodynamics_mcp-0.1.0/.github/CODEOWNERS +2 -0
  2. astrodynamics_mcp-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +75 -0
  3. astrodynamics_mcp-0.1.0/.github/ISSUE_TEMPLATE/client-compatibility.yml +81 -0
  4. astrodynamics_mcp-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. astrodynamics_mcp-0.1.0/.github/ISSUE_TEMPLATE/tool-request.yml +54 -0
  6. astrodynamics_mcp-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +35 -0
  7. astrodynamics_mcp-0.1.0/.github/workflows/ci.yml +107 -0
  8. astrodynamics_mcp-0.1.0/.github/workflows/docs.yml +57 -0
  9. astrodynamics_mcp-0.1.0/.github/workflows/eval.yml +159 -0
  10. astrodynamics_mcp-0.1.0/.github/workflows/release.yml +74 -0
  11. astrodynamics_mcp-0.1.0/.gitignore +43 -0
  12. astrodynamics_mcp-0.1.0/.python-version +1 -0
  13. astrodynamics_mcp-0.1.0/CHANGELOG.md +99 -0
  14. astrodynamics_mcp-0.1.0/CONTRIBUTING.md +93 -0
  15. astrodynamics_mcp-0.1.0/LICENSE +21 -0
  16. astrodynamics_mcp-0.1.0/PKG-INFO +187 -0
  17. astrodynamics_mcp-0.1.0/README.md +146 -0
  18. astrodynamics_mcp-0.1.0/docs/_hooks.py +70 -0
  19. astrodynamics_mcp-0.1.0/docs/api.md +60 -0
  20. astrodynamics_mcp-0.1.0/docs/data-sources.md +125 -0
  21. astrodynamics_mcp-0.1.0/docs/eval-suite.md +97 -0
  22. astrodynamics_mcp-0.1.0/docs/faq.md +124 -0
  23. astrodynamics_mcp-0.1.0/docs/getting-started.md +115 -0
  24. astrodynamics_mcp-0.1.0/docs/index.md +76 -0
  25. astrodynamics_mcp-0.1.0/docs/output-shaping.md +76 -0
  26. astrodynamics_mcp-0.1.0/docs/pick-a-client.md +128 -0
  27. astrodynamics_mcp-0.1.0/docs/recipes.md +143 -0
  28. astrodynamics_mcp-0.1.0/docs/supported-clients.md +50 -0
  29. astrodynamics_mcp-0.1.0/docs/tool-reference.md +29 -0
  30. astrodynamics_mcp-0.1.0/eval/README.md +390 -0
  31. astrodynamics_mcp-0.1.0/eval/__init__.py +5 -0
  32. astrodynamics_mcp-0.1.0/eval/_ci_report.py +240 -0
  33. astrodynamics_mcp-0.1.0/eval/_constraints.py +306 -0
  34. astrodynamics_mcp-0.1.0/eval/_functional.py +410 -0
  35. astrodynamics_mcp-0.1.0/eval/_prompts.py +160 -0
  36. astrodynamics_mcp-0.1.0/eval/prompts/access_windows_iss_explicit_coords_range_filter.yaml +23 -0
  37. astrodynamics_mcp-0.1.0/eval/prompts/access_windows_iss_madrid.yaml +31 -0
  38. astrodynamics_mcp-0.1.0/eval/prompts/bplane_target_mars_approach_readonly.yaml +28 -0
  39. astrodynamics_mcp-0.1.0/eval/prompts/bplane_target_mars_btr_with_dv.yaml +22 -0
  40. astrodynamics_mcp-0.1.0/eval/prompts/frame_transform_icrf_to_itrs_with_iers.yaml +18 -0
  41. astrodynamics_mcp-0.1.0/eval/prompts/frame_transform_teme_to_icrf.yaml +24 -0
  42. astrodynamics_mcp-0.1.0/eval/prompts/lambert_earth_orbit_curtis_5_2.yaml +31 -0
  43. astrodynamics_mcp-0.1.0/eval/prompts/lambert_gooding_algorithm.yaml +23 -0
  44. astrodynamics_mcp-0.1.0/eval/prompts/lambert_multi_revolution.yaml +20 -0
  45. astrodynamics_mcp-0.1.0/eval/prompts/planning_hubble_madrid_passes.yaml +32 -0
  46. astrodynamics_mcp-0.1.0/eval/prompts/planning_mars_2028_transfer.yaml +28 -0
  47. astrodynamics_mcp-0.1.0/eval/prompts/porkchop_earth_mars_2026.yaml +27 -0
  48. astrodynamics_mcp-0.1.0/eval/prompts/porkchop_earth_venus_2027.yaml +22 -0
  49. astrodynamics_mcp-0.1.0/eval/prompts/sequential_hubble_lookup_then_goldstone_access.yaml +30 -0
  50. astrodynamics_mcp-0.1.0/eval/prompts/sequential_lambert_then_bplane_jupiter.yaml +26 -0
  51. astrodynamics_mcp-0.1.0/eval/prompts/sequential_lambert_then_frame_transform_itrs.yaml +24 -0
  52. astrodynamics_mcp-0.1.0/eval/prompts/sequential_porkchop_then_lambert.yaml +27 -0
  53. astrodynamics_mcp-0.1.0/eval/prompts/sequential_sgp4_then_frame_to_itrs.yaml +25 -0
  54. astrodynamics_mcp-0.1.0/eval/prompts/sequential_time_convert_utc_tai_roundtrip.yaml +22 -0
  55. astrodynamics_mcp-0.1.0/eval/prompts/sequential_tle_lookup_sgp4_frame_three_step.yaml +27 -0
  56. astrodynamics_mcp-0.1.0/eval/prompts/sequential_tle_lookup_then_sgp4_24h.yaml +27 -0
  57. astrodynamics_mcp-0.1.0/eval/prompts/sgp4_propagate_iss_default_teme.yaml +26 -0
  58. astrodynamics_mcp-0.1.0/eval/prompts/sgp4_propagate_iss_gcrs_explicit_frame.yaml +22 -0
  59. astrodynamics_mcp-0.1.0/eval/prompts/sgp4_propagate_iss_multiple_epochs.yaml +28 -0
  60. astrodynamics_mcp-0.1.0/eval/prompts/time_convert_utc_to_jd_format.yaml +19 -0
  61. astrodynamics_mcp-0.1.0/eval/prompts/time_convert_utc_to_tai.yaml +18 -0
  62. astrodynamics_mcp-0.1.0/eval/prompts/time_convert_utc_to_ut1_with_iers.yaml +23 -0
  63. astrodynamics_mcp-0.1.0/eval/prompts/tle_lookup_hubble_by_name.yaml +20 -0
  64. astrodynamics_mcp-0.1.0/eval/prompts/tle_lookup_iss_by_norad_id.yaml +18 -0
  65. astrodynamics_mcp-0.1.0/eval/prompts/tle_lookup_weather_group.yaml +18 -0
  66. astrodynamics_mcp-0.1.0/eval/scoring.py +171 -0
  67. astrodynamics_mcp-0.1.0/eval/tasks.py +145 -0
  68. astrodynamics_mcp-0.1.0/examples/01_hohmann_dv.md +106 -0
  69. astrodynamics_mcp-0.1.0/examples/02_hubble_passes_madrid.md +160 -0
  70. astrodynamics_mcp-0.1.0/examples/03_mars_launch_window_2028.md +130 -0
  71. astrodynamics_mcp-0.1.0/examples/README.md +60 -0
  72. astrodynamics_mcp-0.1.0/examples/__init__.py +1 -0
  73. astrodynamics_mcp-0.1.0/examples/_fixtures.py +272 -0
  74. astrodynamics_mcp-0.1.0/examples/run_example_01_hohmann.py +117 -0
  75. astrodynamics_mcp-0.1.0/examples/run_example_02_hubble_passes.py +109 -0
  76. astrodynamics_mcp-0.1.0/examples/run_example_03_mars_launch_window.py +91 -0
  77. astrodynamics_mcp-0.1.0/mkdocs.yml +76 -0
  78. astrodynamics_mcp-0.1.0/pyproject.toml +163 -0
  79. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/__init__.py +15 -0
  80. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/cache.py +214 -0
  81. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/cli.py +130 -0
  82. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/data/__init__.py +27 -0
  83. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/data/celestrak.py +269 -0
  84. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/data/horizons.py +159 -0
  85. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/data/iers.py +81 -0
  86. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/errors.py +138 -0
  87. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/py.typed +0 -0
  88. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/schemas/__init__.py +35 -0
  89. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/schemas/base.py +493 -0
  90. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/server.py +116 -0
  91. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/server_lint.py +137 -0
  92. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/__init__.py +14 -0
  93. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/_astropy_frames.py +109 -0
  94. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/access.py +321 -0
  95. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/bplane.py +392 -0
  96. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/frames.py +176 -0
  97. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/lambert.py +411 -0
  98. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/porkchop.py +576 -0
  99. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/propagation.py +211 -0
  100. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/time.py +263 -0
  101. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/tools/tle.py +127 -0
  102. astrodynamics_mcp-0.1.0/src/astrodynamics_mcp/units.py +321 -0
  103. astrodynamics_mcp-0.1.0/tests/__init__.py +0 -0
  104. astrodynamics_mcp-0.1.0/tests/_regenerate_goldens.py +55 -0
  105. astrodynamics_mcp-0.1.0/tests/_sample_calls.py +440 -0
  106. astrodynamics_mcp-0.1.0/tests/conftest.py +3 -0
  107. astrodynamics_mcp-0.1.0/tests/data/golden/access_windows.json +154 -0
  108. astrodynamics_mcp-0.1.0/tests/data/golden/bplane_target.json +24 -0
  109. astrodynamics_mcp-0.1.0/tests/data/golden/frame_transform.json +23 -0
  110. astrodynamics_mcp-0.1.0/tests/data/golden/lambert_solve.json +96 -0
  111. astrodynamics_mcp-0.1.0/tests/data/golden/porkchop.json +150 -0
  112. astrodynamics_mcp-0.1.0/tests/data/golden/sgp4_iss_24h.json +2904 -0
  113. astrodynamics_mcp-0.1.0/tests/data/golden/sgp4_propagate.json +44 -0
  114. astrodynamics_mcp-0.1.0/tests/data/golden/time_convert.json +7 -0
  115. astrodynamics_mcp-0.1.0/tests/data/golden/tle_lookup.json +31 -0
  116. astrodynamics_mcp-0.1.0/tests/data/golden/tle_lookup_iss.json +31 -0
  117. astrodynamics_mcp-0.1.0/tests/test_cache.py +364 -0
  118. astrodynamics_mcp-0.1.0/tests/test_cli.py +123 -0
  119. astrodynamics_mcp-0.1.0/tests/test_cli_http_smoke.py +112 -0
  120. astrodynamics_mcp-0.1.0/tests/test_data_celestrak.py +518 -0
  121. astrodynamics_mcp-0.1.0/tests/test_data_horizons.py +335 -0
  122. astrodynamics_mcp-0.1.0/tests/test_data_iers.py +101 -0
  123. astrodynamics_mcp-0.1.0/tests/test_data_source_failure_modes.py +144 -0
  124. astrodynamics_mcp-0.1.0/tests/test_errors.py +177 -0
  125. astrodynamics_mcp-0.1.0/tests/test_eval_ci_report.py +284 -0
  126. astrodynamics_mcp-0.1.0/tests/test_eval_constraints.py +176 -0
  127. astrodynamics_mcp-0.1.0/tests/test_eval_coverage.py +105 -0
  128. astrodynamics_mcp-0.1.0/tests/test_eval_functional.py +223 -0
  129. astrodynamics_mcp-0.1.0/tests/test_eval_prompts.py +125 -0
  130. astrodynamics_mcp-0.1.0/tests/test_eval_scoring.py +292 -0
  131. astrodynamics_mcp-0.1.0/tests/test_examples.py +52 -0
  132. astrodynamics_mcp-0.1.0/tests/test_frame_unit_equivalence.py +141 -0
  133. astrodynamics_mcp-0.1.0/tests/test_import.py +13 -0
  134. astrodynamics_mcp-0.1.0/tests/test_reference_output.py +118 -0
  135. astrodynamics_mcp-0.1.0/tests/test_schemas_base.py +433 -0
  136. astrodynamics_mcp-0.1.0/tests/test_server.py +238 -0
  137. astrodynamics_mcp-0.1.0/tests/test_tool_access_windows.py +479 -0
  138. astrodynamics_mcp-0.1.0/tests/test_tool_bplane_target.py +504 -0
  139. astrodynamics_mcp-0.1.0/tests/test_tool_description_lint.py +198 -0
  140. astrodynamics_mcp-0.1.0/tests/test_tool_frame_transform.py +320 -0
  141. astrodynamics_mcp-0.1.0/tests/test_tool_lambert_solve.py +422 -0
  142. astrodynamics_mcp-0.1.0/tests/test_tool_output_roundtrip.py +72 -0
  143. astrodynamics_mcp-0.1.0/tests/test_tool_porkchop.py +956 -0
  144. astrodynamics_mcp-0.1.0/tests/test_tool_sgp4_propagate.py +374 -0
  145. astrodynamics_mcp-0.1.0/tests/test_tool_time_convert.py +407 -0
  146. astrodynamics_mcp-0.1.0/tests/test_tool_tle.py +276 -0
  147. astrodynamics_mcp-0.1.0/tests/test_transport_equivalence.py +229 -0
  148. astrodynamics_mcp-0.1.0/tests/test_unit_discipline.py +277 -0
  149. astrodynamics_mcp-0.1.0/tests/test_units.py +176 -0
  150. astrodynamics_mcp-0.1.0/tests/test_upstream_pins.py +100 -0
  151. astrodynamics_mcp-0.1.0/uv.lock +4300 -0
@@ -0,0 +1,2 @@
1
+ # Default reviewer for everything in this repo.
2
+ * @djankov
@@ -0,0 +1,75 @@
1
+ name: Bug
2
+ description: Report a defect in astrodynamics-mcp.
3
+ title: "<short summary>"
4
+ labels: ["type:bug"]
5
+ body:
6
+ - type: textarea
7
+ id: what_happened
8
+ attributes:
9
+ label: What happened
10
+ description: Observed behavior. Include error messages, MCP error envelopes, and tracebacks verbatim.
11
+ validations:
12
+ required: true
13
+ - type: textarea
14
+ id: what_expected
15
+ attributes:
16
+ label: What you expected
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: repro
21
+ attributes:
22
+ label: Minimal reproduction
23
+ description: Smallest snippet that triggers the bug. A tool name plus the arguments you called it with (or the natural-language prompt that produced the broken call) is ideal.
24
+ render: python
25
+ validations:
26
+ required: true
27
+ - type: input
28
+ id: server_version
29
+ attributes:
30
+ label: astrodynamics-mcp version
31
+ placeholder: "e.g. 0.1.0, or git sha"
32
+ validations:
33
+ required: true
34
+ - type: dropdown
35
+ id: transport
36
+ attributes:
37
+ label: Transport
38
+ options:
39
+ - stdio
40
+ - http
41
+ - Both
42
+ validations:
43
+ required: true
44
+ - type: input
45
+ id: client
46
+ attributes:
47
+ label: MCP client
48
+ description: Which client surfaced the bug, and its version.
49
+ placeholder: "e.g. Claude Code 2.4.1, Cursor 0.45, raw Python script using `mcp` SDK"
50
+ validations:
51
+ required: true
52
+ - type: input
53
+ id: python_version
54
+ attributes:
55
+ label: Python version
56
+ placeholder: "e.g. 3.12.3"
57
+ validations:
58
+ required: true
59
+ - type: dropdown
60
+ id: os
61
+ attributes:
62
+ label: Operating system
63
+ options:
64
+ - Linux
65
+ - Windows
66
+ - macOS
67
+ - Other
68
+ validations:
69
+ required: true
70
+ - type: textarea
71
+ id: logs
72
+ attributes:
73
+ label: Logs
74
+ description: Server stderr, MCP error payloads, client-side traces.
75
+ render: shell
@@ -0,0 +1,81 @@
1
+ name: Client compatibility report
2
+ description: Tell us how astrodynamics-mcp works (or doesn't) inside a specific MCP client.
3
+ title: "<client name + version>: <one-line outcome>"
4
+ labels: ["type:client-compatibility"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Use this template to report on a specific MCP client's behaviour:
10
+ whether stdio / http transports work, whether tools are visible, whether
11
+ tool descriptions and units survive the round-trip, and any client-side
12
+ quirks. These reports populate the "supported clients" matrix in the
13
+ docs. Even a successful "everything works" report is welcome.
14
+ - type: input
15
+ id: client
16
+ attributes:
17
+ label: MCP client and version
18
+ placeholder: "e.g. Claude Code 2.4.1, Cursor 0.45, ChatGPT desktop 1.2024.345, custom LangGraph agent on mcp 1.27.0"
19
+ validations:
20
+ required: true
21
+ - type: input
22
+ id: server_version
23
+ attributes:
24
+ label: astrodynamics-mcp version
25
+ placeholder: "e.g. 0.1.0, or git sha"
26
+ validations:
27
+ required: true
28
+ - type: dropdown
29
+ id: transport
30
+ attributes:
31
+ label: Transport used
32
+ options:
33
+ - stdio
34
+ - http (Streamable HTTP)
35
+ - http (SSE — deprecated, backward-compat only)
36
+ validations:
37
+ required: true
38
+ - type: dropdown
39
+ id: os
40
+ attributes:
41
+ label: Operating system
42
+ options:
43
+ - Linux
44
+ - Windows
45
+ - macOS
46
+ - Other
47
+ validations:
48
+ required: true
49
+ - type: dropdown
50
+ id: outcome
51
+ attributes:
52
+ label: Overall outcome
53
+ options:
54
+ - "Works — tools visible, calls succeed, results render."
55
+ - "Partial — some tools or call patterns are broken."
56
+ - "Broken — server doesn't load, or no tools visible to the client."
57
+ validations:
58
+ required: true
59
+ - type: textarea
60
+ id: setup
61
+ attributes:
62
+ label: Setup
63
+ description: Paste the exact MCP server config block (with secrets redacted).
64
+ render: json
65
+ validations:
66
+ required: true
67
+ - type: textarea
68
+ id: tools_observed
69
+ attributes:
70
+ label: Tools the client sees
71
+ description: List which of the v0.1 tools the client surfaces (or "all 8" / "none"). If the client doesn't surface a `tools/list`, paste the closest observable equivalent.
72
+ - type: textarea
73
+ id: tools_called
74
+ attributes:
75
+ label: Tools you actually called
76
+ description: One or two example tool calls you ran end-to-end. Include the natural-language prompt that triggered each.
77
+ - type: textarea
78
+ id: problems
79
+ attributes:
80
+ label: Problems observed
81
+ description: Quirks, edge cases, broken units, awkward error rendering, slow timeouts — anything that worked but felt wrong.
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Question or discussion
4
+ url: https://github.com/orgs/astro-tools/discussions
5
+ about: Open a discussion for usage questions, ideas, or general chat. The astro-tools org runs a single shared discussions space — there is no per-repo discussions board.
@@ -0,0 +1,54 @@
1
+ name: Tool request
2
+ description: I want astrodynamics-mcp to compute X — does it fit?
3
+ title: "<short summary of the X you want>"
4
+ labels: ["type:tool-request"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ astrodynamics-mcp wraps vetted upstream astrodynamics libraries (sgp4,
10
+ lamberthub, skyfield, astropy, …) so LLM clients can call them as tools.
11
+ Before filing, please skim `CONTRIBUTING.md` — the "Scope discipline"
12
+ section names the libraries we wrap and the orthogonal projects that
13
+ own neighbouring surfaces. If your need is squarely in one of those
14
+ adjacent projects, you'll get a faster answer there.
15
+ - type: textarea
16
+ id: what_i_want
17
+ attributes:
18
+ label: What you want to compute
19
+ description: One or two sentences. What's the natural-language question you'd ask an LLM client that should drop into a tool call?
20
+ placeholder: "e.g. 'For a TLE I provide, when is the next eclipse of the satellite by Earth?'"
21
+ validations:
22
+ required: true
23
+ - type: textarea
24
+ id: example_session
25
+ attributes:
26
+ label: Example session
27
+ description: How would you prompt an MCP-enabled client (Claude Code, Cursor, ChatGPT desktop, …) to invoke this? What inputs / units / outputs do you expect?
28
+ placeholder: |
29
+ > User: "When does Hubble enter eclipse during the next 7 days?"
30
+ > # LLM should call: eclipse_predict(tle=..., start=..., duration_days=7)
31
+ > # Expected output: list of {entry_utc, exit_utc, duration_s, type: umbra|penumbra}
32
+ validations:
33
+ required: true
34
+ - type: textarea
35
+ id: upstream_library
36
+ attributes:
37
+ label: Upstream library that could back this
38
+ description: Which existing Python library (or libraries) implements the computation? If unsure, say so — we'll dig.
39
+ placeholder: "e.g. skyfield has eclipse helpers via dark_twilight_day; astropy.coordinates.get_sun + custom shadow geometry."
40
+ - type: textarea
41
+ id: what_i_tried
42
+ attributes:
43
+ label: What you tried instead
44
+ description: Did you try to use an existing tool (poorly) or a different MCP server? What broke or felt wrong?
45
+ - type: dropdown
46
+ id: priority
47
+ attributes:
48
+ label: How important is this to you
49
+ options:
50
+ - "Blocker — I can't use astrodynamics-mcp for my work without it."
51
+ - "Strong wish — I have a workaround but it's awkward."
52
+ - "Nice to have — drop into the backlog and ship when convenient."
53
+ validations:
54
+ required: true
@@ -0,0 +1,35 @@
1
+ <!--
2
+ Thanks for contributing to astrodynamics-mcp. Keep PRs small and scoped to one issue.
3
+ -->
4
+
5
+ ## Summary
6
+
7
+ <!-- One or two sentences: what this PR changes and why. -->
8
+
9
+ Closes #
10
+
11
+ ## Changes
12
+
13
+ <!-- Short bullet list of the substantive changes. Omit trivia (formatting, imports). -->
14
+
15
+ -
16
+
17
+ ## Tested with
18
+
19
+ <!-- Tick what you ran locally. CI will re-run on both Ubuntu and Windows. -->
20
+
21
+ - [ ] `pytest`
22
+ - [ ] `ruff check`
23
+ - [ ] `ruff format --check`
24
+ - [ ] `mypy`
25
+ - [ ] End-to-end smoke against an MCP client (if this PR touches a tool, transport, or schema)
26
+
27
+ ## Breaking changes
28
+
29
+ <!-- "None" if none. Otherwise: what breaks, who is affected, migration notes. -->
30
+
31
+ None.
32
+
33
+ ## Notes for reviewers
34
+
35
+ <!-- Optional. Things to look at first, known rough edges, questions for the reviewer. -->
@@ -0,0 +1,107 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ci-${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
11
+
12
+ jobs:
13
+ test:
14
+ name: test (${{ matrix.os }}, py${{ matrix.python-version }})
15
+ runs-on: ${{ matrix.os }}
16
+ timeout-minutes: 30
17
+ strategy:
18
+ fail-fast: false
19
+ # Ubuntu + Windows × 3 Python minors = 6 cells. macOS is deferred
20
+ # to v0.2 per the project charter.
21
+ matrix:
22
+ os: [ubuntu-latest, windows-latest]
23
+ python-version: ['3.10', '3.11', '3.12']
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - name: Set up Python ${{ matrix.python-version }}
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v4
34
+ with:
35
+ enable-cache: true
36
+ cache-dependency-glob: uv.lock
37
+
38
+ - name: Install project
39
+ run: uv sync --all-groups
40
+
41
+ - name: Run pytest
42
+ # `-m "integration or not integration"` overrides the default
43
+ # marker filter in pyproject.toml so CI runs both unit and
44
+ # integration suites in a single invocation.
45
+ run: uv run pytest -m "integration or not integration" --cov --cov-report=term-missing
46
+
47
+ - name: Enforce coverage gates
48
+ # Per CONTRIBUTING.md: 90% overall, 95% on the layers whose
49
+ # correctness the LLM consumer can't sanity-check (units,
50
+ # schemas, data adapters). Run only on Ubuntu / Python 3.12 to
51
+ # avoid platform-specific differences inflating misses.
52
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
53
+ run: |
54
+ uv run coverage report --fail-under=90
55
+ uv run coverage report --include='src/astrodynamics_mcp/units.py' --fail-under=95
56
+ uv run coverage report --include='src/astrodynamics_mcp/schemas/*' --fail-under=95
57
+ uv run coverage report --include='src/astrodynamics_mcp/data/*' --fail-under=95
58
+
59
+ lint:
60
+ name: lint
61
+ runs-on: ubuntu-latest
62
+ timeout-minutes: 5
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+ - uses: actions/setup-python@v5
66
+ with:
67
+ python-version: '3.12'
68
+ - uses: astral-sh/setup-uv@v4
69
+ with:
70
+ enable-cache: true
71
+ cache-dependency-glob: uv.lock
72
+ - run: uv sync --all-groups
73
+ - run: uv run ruff check
74
+ - run: uv run ruff format --check
75
+
76
+ typecheck:
77
+ name: typecheck
78
+ runs-on: ubuntu-latest
79
+ timeout-minutes: 10
80
+ steps:
81
+ - uses: actions/checkout@v4
82
+ - uses: actions/setup-python@v5
83
+ with:
84
+ python-version: '3.12'
85
+ - uses: astral-sh/setup-uv@v4
86
+ with:
87
+ enable-cache: true
88
+ cache-dependency-glob: uv.lock
89
+ - run: uv sync --all-groups
90
+ - run: uv run mypy
91
+
92
+ # Smoke-check that `pip install astrodynamics-mcp` (no extras, no dev group)
93
+ # imports cleanly and the console script resolves. Catches an extras-only
94
+ # dependency leaking into the default install path. The full test job above
95
+ # exercises every dev dep via the dev group; this is a small canary.
96
+ minimal-install:
97
+ name: minimal install smoke
98
+ runs-on: ubuntu-latest
99
+ timeout-minutes: 5
100
+ steps:
101
+ - uses: actions/checkout@v4
102
+ - uses: actions/setup-python@v5
103
+ with:
104
+ python-version: '3.12'
105
+ - run: pip install .
106
+ - run: python -c "import astrodynamics_mcp"
107
+ - run: astrodynamics-mcp --version
@@ -0,0 +1,57 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ['v*']
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: docs-${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
13
+
14
+ jobs:
15
+ build:
16
+ name: build
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 5
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: '3.12'
24
+ - uses: astral-sh/setup-uv@v4
25
+ with:
26
+ enable-cache: true
27
+ cache-dependency-glob: uv.lock
28
+ - run: uv sync --all-groups
29
+ - run: uv run mkdocs build --strict
30
+
31
+ deploy:
32
+ name: deploy
33
+ needs: build
34
+ if: |
35
+ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
36
+ github.event_name == 'workflow_dispatch'
37
+ runs-on: ubuntu-latest
38
+ timeout-minutes: 5
39
+ permissions:
40
+ contents: write
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ with:
44
+ fetch-depth: 0
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: '3.12'
48
+ - uses: astral-sh/setup-uv@v4
49
+ with:
50
+ enable-cache: true
51
+ cache-dependency-glob: uv.lock
52
+ - run: uv sync --all-groups
53
+ - name: Configure git identity
54
+ run: |
55
+ git config user.name "github-actions[bot]"
56
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
57
+ - run: uv run mkdocs gh-deploy --force --no-history
@@ -0,0 +1,159 @@
1
+ name: Eval
2
+
3
+ on:
4
+ # Manual-only for now while the gate is being shaken down. Once it
5
+ # passes consistently, add `push: branches: [main]` and a weekly
6
+ # cron so regressions surface without per-PR cost.
7
+ workflow_dispatch:
8
+ inputs:
9
+ tier:
10
+ description: "Optional tier filter (single_tool | sequential | planning). Empty runs the full suite."
11
+ required: false
12
+ default: ""
13
+ type: choice
14
+ options: ['', 'single_tool', 'sequential', 'planning']
15
+
16
+ concurrency:
17
+ group: eval-${{ github.workflow }}-${{ github.ref }}
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ eval:
22
+ name: eval
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 30
25
+ permissions:
26
+ # `models: read` lets the workflow call api.github.com/inference
27
+ # via the auto-generated GITHUB_TOKEN.
28
+ models: read
29
+ contents: read
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+
33
+ - uses: actions/setup-python@v5
34
+ with:
35
+ python-version: '3.12'
36
+
37
+ - uses: astral-sh/setup-uv@v4
38
+ with:
39
+ enable-cache: true
40
+ cache-dependency-glob: uv.lock
41
+
42
+ - run: uv sync --all-groups
43
+
44
+ - name: Probe GitHub Models auth
45
+ # Run a one-token call against the eval model before the full
46
+ # suite so any auth failure surfaces here as a clear HTTP status
47
+ # and error body, rather than buried inside the Inspect AI stack
48
+ # trace. Prefer the MODELS_PAT secret when present — on Free-plan
49
+ # orgs the workflow-issued GITHUB_TOKEN is an unreliable inference
50
+ # auth path (observed 403 and 429 without obvious cause). Use a
51
+ # personal-owned PAT with `Models: Read` for MODELS_PAT; org-owned
52
+ # PATs return 403 no_access from this endpoint regardless of
53
+ # granted permissions. See eval/README.md for provisioning.
54
+ #
55
+ # Retries with exponential backoff because GitHub's secondary
56
+ # anti-abuse rate limit returns 429 for ~5-30 min when the
57
+ # triggering account has been making many recent requests. The
58
+ # retry loop lets routine CI churn ride through it.
59
+ env:
60
+ MODELS_PAT: ${{ secrets.MODELS_PAT }}
61
+ WORKFLOW_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62
+ run: |
63
+ TOKEN="${MODELS_PAT:-$WORKFLOW_TOKEN}"
64
+ status=""
65
+ for delay in 0 30 60 120 180; do
66
+ [ "$delay" -gt 0 ] && { echo "Backing off ${delay}s before retry"; sleep "$delay"; }
67
+ status=$(curl -s -o /tmp/probe.json -w '%{http_code}' \
68
+ https://models.github.ai/inference/chat/completions \
69
+ -H "Authorization: Bearer $TOKEN" \
70
+ -H "Content-Type: application/json" \
71
+ -d '{"model":"openai/gpt-4.1-mini","messages":[{"role":"user","content":"PING"}],"max_tokens":2}')
72
+ echo "HTTP $status"
73
+ head -c 500 /tmp/probe.json; echo
74
+ case "$status" in
75
+ 200) break ;;
76
+ 429) continue ;;
77
+ *) echo "Non-retriable status $status"; break ;;
78
+ esac
79
+ done
80
+ test "$status" = "200"
81
+
82
+ - name: Run eval suite
83
+ env:
84
+ # The openai-api/github/* provider in Inspect AI derives env vars
85
+ # from the segment after the slash; here `github` maps to GITHUB_*.
86
+ # MODELS_PAT (if set) overrides the workflow-issued GITHUB_TOKEN —
87
+ # see the probe step above for the rationale.
88
+ GITHUB_API_KEY: ${{ secrets.MODELS_PAT || secrets.GITHUB_TOKEN }}
89
+ GITHUB_BASE_URL: https://models.github.ai/inference
90
+ run: |
91
+ # Flags explained:
92
+ # * -M strict_tools=false : pydantic unions in our schemas use
93
+ # anyOf + $ref, which OpenAI strict-mode tool-calling rejects.
94
+ # * --max-samples 3 : prompts processed concurrently. GH
95
+ # Models Free-plan Low-tier (gpt-4.1-mini) caps concurrent
96
+ # requests at 5; we sit 2 below the ceiling to absorb
97
+ # multi-turn tool-call bursts within each sample.
98
+ # * --max-connections 4 : max in-flight HTTP requests across
99
+ # all samples (covers retries + tool-call turns); 1 below
100
+ # the 5-concurrent ceiling so a transient retry can fire
101
+ # without hitting the cap.
102
+ # * --no-fail-on-error : let every sample run to completion
103
+ # even when others error. Errored samples count against
104
+ # accuracy in eval/_ci_report.py (zero-scored), so the
105
+ # gate still fails them; we just don't want to lose data
106
+ # from in-flight samples being cancelled when an error
107
+ # threshold trips. Inspect AI's default ('fail on any
108
+ # error') is too aggressive for a survey gate.
109
+ if [ -n "${{ inputs.tier }}" ]; then
110
+ uv run inspect eval eval/tasks.py@astrodynamics_mcp_eval_subset \
111
+ -T tier="${{ inputs.tier }}" \
112
+ --model openai-api/github/openai/gpt-4.1-mini \
113
+ -M strict_tools=false \
114
+ --max-samples 3 \
115
+ --max-connections 4 \
116
+ --no-fail-on-error \
117
+ --temperature 0 \
118
+ --log-dir logs
119
+ else
120
+ uv run inspect eval eval/tasks.py@astrodynamics_mcp_eval \
121
+ --model openai-api/github/openai/gpt-4.1-mini \
122
+ -M strict_tools=false \
123
+ --max-samples 3 \
124
+ --max-connections 4 \
125
+ --no-fail-on-error \
126
+ --temperature 0 \
127
+ --log-dir logs
128
+ fi
129
+
130
+ - name: Upload Inspect log artifact
131
+ if: always()
132
+ uses: actions/upload-artifact@v4
133
+ with:
134
+ name: inspect-eval-logs
135
+ path: logs/
136
+ retention-days: 14
137
+
138
+ - name: Generate report
139
+ id: report
140
+ if: always()
141
+ # `set +e` so we capture the exit code; the workflow fails on it
142
+ # in the dedicated step below so the report still lands in the
143
+ # run summary even when the gate fails. The report body is
144
+ # appended to GITHUB_STEP_SUMMARY — that's where the score
145
+ # appears at the top of the workflow run page.
146
+ run: |
147
+ set +e
148
+ uv run python eval/_ci_report.py \
149
+ --log-dir logs \
150
+ --threshold 0.80 >> "$GITHUB_STEP_SUMMARY"
151
+ echo "exit_code=$?" >> "$GITHUB_OUTPUT"
152
+
153
+ - name: Enforce threshold
154
+ # The report step uses `set +e`, so we fail here based on its exit code.
155
+ # Code 0 = passed gate, 1 = below threshold, 2 = missing or empty log.
156
+ if: steps.report.outputs.exit_code != '0'
157
+ run: |
158
+ echo "Eval gate failed: _ci_report.py exited with ${{ steps.report.outputs.exit_code }}."
159
+ exit 1
@@ -0,0 +1,74 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: release-${{ github.ref }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ build:
16
+ name: build sdist + wheel
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 5
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: '3.12'
24
+ - uses: astral-sh/setup-uv@v4
25
+ with:
26
+ enable-cache: true
27
+ cache-dependency-glob: uv.lock
28
+ - run: uv build
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+ if-no-files-found: error
34
+
35
+ publish-pypi:
36
+ name: publish to PyPI
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ timeout-minutes: 10
40
+ environment:
41
+ name: pypi
42
+ url: https://pypi.org/p/astrodynamics-mcp
43
+ permissions:
44
+ id-token: write
45
+ steps:
46
+ - uses: actions/download-artifact@v4
47
+ with:
48
+ name: dist
49
+ path: dist/
50
+ - uses: pypa/gh-action-pypi-publish@release/v1
51
+ with:
52
+ skip-existing: true
53
+
54
+ github-release:
55
+ name: github release
56
+ needs: publish-pypi
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 5
59
+ permissions:
60
+ contents: write
61
+ steps:
62
+ - uses: actions/download-artifact@v4
63
+ with:
64
+ name: dist
65
+ path: dist/
66
+ - name: Create GitHub Release
67
+ env:
68
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69
+ run: |
70
+ gh release create "${GITHUB_REF_NAME}" \
71
+ --repo "${GITHUB_REPOSITORY}" \
72
+ --title "${GITHUB_REF_NAME}" \
73
+ --generate-notes \
74
+ dist/*