nina-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@nina-mcp slew the mount to the Orion Nebula"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
nina-mcp
An MCP server that gives an AI agent (your openclaw agent, Claude, or anything else that speaks MCP) control over N.I.N.A. (Nighttime Imaging 'N' Astronomy). Mount, camera, sequencer, filter wheel, focuser, rotator, dome, guider, switches, flat panels, and target scheduling are all fully implemented. Safety monitor and weather stations are exposed as status reads.
It talks to NINA over HTTP via the community ninaAPI plugin (also called "Advanced API" in the NINA plugin list) — this MCP server doesn't replace that plugin, it wraps it.
Prerequisites
NINA installed and running, with the Advanced API plugin installed (NINA > Plugins > search "Advanced API") and enabled.
Note the host/port it's listening on (NINA > Options > Plugins > Advanced API — default port is
1888). If this MCP server runs on the same PC as NINA, the default127.0.0.1:1888just works.Python 3.10+ wherever this MCP server runs.
Related MCP server: mcp-server-ascom
Install
cd nina-mcp
python -m venv .venv
# Windows: .venv\Scripts\activate
source .venv/bin/activate
pip install -e .Copy .env.example to .env and adjust NINA_HOST / NINA_PORT if needed
(defaults assume NINA is on the same machine, default port).
Run it
Directly, for any MCP client that spawns servers over stdio (Claude Desktop, Claude Code, and presumably your openclaw agent — MCP's stdio transport is just "run this command, talk JSON-RPC over its stdin/stdout"):
nina-mcp
# or: python -m nina_mcp.serverPoint your MCP client's config at it. The generic shape (adjust keys to whatever openclaw's config format expects — this is the same shape Claude Desktop uses):
{
"mcpServers": {
"nina": {
"command": "/absolute/path/to/nina-mcp/.venv/bin/python",
"args": ["-m", "nina_mcp.server"],
"env": {
"NINA_HOST": "127.0.0.1",
"NINA_PORT": "1888"
}
}
}
}If openclaw needs a network transport instead of stdio (SSE / streamable
HTTP), FastMCP supports that too — change the mcp.run() call in
server.py to mcp.run(transport="streamable-http") (see the mcp Python
SDK docs for the current options; this changed a couple of times across
versions).
Testing without an agent
test_client.py is a minimal MCP client included so you can sanity-check the
server without wiring up openclaw first. It spawns the server itself over
stdio, same as a real client would:
python test_client.py # list all tools
python test_client.py nina_mount_info # call with no args
python test_client.py nina_mount_sync ra=10.5 dec=41.2 # call with argsWhat's actually implemented
Core (full control)
Equipment (generic,
tools/equipment.py) — combined status, list available devices, connect/disconnect/rescan any device by name, and poll recent event history. Connect equipment through these tools before using the device-specific ones below.Mount (
tools/mount.py) — info, home, park/unpark, tracking mode, slew (raw, center, or center+rotate), stop slew, sync, meridian flip, set park position.Camera (
tools/camera.py) — info, capture (all the knobs: duration, gain, save, target name, image type, plate-solve, binning, readout mode), abort exposure, capture statistics, cooling/warming, dew heater, USB limit.Sequencer (
tools/sequencer.py) — get sequence JSON/state, start, stop, reset, skip, edit a field in place, list/load saved sequences by name, load a full sequence from JSON, update a target's coordinates.
Target Scheduler — read this before assuming "full control"
ninaAPI does not expose Target Scheduler's projects/targets/exposure plans
as REST endpoints. I checked ninaAPI's source directly
(WebService/V2/Application/TargetScheduler.cs): all it does is forward
three of TS's internal status messages (wait-start, new-target-start,
target-start) as read-only events. There's no endpoint to list projects,
change priorities, or ask what it'll image next — that logic is entirely
internal to the Target Scheduler plugin's own Planning Engine, which only
runs from inside a NINA sequence via its "Target Scheduler Container"
instruction (which you still have to build once, by hand, in the NINA UI —
no API creates it for you either).
So tools/target_scheduler.py gives you the two things that actually exist:
Running it — just a normal sequence. Build a sequence containing a Target Scheduler Container instruction in NINA's UI once, then use
nina_sequence_load_by_name+nina_sequence_startto run it.Reading/editing its data — Target Scheduler stores everything in a local SQLite database (
schedulerdb.sqlite, path documented in the plugin's own docs). Rather than hardcode column names I couldn't verify against a real database (the schema changed across major TS versions),ts_db.pyimplements generic, schema-validated tools:ts_list_tables,ts_describe_table,ts_read_table, and a narrowts_update_cell(single column, single row, by id) that's disabled by default — setTS_ALLOW_WRITES=trueonce you've backed up your database and want an agent editing it.ts_recent_eventspolls the live status events from (1).
To support Target Scheduler automation, we have implemented typed database write
tools (ts_set_project_priority, ts_set_project_enabled, and ts_toggle_target_enabled)
that dynamically discover the active tables and columns (supporting both TS4 and TS5).
ts_recent_events polls the live status events from (1).
Status-Only Modules (tools/placeholders.py)
Safety monitor and weather stations do not have REST control surfaces upstream, so they only expose *_info status reads.
Architecture
src/nina_mcp/
config.py settings from environment variables
nina_client.py async HTTP client, unwraps ninaAPI's response envelope
ts_db.py generic, schema-validated SQLite access for Target Scheduler
server.py FastMCP app, registers every tool module
tools/
equipment.py generic connect/disconnect/list/rescan/event-history
mount.py full mount control
camera.py full camera control
sequencer.py full sequencer control
target_scheduler.py DB + event-based TS tools (with schema discovery)
filterwheel.py filter wheel control
focuser.py focuser movement and configuration
rotator.py sky rotator control
dome.py observatory dome control
guider.py autoguiding control
switch.py switch/relay control
flatdevice.py flat panel illumination and cover control
placeholders.py status-only equipment (weather, safety monitor)
test_client.py minimal standalone MCP client for manual testingEvery endpoint path and query parameter here was confirmed against ninaAPI's
own C# source (WebService/V2/...), not guessed from third-party docs —
I pulled the repo and grepped the [Route(...)] attributes directly. The one
thing that genuinely doesn't exist upstream is Target Scheduler's REST API,
which is why that module takes a database-directed approach.
Safety notes
This gives an agent real control of a telescope mount and camera. A clumsy
nina_mount_slewor an unattendednina_sequence_startcan point expensive optics somewhere bad or burn a night's imaging. Consider keeping a human in the loop for anything that moves hardware until you trust the setup.TS_ALLOW_WRITESis off by default for a reason — back upschedulerdb.sqlitebefore turning it on.nina_camera_capture'somit_imagedefaults toTrueto avoid dumping large base64 blobs into an agent's context by default.
Available Tools
66 toolsnina_camera_abort_exposureB
Abort the exposure currently in progress.
Inputs: no named parameters.
nina_camera_captureA
Capture a single exposure.
duration: exposure length in seconds. gain: camera gain; omit to use the camera's current/default gain. save: whether to save the resulting image to disk. target_name: subfolder/name association for the saved file, if any. image_type: e.g. "LIGHT", "DARK", "FLAT", "BIAS" (NINA's own image type strings); omit to use the current default. solve: plate-solve the resulting image. wait_for_result: block until the exposure (and any requested download/processing) finishes. only_await_capture_completion: return as soon as the exposure itself finishes, without waiting for download/processing -- use with wait_for_result=True. get_result: include image statistics/metadata in the response. omit_image: don't include the actual image data (base64) in the response -- keep this True unless you specifically want the pixel data returned inline, since it can be large. resize / size / quality / scale: control a resized preview image if omit_image=False (size like "1920x1080", quality 1-100). stream: stream the image back as it's captured rather than after. only_save_raw: save only the raw sensor data, skipping NINA's normal processing pipeline. skip_auto_stretch: skip auto-stretch when producing a preview image.
Inputs: duration (required), gain, save, size, +13 more.
nina_camera_capture_statisticsA
Get statistics (HFR, star count, mean ADU, etc.) for the most recently captured image.
Inputs: no named parameters.
nina_camera_coolA
Start cooling the camera to a target temperature (°C).
temperature: target sensor temperature in Celsius. minutes: cool down over this many minutes rather than immediately, if supported by the camera. cancel: if True, cancel an in-progress cooldown instead of starting one (temperature/minutes are ignored in that case).
Inputs: temperature (required), cancel, minutes.
nina_camera_infoA
Get full camera status: connection state, cooler on/off, current and target temperature, cooler power, gain/offset, binning, readout mode, and capabilities.
Inputs: no named parameters.
nina_camera_set_binningB
Set camera binning, e.g. "1x1", "2x2", "3x3".
Inputs: binning (required).
nina_camera_set_dew_heaterB
Turn the camera's dew heater on (True) or off (False), if the camera has one.
Inputs: power (required).
nina_camera_set_readout_modeA
Set the camera's readout mode index (camera-specific; see nina_camera_info for the list of supported modes).
Inputs: mode (required).
nina_camera_set_usb_limitC
Set the camera's USB bandwidth/traffic limit, if supported.
Inputs: limit (required).
nina_camera_warmA
Warm the camera back up (turn off/ramp down the cooler).
minutes: warm up over this many minutes rather than immediately. cancel: if True, cancel an in-progress warmup instead of starting one.
Inputs: cancel, minutes.
nina_connect_deviceA
Connect a piece of equipment. If to (a device Id from
nina_list_devices) is omitted, NINA connects whichever device is
currently selected in its own equipment dropdown for that category.
Inputs: device (required), to.
nina_disconnect_deviceC
Disconnect a piece of equipment.
Inputs: device (required).
nina_dome_closeB
Close the dome shutter.
Inputs: no named parameters.
nina_dome_infoA
Get dome status: connection state, shutter state, azimuth angle, slewing state, parked state, and dome-follows-mount status.
Inputs: no named parameters.
nina_dome_openA
Open the dome shutter.
Inputs: no named parameters.
nina_dome_slewA
Slew the dome to a specific azimuth angle.
azimuth: Target azimuth in degrees (0.0 to 360.0). North is 0.0, East is 90.0.
Inputs: azimuth (required).
nina_filterwheel_change_filterA
Change the active filter in the filter wheel.
filter_id: The index/position of the filter to change to (0-indexed). Must be >= 0.
Inputs: filter_id (required).
nina_filterwheel_infoA
Get filter wheel status: connection state, current filter index, list of available filters, and their focus offsets.
Inputs: no named parameters.
nina_flatdevice_infoA
Get flat panel/flat device status: connection, light on/off state, brightness, and cover open/closed status.
Inputs: no named parameters.
nina_flatdevice_set_lightA
Turn the flat panel light on (True) or off (False).
Inputs: on (required).
nina_focuser_infoA
Get focuser status: connection state, current step position, ambient temperature, moving state, and temperature compensation state.
Inputs: no named parameters.
nina_focuser_moveA
Move the focuser to a specific absolute step position.
position: Target absolute position in steps. Must be >= 0.
Inputs: position (required).
nina_focuser_stopA
Immediately stop any in-progress focuser movement.
Inputs: no named parameters.
nina_get_all_equipment_infoA
Get a single combined status snapshot of every equipment category NINA knows about (camera, mount, filter wheel, focuser, rotator, dome, flat device, guider, safety monitor, switch, weather). Good first call to orient yourself on what's connected before doing anything else.
Inputs: no named parameters.
nina_get_event_historyA
Get NINA's recent event/notification history (the same stream sent over its websocket), most recent first. Useful for polling status changes -- including Target Scheduler's TS-WAITSTART / TS-TARGETSTART / TS-NEWTARGETSTART events -- without holding an open websocket connection open from an MCP tool call.
Inputs: no named parameters.
nina_guider_infoA
Get autoguider status: connection state, guiding state, and last guide step/RMS error info.
Inputs: no named parameters.
nina_guider_startB
Start the autoguider loop.
calibrate: True to force a new calibration sequence, False to use existing calibration.
Inputs: calibrate.
nina_guider_stopA
Stop autoguiding.
Inputs: no named parameters.
nina_list_devicesA
List the devices NINA's device chooser sees for a given category
(e.g. every ASCOM/native mount driver detected), each with an Id you
can pass to nina_connect_device.
Inputs: device (required).
nina_mount_homeA
Send the mount to its home position. Fails if not connected or parked (unpark first).
Inputs: no named parameters.
nina_mount_infoA
Get full mount status: RA/Dec, altitude/azimuth, tracking state, parked/homed state, slewing state, site coordinates, and more.
Inputs: no named parameters.
nina_mount_meridian_flipB
Perform a meridian flip at the mount's current coordinates.
Inputs: no named parameters.
nina_mount_parkC
Park the mount.
Inputs: no named parameters.
nina_mount_set_park_positionA
Set the mount's current position as its park position. Fails if the mount doesn't support this or is already parked.
Inputs: no named parameters.
nina_mount_set_tracking_modeA
Set the mount's tracking mode/rate.
mode: one of "sidereal", "lunar", "solar", "king", "stopped".
Inputs: mode (required).
nina_mount_slewA
Slew the mount to J2000 RA/Dec coordinates.
ra: Right ascension in HOURS (0-24), not degrees.
dec: Declination in degrees (-90 to 90).
center: if True, plate-solve and iteratively center on the target
after the raw slew (requires camera + plate solver configured),
instead of just a raw GOTO.
rotate: if True, also rotate to rotation_angle and re-center after
rotating (requires a connected rotator). Implies centering.
rotation_angle: target sky position angle in degrees, only used if
rotate=True.
wait_for_result: if True, block until the slew (and center/rotate, if
requested) completes and report success/failure. If False, return
immediately once the slew has been started.
Inputs: ra (required), dec (required), center, rotate, +2 more.
nina_mount_stop_slewA
Immediately stop any in-progress slew (including a center/rotate started via nina_mount_slew).
Inputs: no named parameters.
nina_mount_syncA
Sync the mount's internal position to the given J2000 RA (hours) / Dec (degrees), without moving it. Used to correct pointing model drift once you know your true position (e.g. after a plate solve).
Inputs: ra (required), dec (required).
nina_mount_unparkA
Unpark the mount.
Inputs: no named parameters.
nina_rescan_devicesA
Re-scan for available devices in a given category (useful after plugging in USB hardware or restarting an ASCOM driver).
Inputs: device (required).
nina_rotator_infoA
Get rotator status: connection state, current sky and mechanical position angles in degrees, moving state, and direction settings.
Inputs: no named parameters.
nina_rotator_moveA
Slew the rotator to a specific sky angle.
angle: Target sky position angle in degrees (0.0 to 360.0).
Inputs: angle (required).
nina_rotator_stopA
Stop any in-progress rotator movement.
Inputs: no named parameters.
nina_safetymonitor_infoA
Get safety monitor status: connection and whether it currently reports conditions as safe.
Inputs: no named parameters.
nina_sequence_editA
Edit a single field of the loaded sequence in place, addressed by
path (as shown in nina_sequence_get_json's structure). Useful for
small tweaks (e.g. an exposure count or duration) without reloading
the whole sequence.
Inputs: path (required), value (required).
nina_sequence_get_jsonA
Get the currently loaded Advanced Sequence as NINA's own JSON structure (containers, instructions, triggers, conditions).
Inputs: no named parameters.
nina_sequence_get_stateA
Get the current run state of the loaded sequence: which item is executing, overall progress, and whether it's running/stopped.
Inputs: no named parameters.
nina_sequence_list_availableA
List sequence files available in NINA's default sequence folder,
loadable by name via nina_sequence_load_by_name.
Inputs: no named parameters.
nina_sequence_load_by_nameA
Load a saved sequence (by name, without the .json extension) from NINA's default sequence folder as the active Advanced Sequence. Fails if a sequence is currently running.
Inputs: sequence_name (required).
nina_sequence_load_jsonA
Load an entire Advanced Sequence from a raw JSON string (NINA's
own sequence export format, as returned by nina_sequence_get_json).
Fails if a sequence is currently running.
Inputs: sequence_json (required).
nina_sequence_resetA
Reset the loaded sequence back to its initial, not-yet-run state.
Inputs: no named parameters.
nina_sequence_set_targetB
Update the coordinates/name/rotation of a DSO target container already present in the loaded sequence.
name: display name for the target. ra: J2000 right ascension in hours. dec: J2000 declination in degrees. rotation: target sky position angle in degrees. index: which target container to update, if the sequence has more than one (0-based).
Inputs: ra (required), dec (required), name (required), index, +1 more.
nina_sequence_skipA
Skip ahead in the running sequence.
skip_type: "CurrentItems" (skip just the current instruction(s)), "ToEnd" (skip to the end area / shutdown), or "ToImaging" (skip straight to the imaging area).
Inputs: skip_type (required).
nina_sequence_startA
Start running the currently loaded Advanced Sequence.
skip_validation: skip NINA's pre-flight validation checks (equipment connected, safe to image, etc). Leave False unless you have a good reason.
Inputs: skip_validation.
nina_sequence_stopA
Stop the currently running sequence.
Inputs: no named parameters.
nina_switch_infoA
Get switch device status: connection and the state/value of each configured switch.
Inputs: no named parameters.
nina_switch_setA
Set a switch value (e.g. relay state or slider value).
switch_index: Index of the switch. Must be >= 0. value: Target state (0.0 for OFF, 1.0 for ON, or intermediate value for sliders).
Inputs: value (required), switch_index (required).
nina_weather_infoA
Get weather station status: connection, cloud cover, wind, temperature, humidity, pressure, sky quality, rain rate (fields depend on what your weather device reports).
Inputs: no named parameters.
ts_describe_tableA
List the columns (name, type, nullability, PK) of a Target Scheduler database table, as returned by ts_list_tables.
Inputs: table (required). Returns: result
ts_list_tablesA
List every table in the Target Scheduler SQLite database. Start here -- table/column names vary across Target Scheduler versions (notably the TS5 migration), so don't assume names like "Project" or "Target" without checking first.
Inputs: no named parameters. Returns: result
ts_read_tableA
Read rows from a Target Scheduler database table.
where_column / where_value: optionally filter to rows where this column equals this value. limit: max rows to return (capped at 2000).
Inputs: table (required), limit, where_value, where_column. Returns: result
ts_recent_eventsA
Get recent Target Scheduler status events forwarded through NINA: TS-WAITSTART (scheduler is waiting for the next imaging window), TS-NEWTARGETSTART / TS-TARGETSTART (scheduler picked a target and started imaging it, with target name, project name, coordinates, rotation, and expected end time). This is TS's actual live state -- the database only holds its configuration and historical progress.
Inputs: limit. Returns: result
ts_set_project_enabledA
Toggle the enabled/active state of a specific project.
project_id: The unique ID of the project. enabled: True to enable/activate the project, False to disable.
Inputs: enabled (required), project_id (required).
ts_set_project_priorityA
Set the scheduling priority for a specific project in the database.
project_id: The unique ID of the project. priority: Target priority integer. Lower values represent higher priority (e.g., 1 is highest).
Inputs: priority (required), project_id (required).
ts_toggle_target_enabledC
Toggle the enabled/active state of a specific target.
target_id: The unique ID of the target. enabled: True to enable/activate the target, False to disable.
Inputs: enabled (required), target_id (required).
ts_update_cellA
Update a single column on a single row in the Target Scheduler database, e.g. disabling a target or bumping a project's priority.
Disabled by default for safety -- set the environment variable TS_ALLOW_WRITES=true to enable this tool, ideally after backing up schedulerdb.sqlite. Only ever touches one column on one row (identified by id_column=id_value); there's no bulk-edit or delete here on purpose.
Inputs: table (required), value (required), column (required), id_value (required), +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 66 tools
Each tool targets a specific action on a distinct subsystem (camera, mount, sequence, etc.), with clear and unique purposes. Even within the same subsystem, tools like nina_camera_capture and nina_camera_abort_exposure are easily distinguishable.
All tools follow a consistent nina_<subsystem>_<action> naming pattern (e.g., nina_camera_capture, nina_mount_slew). No mixing of conventions or unclear verb choices.
At 66 tools, the count is high but appropriate for the extensive domain covering multiple equipment categories, sequence management, and a database interface. Each tool serves a distinct purpose, and no single subsystem has an excessive number.
The tool surface covers core operations for all major subsystems (connect/disconnect, control, info) and includes advanced features like sequence editing and target scheduler database access. A few minor gaps exist (e.g., no sequence creation from scratch), but overall coverage is strong.
Maintenance
Related MCP Connectors
Connect your AI agent to 300+ methods and 100+ live tools
Enables AI assistants to retrieve event data and execute event management operations in Nunify.
Control your Tesla from your AI assistant - climate, charging, access, and security.
Connect any AI agent to 1,000+ apps and 27,000+ actions through one remote MCP server (OAuth).
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA protocol server enabling AI agents to control astrophotography equipment through the N.I.N.A. (Nighttime Imaging 'N' Astronomy) software, allowing for natural language command processing of cameras, mounts, focusers, and other astronomy equipment.13MIT
- AlicenseNot gradedqualityCmaintenanceControl telescopes, cameras, and focusers with AI through natural language, using any ASCOM Alpaca device.18 PyPIMIT
- AlicenseBqualityBmaintenanceThis MCP server lets AI clients inspect N.I.N.A. equipment and telemetry, write and persist observation plans, and load, run, and tear down imaging sequences. It also supports hardware actions like parking, homing, and warming the camera for complete astrophotography sessions.141MIT
- AlicenseBqualityCmaintenanceEnables AI agents to fully control PixInsight for astrophotography workflows, including calibration matching, WBPP stacking, blink review, and post-processing with visual previews.92MIT