Ryan Malloy 8a6b99bd8c Add birdcage-mcp FastMCP server for satellite dish control
34 tools (connection, movement, signal, system, satellite, console),
5 resources, 3 prompts. Backed by DemoDevice for offline testing.
46 tests passing against the demo backend via run_server_async.
2026-02-17 16:01:51 -07:00

79 lines
3.6 KiB
Python

"""MCP prompts — guided workflows for common dish operations."""
def register(mcp):
"""Register MCP prompts on the FastMCP instance."""
@mcp.prompt()
async def setup_wizard() -> str:
"""Step-by-step guide to connect and initialize the satellite dish.
Walks through: check status -> connect to serial port -> verify
firmware -> home motors -> confirm position.
"""
return (
"Follow these steps to set up the satellite dish:\n\n"
"1. Call `status` to check the current connection state.\n"
"2. If not connected, call `connect` with the correct port "
"and firmware variant.\n"
"3. Call `get_firmware_id` to verify the firmware version.\n"
"4. Call `home_motor` with motor_id=0 (AZ) then motor_id=1 (EL) "
"to establish reference positions.\n"
"5. Call `get_position` to verify the dish is at the expected "
"home coordinates.\n"
"6. Call `get_el_limits` to confirm the elevation operating range.\n\n"
"The dish is now ready for tracking or manual positioning."
)
@mcp.prompt()
async def satellite_tracking_guide() -> str:
"""Guide for tracking a satellite pass with the dish.
Walks through: search -> get passes -> wait for AOS -> poll
position at 1 Hz -> move dish -> detect LOS.
"""
return (
"Follow these steps to track a satellite:\n\n"
"1. Call `search_satellites` with the satellite name "
"(e.g. 'ISS', 'NOAA 19').\n"
"2. Note the NORAD ID from the search results.\n"
"3. Call `get_passes` with the NORAD ID to see upcoming passes.\n"
"4. Choose a pass with good max elevation (>30 deg is ideal).\n"
"5. When the pass is about to start (AOS time), begin a tracking "
"loop:\n"
" a. Call `get_visible_targets` to get the satellite's current "
"AZ/EL.\n"
" b. Call `move_to` with those coordinates.\n"
" c. Wait ~1 second, then repeat from (a).\n"
"6. Stop tracking when the satellite drops below your minimum "
"elevation.\n\n"
"TIP: Enable the LNA first with `enable_lna` if you want to "
"measure signal strength during the pass."
)
@mcp.prompt()
async def rf_sweep_guide() -> str:
"""Guide for performing an RF sky sweep with the dish.
Walks through: enable LNA -> baseline RSSI -> configure sweep
-> run az_sweep -> analyze results.
"""
return (
"Follow these steps for an RF sky sweep:\n\n"
"1. Call `enable_lna` to power the low-noise amplifier.\n"
"2. Call `get_rssi` to establish a baseline noise floor.\n"
"3. Decide your sweep parameters:\n"
" - start_az: Starting azimuth\n"
" - span: How many degrees to sweep\n"
" - step_cdeg: Resolution in centidegrees (100 = 1 deg)\n"
" - num_xponders: Transponders per position (1 for basic)\n"
"4. Call `az_sweep` with those parameters.\n"
"5. Analyze the returned points:\n"
" - Look for RSSI peaks above the noise floor (~500)\n"
" - Check lock=1 points for strong signals\n"
" - SNR values indicate signal quality\n\n"
"For a 2D sky map, repeat the sweep at different elevations "
"(e.g. EL 20 to 60 in 5 deg steps) using `move_motor` to "
"set each elevation before sweeping."
)