Skip to main content
Glama

πŸ”­ nina_planner β€” An Observatory Operator Agent

Banner for repository

nina_planner is an MCP tool server for N.I.N.A. (Nighttime Imaging 'N' Astronomy). The separate events nina-notify plugin is OpenCode-specific. The focus is on building, executing, and monitoring multi-target sequence runs from observation plans.

πŸš€ Quick Start

  1. πŸ”­ Prepare N.I.N.A. β€” install the Advanced API, Sequencer+, and Session Metadata plugins, and put $$TARGETNAME$$ in the profile's FilePattern (see Notes) β€” without it, light frames can't be attributed to a plan.

  2. πŸ“¦ Install requirements β€” opencode v2 (the nina-notify plugin is not compatible with Claude Code or other coding agents), Python 3.11+, and uv. Dependencies are installed by uv on first run.

  3. 🧩 Pick a config β€” replace .opencode/opencode.json with the opencode.json variant that matches where opencode runs: win_opencode.json (Windows), wsl_opencode.json (WSL), or smb_opencode.json (e.g. a Mac reaching a N.I.N.A. VM).

  4. πŸ”Œ Point at N.I.N.A. β€” set NINA_ENDPOINT (default 127.0.0.1:1888) in the config's environment. If the server isn't on the N.I.N.A. machine, also set NINA_DRIVE_MOUNT so image metadata can be read, as smb_opencode.json does (see Environment Variables).

  5. ▢️ Start opencode in the project root; it launches the MCP server for you.

  6. 🌟 Plan a night β€” list targets in PLAN.md and let the agent write plans, load a sequence, and start it.

Related MCP server: NASA-MCP

🧰 Available Tools

Each table gives a one-line summary; details for the larger tools are in Tool Details.

πŸ“‘ Equipment & Telemetry

Tool

Purpose

get_site_equipment_status()

Live connection, state, measurements, and capabilities of all equipment, plus weather and safety-monitor status. ⚠️ Not read-only β€” it auto-connects devices (see details).

get_site_profile()

Active NINA profile and static configuration: site, optics, camera geometry, filters, plate solvers, image-save path. Read-only.

list_site_profiles()

Summaries (id, name, description, last-used time) of every NINA profile, with the active one marked active: true. Read-only.

switch_site_profile(profile_id)

Switch the active NINA profile by GUID from list_site_profiles. Reconnects equipment β€” 🚫 never call during a running sequence.

get_events(since=300)

Timestamped NINA events from the last since seconds, to reconstruct sequence, equipment, safety, imaging, and error activity. Read-only.

get_logs(since=300)

NINA application log entries from the last since seconds, for diagnosing failures. Read-only.

screenshot_dashboard()

Capture the N.I.N.A. dashboard as a PNG; returns the file path and the image. Only side effect: switches NINA to the Imaging tab.

πŸ—ΊοΈ Planning & Progress

Tool

Purpose

list_plans(project_dir?)

List every observation plan file in the project directory with its file, absolute path, plan_id, target, intent, pointing_count, and mtime β€” enumerate plans without globbing. Read-only.

write_plan_file(plan)

Validate and write an observation plan JSON file for later use by load_sequence. Creates the file only; loads and starts nothing.

write_mosaic_plan(plan_path, mosaic_csv, output_path?)

Expand a base plan into a multi-pointing mosaic plan from a Telescopius CSV. Creates the file only.

get_plan_progress(file_path, max_hfr?, min_detected_stars?, max_guiding_rms_arcsec?)

Per-frame-type acquired/remaining counts for every pointing of a plan. Use before load_sequence to see what still needs acquiring. Read-only.

audit_plans(plans?, project_dir?, types?, include_orphans=true)

Whole-project capture audit in one call: reconcile plan expectations with CSV attribution (plus the one filesystem fact the CSV cannot hold β€” bad rejects) for every plan into an all_saved verdict plus left_to_do (including never-shot plans) and blocked work lists, and a never_shot list of plans with no frames at all. Read-only.

get_imaging_metadata(file_path, pointing_index=1, image_type="light")

Per-exposure quality, guiding, pointing, and weather metrics for one pointing of a plan. Read-only.

get_frame_stats(image_type?)

Live per-frame statistics for the current (most recent) frame, straight from the image-history API β€” the watchdog's own data source (HFR, HFR Οƒ, stars, ADU, guiding RMS, temp, filter, filename). Single-frame; for a trend use get_imaging_metadata. Read-only.

🌌 Sky Calculations

Powered by astropy; no NINA connection is needed for resolve_target.

Tool

Purpose

resolve_target(name)

Resolve a deep-sky object name (e.g. M31, NGC 6960) to J2000 ra_hours / dec_deg through a SIMBAD lookup, so plans use verified coordinates. 🌐 Requires internet access. Read-only.

target_altitudes(targets, hours_ahead=0.0)

Altitude/azimuth of each target at the active site, sorted highest first, for ranking candidates. Each target is {"name": ...} plus optional ra_hours / dec_deg (otherwise resolved via SIMBAD). Read-only.

sun_times()

Next sunrise/sunset plus all four twilight boundaries (civil βˆ’6Β°, nautical βˆ’12Β°, astronomical βˆ’18Β°) and the astronomical-darkness window in effect or next to open, with night_phase (evening/dark/morning) and is_dark_now for end-of-night decisions. Read-only; reads only the site profile.

best_targets(min_altitude=30.0, top=10, level, kinds)

Ranks Messier and bright NGC objects to observe from the active site right now, returning visible_now (dark and above the floor now) plus later_tonight (peak altitude within astronomical darkness), with is_dark_now and surface-brightness-based difficulty filtering. Catalog coordinates are for ranking only β€” use resolve_target for pointing. Read-only.

🎬 Sequence Management

Tool

Purpose

load_sequence(request=[...])

Load a sequence for the whole night with safety guardrails. πŸ’€ Never starts it. An empty list is the stow. See details.

start_sequence()

Start the currently loaded sequence immediately. Use after load_sequence(). ▢️

stop_sequence()

Stop the running sequence and wait until NINA reports it stopped. ⚠️ Does not stow the scope.

get_sequence_state(detail)

Per-step view of the loaded sequence: plan attribution, status, exposure progress, horizon altitude, errors. detail: summary (default) or raw (/sequence/state). Read-only.

βš™οΈ NINA Lifecycle

Tool

Purpose

get_nina_status()

Lightweight liveness probe (no device probes); never raises on a down NINA.

start_nina()

πŸš€ Launch NINA into the interactive desktop session. Raises if NINA is already running or the host has no Windows interop.

stop_nina()

πŸ›‘ Terminate NINA.exe immediately via taskkill. A no-op when NINA is already down.

πŸ“– Tool Details

get_site_equipment_status

Returns the current connection, operating state, measurements, and capabilities of active observatory equipment (mount, camera, focuser, guider, filter wheel, rotator, dome, switches), including weather and safety-monitor status (whether the enclosure is open and it is safe to unpark) and mount capabilities (can_park, can_find_home) that determine how the scope can be stowed. Use for live operational and safety checks.

⚠️ Side effect: unlike the other telemetry tools, this call is not read-only. It connects any device that is present but disconnected. If any device is mid-connect the call errors; retry after a few seconds.

get_site_profile

image_save_path is the raw Windows path from the profile; local_image_save_path is the derived path usable on this host (the drive letter is mapped through NINA_DRIVE_MOUNT, default /mnt/<drive>, on Linux/WSL/macOS; unchanged on Windows) and is the one to use for local file access. Use get_site_equipment_status for live equipment state.

list_site_profiles / switch_site_profile

list_site_profiles only exposes summaries; only the active profile exposes full site/optics/filter detail, so use get_site_profile to inspect it. switch_site_profile passes the id straight through to NINA; an unknown id or refusal surfaces NINA's own error. Switching disconnects and reconnects equipment and changes site, filters, image-save path, and file pattern. Confirm with get_site_profile afterward.

get_events / get_logs

Use get_events to reconstruct activity; use get_site_equipment_status and get_sequence_state for current status. Use get_logs to diagnose sequence failures, equipment communication problems, and unexpected behavior.

screenshot_dashboard

Returns two content blocks: the absolute file path as text, then the image itself (e.g. /var/folders/.../T/nina_screenshots/dashboard_20261005T120000123456.png). The image is written to the system temp dir under nina_screenshots/, one uniquely named file per call, nothing overwritten. Clients that render MCP image blocks show the capture inline; otherwise open the returned path with the read tool. The tool is unstructured (no outputSchema) because it carries mixed text/image content. Switches NINA to the Imaging tab first so the capture shows the main imaging workspace; that tab switch is the only side effect.

get_imaging_metadata

Returns per-exposure imaging metadata for one pointing of an observation plan, parsed from ImageMetaData.csv and WeatherData.csv in the N.I.N.A. imaging directory. image_type is light (default), dark, bias, or flat (case-insensitive).

  • Attribution: lights are attributed via the underscore-delimited plan-id token in the recorded file path (_{base_plan_id}_ for a single-pointing plan, _{base_plan_id}-{pointing_index}_ per pane for a mosaic; no target-name fallback). Dark/bias frames are matched plan-wide by image type and exposure; flat frames by image type and filter (exposure is not checked for flats). All calibration frames count only when shot within calibration_window_days of the session start (the earliest attributed light), on either side of it.

  • pointing_index: range-checked (1..pointing count) for every image type and labels the response. For lights it selects the pointing; for calibration types it only selects which pointing's light session anchors the window.

  • Scope: reports the frames that are attributable, not what the plan still needs β€” use get_plan_progress for that.

  • Output: a wide table with one row per exposure: identity fields (file_path, exposure_number, exposure_start, duration, filter_name) plus metrics as columns namespaced by group (quality.hfr, guiding.rms, background.adu_mean, pointing.airmass, weather.temperature, ...). Weather samples are joined by exposure number and UTC exposure start. The result also carries plan_id, pointing_index, and target.

  • Timestamps: come from the CSV's ExposureStartUTC column and are displayed in the host's local time with its UTC offset (e.g. 2026-09-21T21:16:25.123-05:00); calibration-window arithmetic runs on UTC instants.

  • Summary: columns constant across the returned frames are listed once under summary.constants; columns with no real values (NaN/-1, empty, n/a, or the 0 sentinel N.I.N.A. writes for unmeasured metrics) are listed under summary.unpopulated.

get_frame_stats

Returns N.I.N.A.'s live per-frame statistics for the current (most recently captured) frame, read from the GET /v2/api/image-history API β€” the same source the nina_notify quality watchdog polls, exposed on demand so a QUALITY-WATCHDOG wake can be verified against the exact numbers that fired it.

  • Full record: hfr, hfr_st_dev, stars, background ADU (mean, median, st_dev, min, max), guiding RMS text (rms_text), temperature, gain, offset, filter, exposure_time, target_name, filename, and date (the capture timestamp). A genuine 0 (e.g. stars: 0, a star-count failure) is preserved as 0 β€” unlike the CSV path, 0 here is a real reading, not a "not measured" sentinel.

  • Single frame only: despite its name, image-history serves only the current frame β€” one record, no paging, no count parameter. For a multi-frame trend use get_imaging_metadata, which reads the on-disk ImageMetaData.csv for the whole session. This tool is the live view; that one is the historical view.

  • image_type: optionally passed through as the API's imageType filter to assert the current frame's type (e.g. "light", "dark"). When the current frame isn't of that type the tool reports count: 0 with a note rather than erroring.

  • Output: {count, image_type, frame, note} β€” count is 1 when a frame is present (else 0, with frame: null and note explaining why). This tool is read-only.

write_plan_file

Validates and writes an observation plan to a JSON file. The plan defines one or more target pointings (RA/Dec/PA, optional label), acquisition intent, light and calibration frames, batching, cooling, autofocus, guiding, and observing constraints. See The Observation Plan.

write_mosaic_plan

Expands a base observation plan into a multi-pointing mosaic plan from a Telescopius-formatted mosaic CSV (see Mosaic CSV format). The base plan supplies every setting except the pointings, which are replaced wholesale by one pointing per CSV row. Writes <target>_<intent>_mosaic_<timestamp>.json in the project directory unless output_path is given.

get_plan_progress

Returns per-frame-type acquisition progress for every pointing of an observation-plan JSON file: for each exposure group, total_count from the plan, acquired_count attributed to that plan/pointing from the imaging metadata (ImageMetaData.csv), remaining_count, and deficit_count (the raw total βˆ’ acquired, floored at zero, never adjusted for the window).

  • Attribution: lights are attributed via _{base_plan_id}_ (single-pointing) or _{base_plan_id}-{pointing_index}_ (mosaic pane). Flats are matched by image type and filter, darks/bias by image type and exposure, and all count only when shot within calibration_window_days of the earliest attributed light, on either side of it (inclusive; a 1-day window spans anchor βˆ’ 1d … anchor + 1d).

  • Calibration window: calibration groups also carry window_open (bool) and window_end (earliest light + calibration_window_days, displayed in the host's local time with its UTC offset). While the current time is inside the window remaining_count is total βˆ’ acquired; once outside, no frame shot now could be credited, so remaining_count is 0 (with window_open: false). acquired_count stays truthful, and lights carry neither field. Raise calibration_window_days or write a new plan to reopen the window.

  • remaining_count == 0 is not "complete": the window rule above zeroes a calibration group's remaining even when frames are missing, so completion is decided from acquired_count vs total_count and window state instead β€” a closed window under a deficit is reported as expired_with_deficit, not complete. Per pane, status is complete / incomplete / expired_with_deficit; the top level carries expired_with_deficit (bool), and complete is true only when every pane is genuinely done. The expired case is separated from incomplete because its fix differs: no sequence run can close an expired calibration deficit β€” raise calibration_window_days or write a new plan β€” and warning says so.

  • Quality thresholds: pass max_hfr, min_detected_stars, and/or max_guiding_rms_arcsec to exclude light frames that fail them (frames missing the relevant field are excluded when a threshold is set).

  • Scope: one plan per call β€” for a whole-project sweep (every plan, disk files included, in one call), use audit_plans.

  • Output: complete (all panes genuinely done β€” decided from acquired counts and window state, never from remaining_count == 0 alone) plus a pointings list with per-pane progress, per-pane complete, and per-pane status, so panes can be acquired sequentially.

  • Overshoot β€” read this before trusting complete: light items also carry surplus_count (frames attributed beyond total_count), and the top level carries overshot (bool) and warning (string or null). acquired_count is cumulative across every run of the plan while total_count is a per-plan constant, so a mode="full" re-shoot asks N.I.N.A. for total_count more frames and the older ones keep the plan complete no matter what that run delivered β€” a re-shoot that lost 103 of its 120 frames still reported acquired_count: 137, remaining_count: 0, complete: true. When overshot is true, treat complete as "these frames exist" and not as "the last run succeeded": confirm what the latest run actually landed with audit_plans plus the per-date frame folders on the imaging drive. surplus_count cannot tell a re-shoot apart from the same target shot on separate nights under one plan id, so it is a prompt to check, not a verdict. Lights only β€” calibration is pool-credited across plans and routinely overshoots.

list_plans

list_plans(project_dir?)

Returns {"plans": [...], "errors": [...]} β€” one entry per plan JSON in the project directory (default: the server's working directory) with file, absolute path, plan_id, target, intent, pointing_count, and written (mtime, ISO-8601 local with offset). Unparseable JSON files are reported per file under errors instead of failing the call, and a relative project_dir resolves against the project directory. Use it to enumerate plans without globbing; audit_plans shares the same discovery.

audit_plans

audit_plans(plans?, project_dir?, types?, include_orphans=true)

Answers "are all my targets' captures saved to disk, and what is left to do?" for every plan in one call, replacing per-plan get_plan_progress sweeps (15 plans = 15 calls otherwise). plans selects a subset (paths or names, resolved against project_dir, default = every *.json in the project directory); types subsets the output rows to any of light / flat / dark / bias (case-insensitive).

  • Two truth sources, reconciled per plan Γ— frame type: expected (the plan's total_count, summed over its exposure groups β€” per pointing for lights, plan-level for calibration) and attributed / remaining (exactly what get_plan_progress reports, quirks included). The filesystem is consulted for exactly one fact ImageMetaData.csv cannot carry: frames N.I.N.A. rejected via its BAD_ filename prefix β€” 2026-10-22_04-46-37_L_...fits.fz β†’ BAD_2026-10-22_04-46-37_L_...fits.fz β€” whose CSV row still names the pre-rename file, so the reject never reaches attributed; the light row reports them as bad. Calibration rows carry own_rows: how many of that type's recorded frames sit under this plan's own token (_{base_plan_id}_, or _{base_plan_id}-{n}_ per mosaic pane) rather than the shared pool.

  • Top-level output: all_saved (true only when no row needs work, no plan file failed to load, and no plan is unshot), totals (expected / attributed across everything, plus a never_shot count), left_to_do, blocked, never_shot, rows, orphans, errors, and root (the imaging directory actually scanned).

  • left_to_do holds only what running a sequence can fix β€” MISSING_FILES and row-level NEVER_SHOT rows, with missing = remaining and an action hint per type, plus one NEVER_SHOT entry per unshot pointing of a wholly unshot plan with missing = that pointing's whole light ask. blocked is EXPIRED: the calibration window closed under a deficit, so only raising calibration_window_days or writing a new plan helps.

  • never_shot β€” the left join's blind spot, closed: discovery is driven by ImageMetaData.csv, so a plan that was written but never imaged has no row to join on and used to vanish from the audit entirely. The cost was real: an operator reading left_to_do alone saw two 1-frame deficits while six whole unshot plans (240 frames) also waited β€” found only by diffing list_plans by hand. Only light attributions count as a reference: calibration is pool-credited and lands under the shared base token, so a plan whose id appears solely on flat/dark/bias rows has never had a light acquired and is reported here, not joined into the audit by its own darks. Every loadable plan whose id no recorded light frame references is now reported here (plan_id, file, target, intent, pointing_count, light_expected, written) and in left_to_do with reason: "NEVER_SHOT". Matched on plan id, not file: a rewritten/archived twin of an already-shot plan is never flagged (its frames exist under the shared id), and two unshot copies of one plan report once, from the shallowest file. Unshot plans still get no rows β€” expected vs attributed means nothing for frames never attempted β€” and they make all_saved false, so delete or move out any plan file you never intend to shoot (unrelated JSON is already ignored: it never loads as a plan). Explicit plans= never populates it: naming a file is the request to audit it, so an unshot plan named there audits as an ordinary row with status NEVER_SHOT.

  • status per row (first match wins):

    • EXPIRED β€” calibration window closed with an uncredited deficit; surfaces it despite remaining being forced to 0 by the window rule ⏳

    • NEVER_SHOT β€” a light row with zero attributed frames: no run ever delivered a frame for this pointing, so the whole ask is untried work. Identified from the attribution count itself, never from remaining (which is just expected βˆ’ attributed and cannot tell untried work from a partial deficit) πŸŒ‘

    • MISSING_FILES β€” remaining > 0 and the frames genuinely aren't there: shoot more πŸ”«

    • SUSPECT_JUNK β€” degenerate light/dark rows (duration under half the plan's exposure or 0.01s, e.g. 0.00003s Smart-Exposure junk) riding alongside an otherwise-complete set. Bias and flats are exempt (0s is correct for bias; SkyFlat adapts). When junk instead leaves a deficit, the status is MISSING_FILES with the junk count as the cause πŸ—‘οΈ

    • POOL_CREDITED β€” calibration satisfied plan-wide from another target's directory while this plan's own own_rows fall short β€” informational, not a gap 🏷️

    • OVERSHOT β€” lights attributed above expected: the target has been acquired more than once, either by a mode="full" re-run or on separate nights under one plan id. Harmless in itself ℹ️, but it is the case where complete cannot describe the latest run β€” a re-shoot that lost frames still reads complete because the older ones satisfy the plan β€” so confirm what that run landed via the per-date folder counts 🧐

    • OK βœ…

  • ⚠️ Why the columns can disagree: calibration is matched plan-wide (flats by type+filter, darks by type+exposure, bias by type, all within the window around the earliest attributed light), so one bias set credits every plan and attributed can legitimately exceed that plan's own own_rows β€” which is precisely what POOL_CREDITED reports. Lights also require the exposure duration to match (Β±0.05s), so degenerate frames never count. ℹ️ A bad-rejected frame never reaches attributed, so it shows up as a remaining deficit explained by the light row's bad count. There is no disk-vs-CSV cross-check any more: a frame the Session Metadata plugin never recorded appears in neither source, so it surfaces as an ordinary MISSING_FILES deficit. That is rare and safe to re-shoot, which is all this audit ever advised.

  • 🚨 Failure semantics: a missing or unmounted imaging root raises (naming the site profile path and NINA_DRIVE_MOUNT) rather than reporting empty counts β€” "no frames" and "no mount" must never look alike. A broken plan file lands in errors and the rest of the sweep still runs; a project directory with no plans returns a warning with root: null. orphans lists landing token directories whose plan file no longer exists (from deleted plans) β€” the dual of never_shot, which is plan files whose frames do not exist. Every .fits/.fits.fz under an orphan is counted regardless of what its parent folder is called, so an unexpected folder name can never make real frames read as absent.

load_sequence

load_sequence(request=[{action, plan, variant?, pointing_index?, mode="remaining", max_hfr?, min_detected_stars?, max_guiding_rms_arcsec?}, ...])

Loads a sequence with safety guardrails β€” πŸ’€ it never starts it; call start_sequence() to begin. ⚠️ Fails if a sequence is already running β€” call stop_sequence() first.

request is a list of request objects composing one N.I.N.A. sequence for the whole night: a single Start area, one Target area holding each step's containers in list order, and one End area that always stows β€” one bounded 3-minute "On Safe" wait, then park (or home) the mount and warm the camera. πŸ…ΏοΈ An empty list (the default) is therefore the stow; there is no stow/park/warm/unpark action, and load_sequence() with no arguments is the teardown.

Field

Description

action

light, dark, bias, or flat; decides what is built from plan.

plan

The observation-plan JSON path. Required by every action; omitting it raises an error naming the entry.

pointing_index

1-based, default 1. Selects the pointing; applies to lights only. Calibration is plan-level, so a calibration request must omit it (passing one is rejected).

variant

Scoped to the action; a bad pairing fails naming the entry. flat requires dawn (waits for morning twilight) or dusk (waits for sunset). dark/bias take wheel (default β€” park the wheel on the profile's Dark slot), cover (close the motorized cover, e.g. a flip-flat, around the exposures, then reopen it), or shutter (expose without moving the filter wheel, relying on the camera's shutter) β€” each capability-checked at load: wheel needs a filter wheel with a Dark slot, cover a flat device reporting open/close support, shutter a camera reporting HasShutter, and a variant the rig cannot perform fails naming the reason plus the variants it supports. light takes none.

mode

remaining (default) acquires only frames still needed (total minus frames already attributed); if nothing remains the plan is reported complete and nothing is loaded. full acquires the whole plan again β€” and warns when that light group is already complete (see Behavior).

max_hfr, min_detected_stars, max_guiding_rms_arcsec

Exclude failing light frames from the acquired count.

Behavior:

  • Lights embed _{base_plan_id}_ (single-pointing plan, shared with its calibration) or _{base_plan_id}-{pointing_index}_ (per mosaic pane) in the target name for attribution, which requires $$TARGETNAME$$ in the profile's FilePattern. The token is underscore-delimited on purpose: N.I.N.A. rewrites [ and ] to _ when it substitutes $$TARGETNAME$$ into the path, so the id is written exactly as it will be recorded (the older bracketed and parenthesised spellings still attribute as legacy).

  • Steps run in order as the sequence reaches them; a step with its own waits (lights, flats) waits, or is skipped once its window has passed, without blocking the steps behind it.

  • Entries already complete are skipped and reported; if every entry is complete nothing is sent to N.I.N.A. A calibration group whose window expired under a deficit reports the expired shortfall rather than "complete" (and still loads nothing β€” no frame shot now could be credited).

  • A full-mode load of a light group that is already complete appends a ⚠️ warning naming the frame count, because nothing downstream can verify the outcome afterwards β€” get_plan_progress reports complete: true whether the re-shoot delivered every frame or a handful, since its acquired counts are cumulative across runs. Calibration is deliberately exempt: it is pool-credited across plans, so a dark/bias/flat group reads "already complete" on nearly every plan and warning there would fire on every routine calibration load. A metadata read failure degrades to no warning rather than a failed load β€” full is the mode chosen precisely when remaining counts are untrusted.

  • Profile, equipment, and metadata are fetched once, one POST carries the whole sequence, and a validation error names the entry by 0-based position (request[1]: ...).

stop_sequence

For an urgent halt or a stuck sequence. ⚠️ The scope is left where it is. To stow afterward, load_sequence() (empty list) and start it. Avoid interrupting an in-progress stow unless safety requires it, since stopping mid-slew can leave the scope in an unsafe position.

get_nina_status

Cheaper than get_site_equipment_status. Returns running (true iff GET /time answered β€” a missing local NINA.exe does not mean NINA is down when it runs remotely), lifecycle_control_available (this host has Windows interop, so start_nina/stop_nina can work), probe_error (N.I.N.A.'s failure text, or None when running), and a one-line summary.

start_nina / stop_nina

stop_nina determines liveness from the REST API: it is a no-op when NINA is already down and raises when NINA is up but remote (no local Windows interop), since it cannot be terminated from here. start_nina raises if NINA is already running (call stop_nina() first) or if this host has no Windows interop to launch a remote NINA.

πŸ“ The Observation Plan

A plan is a JSON document that describes one complete imaging session. It encodes the target, exposure settings for three of the four frame types (light, dark, bias β€” flats only list a filter and a count, since N.I.N.A. picks their exposure automatically), and equipment configuration (cooler, autofocus, guiding, constraints).

🧱 Plan structure

An example json plan:

plans/m15_globular-cluster_20261003T112008.json

{
  "target": "M15",
  "intent": "Globular cluster",
  "plan_id": "plan-a745ffa2357a",
  "description": "Seestar S30 starter plan for M15. Use the available broadband L filter and short exposures to help preserve the bright cluster core; 90 ten-second light frames for 15 minutes total.",
  "pointings": [
    {
      "label": "M15 center",
      "ra_hours": 21.49954,
      "dec_deg": 12.167,
      "position_angle_deg": null,
      "row": null,
      "column": null
    }
  ],
  "batch_size": 10,
  "calibration_window_days": 7,
  "cooler": {
    "on": false,
    "setpoint_celsius": -10.0
  },
  "constraints": {
    "min_altitude": 30.0,
    "horizon_offset_degrees": 2.0
  },
  "autofocus": {
    "reference_filter_name": "L",
    "hfr_increase_sample_size": 3,
    "hfr_increase_threshold_percent": 15.0,
    "every_n_exposures": 10,
    "threshold_celsius": 1.0
  },
  "guiding": {
    "dither_every_n_exposures": 2,
    "check_drift_every_n_exposures": 6,
    "max_drift_arcmin": 1.5
  },
  "light_frames": [
    {
      "filter_name": "L",
      "exposure_time_seconds": 10.0,
      "total_count": 90
    }
  ],
  "flat_frames": [
    {
      "filter_name": "L",
      "total_count": 20
    }
  ],
  "dark_frames": [
    {
      "exposure_time_seconds": 10.0,
      "total_count": 20
    }
  ],
  "bias_frames": [
    {
      "total_count": 20
    }
  ]
}

🧾 Fields

Field

Required

Description

target

no

Catalog designation, e.g. "M31", "NGC 7000"

intent

no

2-3 words describing the goal

plan_id

no

Stable identifier stamped on write. Used for frame attribution; when absent, a deterministic hash of plan content is used. Must be empty or ASCII letters, digits, . and - only: _ is the attribution-token delimiter, and N.I.N.A. rewrites other characters when it puts $$TARGETNAME$$ into the file path, so an id outside that charset is refused at write and at load rather than silently never matching its frames.

description

no

Explanation of the observation plan, including rationale, exposure goals, equipment, or sky constraints.

pointings

yes

One or more pointing objects. Each requires ra_hours (J2000 RA in hours, [0, 24)) and dec_deg (J2000 declination in degrees, [-90, +90]). Optional label is shown in the N.I.N.A. target name; position_angle_deg is rotator PA in degrees east of north, [0, 360). If omitted, 0 is sent to N.I.N.A., leaving the rotator at its current/synced position. Optional row and column are nonnegative mosaic metadata not sent to N.I.N.A.

batch_size

no

Exposures per batch; defaults to 5. Set to 0 to disable batching.

calibration_window_days

no

Defaults to 7 (minimum 1). Calibration frames count only when taken within this many days before or after the earliest attributed light. After the window closes, remaining calibration is reported as 0.

cooler

no

Cooler settings: on defaults to true; setpoint_celsius defaults to -10 and must be in [-40, 30].

constraints

no

min_altitude defaults to 30 degrees and must be in [0, 90]; horizon_offset_degrees defaults to 2 and must be in [0, 10].

autofocus

no

reference_filter_name defaults to "L"; hfr_increase_sample_size to 3; hfr_increase_threshold_percent to 15; every_n_exposures to 10; and threshold_celsius to 1. Numeric values must be greater than zero.

guiding

no

dither_every_n_exposures defaults to 2; check_drift_every_n_exposures to 6; and max_drift_arcmin to 1.5. Numeric values must be greater than zero.

light_frames

yes

Nonempty list of light exposure specs, each with filter_name, exposure_time_seconds (greater than zero), and total_count (at least 1).

flat_frames

yes

Nonempty list of flat exposure specs, each with filter_name and total_count (at least 1). No exposure time: N.I.N.A.'s SkyFlat item chooses it automatically to hit the right signal level.

dark_frames

yes

Nonempty list of dark exposure specs, each with exposure_time_seconds (greater than zero) and total_count (at least 1).

bias_frames

yes

Nonempty list of bias exposure specs, each with total_count (at least 1).

πŸ“Š Mosaic CSV format

For write_mosaic_plan, use a Telescopius-formatted CSV with these nine columns in this order:

plans/mosaic.csv

Pane, RA, DEC, Position Angle (East), Pane width (arcmins), Pane height (arcmins), Overlap, Row, Column
Pane 1, 0hr 56' 01", 45ΒΊ 51' 18", 0.00, 309.00, 205.80, 10%, 1, 1
Pane 2, 0hr 29' 28", 45ΒΊ 51' 18", 0.00, 309.00, 205.80, 10%, 1, 2
Pane 3, 0hr 55' 22", 42ΒΊ 46' 13", 0.00, 309.00, 205.80, 10%, 2, 1
Pane 4, 0hr 30' 07", 42ΒΊ 46' 13", 0.00, 309.00, 205.80, 10%, 2, 2
Pane 5, 0hr 54' 47", 39ΒΊ 41' 07", 0.00, 309.00, 205.80, 10%, 3, 1
Pane 6, 0hr 30' 42", 39ΒΊ 41' 07", 0.00, 309.00, 205.80, 10%, 3, 2
Pane 7, 0hr 54' 15", 36ΒΊ 36' 00", 0.00, 309.00, 205.80, 10%, 4, 1
Pane 8, 0hr 31' 14", 36ΒΊ 36' 00", 0.00, 309.00, 205.80, 10%, 4, 2

Each row becomes a Pointing: RA/DEC are parsed server-side (sexagesimal like 0hr 56' 01" / 45ΒΊ 51' 18" or decimal), Pane becomes the label, and Row/Column are stored as row/column metadata (N.I.N.A. does not use them β€” they are kept for ordering, labeling, and later mosaic assembly). The base plan's plan_id is cleared so the mosaic gets its own content-derived id; each pane is attributed independently via _{plan_id}-{pointing_index}_.

  • 🚫 No rotator: every pane's position_angle_deg must be 0. Any nonzero PA fails loudly, since PA can't be enforced without a rotator.

  • Acquiring panes: query get_plan_progress(file_path) to see which panes still need frames, then load the first incomplete pane with load_sequence(request=[{"plan": file_path, "action": "light", "pointing_index": N, "mode": "remaining"}]) and start_sequence(). Repeat pane by pane across the night; when the top-level complete is true, the mosaic is done β€” and if expired_with_deficit is true instead, the lights may well be fine and only the calibration window needs raising or a new plan (see get_plan_progress).

πŸ“Œ Notes

  • πŸ”Œ Required N.I.N.A. plugins: the MCP server requires the following three plugins to be installed in N.I.N.A.: Advanced API, Sequencer+, and Session Metadata.

  • πŸ“ Image file pattern: for light frames only, the N.I.N.A. profile's ImageFileSettings.FilePattern must put $$TARGETNAME$$ in a path segment so frames can be attributed to a plan (e.g. $$TARGETNAME$$\$$IMAGETYPE$$\...). Attribution accepts the token in any segment β€” directory or filename β€” but put it in a directory, and let that directory be a direct child of the image-save path (the usual <target> _<plan-id>_ landing folder): audit_plans claims landing directories by the names of the top-level directories under the image-save path, so a filename-only token (or a token nested one level deeper) attributes fine on the CSV side yet still lands the folder in orphans, flagged as belonging to no plan. Its frames are counted correctly under that entry β€” the disk and CSV views no longer disagree about how many there are β€” but the folder will keep reading as unclaimed until the pattern puts the token in that directory. Calibration frames (dark, bias, and flat) do not require this token; they are matched by frame type and exposure settings within the calibration window (darks by exposure time, flats by filter alone).

  • βœ… Quality thresholds: max_hfr, min_detected_stars, and max_guiding_rms_arcsec (all optional) exclude light frames that fail the thresholds from the acquired count. Frames missing the quality fields are excluded whenever a threshold is set (fail-closed). Thresholds are applied only to light frames β€” calibration frames have no star quality.

  • πŸ‘Ž Manually failing a frame: delete (or move using thumbs down icon) the image file on disk β€” e.g. a bad .fits/.tif. The metadata row stays in the CSV (the plugin only appends), but it is omitted when the metadata is read, so progress no longer counts that frame as acquired and the next mode="remaining" load re-acquires it.

  • πŸ’Ύ The plan file is persistent. You can inspect, edit, and reuse it across sessions. Changing the file will, however, alter its derived plan_id, which is calculated when plan_id is missing. A hand-written plan_id must satisfy the same charset rule (empty, or ASCII letters, digits, . and -) β€” anything else, _ included, is refused when the plan is loaded.

  • 🧩 opencode.json variants: the repo contains example opencode.json variants: win_opencode.json for Windows, wsl_opencode.json for WSL, and smb_opencode.json for, say, a Mac accessing a N.I.N.A. VM with its image file directory mapped back over SMB.

πŸ”” nina-notify β€” OpenCode v2 Autonomous Plugin

nina-notify is an opencode v2 plugin (not compatible with Claude Code or other coding agents). Registered in .opencode/opencode.json, it runs as a background server inside opencode and does two things:

  1. Websocket event monitoring β€” Connects to the N.I.N.A. event socket (ws://<host:port>/v2/socket), subscribes to all events, and wakes the active agent only when a decision is actually required.

    Only three classes of event ever wake the agent, because N.I.N.A. already runs routine work itself β€” autofocus, dithering and drift correction are its own internal triggers, so reporting them to the agent buys nothing:

    • Milestones β€” sequence lifecycle (RUN-STARTING/RUN-FINISHED/SEQUENCE-STARTING/SEQUENCE-FINISHED) and per-target transitions (TS-NEWTARGETSTART): the observation queue needs evaluating β€” transit checks, altitude limits, next target dispatch.

    • Faults β€” failures (ERROR/FAIL/EXCEPTION/ABORT/INTERRUPT/CRITICAL/TIMEOUT, which catches CAMERA-DOWNLOAD-TIMEOUT) and link loss (OFFLINE/DISCONNECT/LOST/EMERGENCY): the agent diagnoses the root cause and selects a fallback. The tokens are matched as substrings, so PRESTOW-DELAY is caught by STOW.

    • Safety β€” safety state (SAFE/UNSAFE/SAFETY/WEATHER/DOME/ROOF) and mount/stow (MOUNT*/STOW*/PRESTOW-*): stow or stand down.

    Two demotions came out of that same 16h sample of real events (476 events β†’ 45 matched the regex β†’ but only 11 of the 24 coalesced prompts actually carried a milestone, fault or safety change): MOUNT-CENTER never wakes β€” it is the slew/settle centering the mount performs itself on every acquisition, and it alone produced 8 wake events β€” and device link events wake only while a sequence is running. N.I.N.A. cycles every device at session start and teardown, which produced the other 29 of 45 wake events, and a link change is only operationally interesting while something is imaging. That gate is seeded from GET /v2/api/sequence/json at startup (using the same any-node-RUNNING rule as the MCP server, so the two never disagree) and maintained from RUN-*/SEQUENCE-* events; a seed failure leaves it wake-worthy, the safe default. Safety state changes (SAFETY-CHANGED) match neither rule and always wake. Replaying the same 476 events through the new rules gives 7 prompts instead of 24 β€” every one a genuine milestone, fault or safety change.

    Wakes are coalesced over a 250ms window, so a burst of distinct fault events costs the agent one prompt instead of one per event; safety still lands essentially instantly, since an agent wake already takes seconds.

    Routine telemetry is never pushed. Everything else β€” per-frame IMAGE-PREPARED/IMAGE-SAVE, GUIDER-*, ACQUISITION-*, EXPOSING-FINISHED, and any unrecognized event β€” is counted and dropped. Nothing is buffered to disk either: N.I.N.A. already keeps its own event history server-side (GET /v2/api/event-history), which the MCP server exposes as get_events(since=…). So every wake prompt carries the instruction get_events(since=<seconds since your last wake>) plus how many routine events were waiting, and the agent reads exactly the window it needs, on demand. Per-frame quality detail (HFR, star counts, ADU) is likewise already on disk in ImageMetaData.csv / get_imaging_metadata.

    Quality watchdog. Because routine telemetry no longer wakes anyone, a slowly degrading night (climbing HFR, collapsing star count) would otherwise stay invisible until the next milestone. While light frames are being acquired the plugin polls GET /v2/api/image-history on each IMAGE-SAVE and wakes the agent as an operational fault β€” a QUALITY-WATCHDOG: … event β€” when a frame-quality-diagnostics standing trigger holds for 3 consecutive frames β€” HFR > 3β€³ or under 10 detected stars. The consecutive-frame rule is deliberate: the single-frame GUIDER-START settles and post-recenter dips that skill documents can never wake anyone, and it fires once per degradation, re-arming only after the metric recovers. Calibration is exempt twice over: a known dark/bias/flat step (from ACQUISITION-STARTING: [{kind}] …) is skipped without even polling, and the record's own ImageType is a per-frame hard stop β€” darks, biases and flats have no stars at all, so without that check every calibration frame would read as a star-count failure. That ImageType check is also what keeps the watchdog working when the plugin attaches after a step's ACQUISITION-STARTING. qualityWatchdog: false turns the whole check off.

    image-history is used in preference to …/equipment/camera/capture/statistics: that one is a transient "last capture" buffer wiped by any camera reconnect, profile switch or sequence load (it answered 400 No image available for a whole idle afternoon, while the previous night's frame was still served from image-history). Freshness is proven, not assumed β€” a sample is accepted only when its Date is newer than the last one, cross-checked against the IMAGE-SAVE event to 0.95 ms, so two saves reading one identical record can never count a single frame twice. A record with no provable Date, or nothing recorded yet, costs exactly one deferred re-check (1.5s later) and then leaves the streaks untouched: a missing sample may only delay a detection, never invent one, and a source that never advances cannot escalate into a retry loop (two attempts per frame, spaced at least 5s apart).

    Net effect: ~10–30 prompts per night instead of ~1,100 β€” one per milestone, fault, or sustained quality problem.

  2. Interval check β€” every intervalCheckMinutes (default 45, previously -1/disabled) it prompts the agent to query observatory status (get_sequence_state, get_site_equipment_status) and decide what to do next, even when no N.I.N.A. events are firing. With routine telemetry no longer prompting, this is the only periodic driver between milestones, so it deliberately fires even when the event socket is down β€” a silently dead event stream is exactly when a status check matters most.

The plugin auto-reconnects on websocket disconnection with a 5-second retry. This 5s delay is connection recovery only β€” it fires solely on onclose and waits before re-establishing the socket; it never delays event delivery. Immediate events flush after a 250ms coalescing window and routine telemetry never flushes at all. On server dispose, it cleans up all timers and the socket.

πŸ’« PLAN.md β€” the living wishlist

A markdown file at the project root, edited by the user at any time (before or during the night). It is intentionally lightweight β€” entries can be as simple as a target name:

PLAN.md

# Tonight - 2026-10-08

- M31          "before the moon comes up"
- Veil Nebula  plan=veil-nebula_..._20260913.json   prio 1
- M33          "if high targets done"

Rules:
- min_altitude: 30
- horizon_offset_degrees: 2
- tie_break: priority, then altitude

πŸ“ Project directory

Plan JSON files, PLAN.md, and PROGRESS.md live in the project directory. Relative paths (e.g. a plan= entry in PLAN.md, or mosaic_csv) resolve against it, and the project directory is simply the server process's working directory: opencode starts local MCP servers in the workspace, and the launch command uses uv run --project rather than uv --directory β€” --directory chdirs into .opencode/local/mcps/nina_planner, which would make relative paths resolve inside .opencode/ instead of the project.

πŸ€– AGENTS.md β€” Agent Instructions

Tells the agent it runs an observatory.

.opencode/rules/AGENTS.md

# Observatory Controller

You are the automated observatory controller. The observatory runs NINA via the nina-planner MCP server.

## Always-on rules:
- `is_safe=false` (enclosure closed) or weather violations mean: scope stowed, if not by the NINA sequence then by you.
- Append a timestamped entry to `PROGRESS.md` after each action; never edit `PLAN.md` (the user's living wishlist).
- You are woken only by milestones, faults, safety changes, the interval check, and sustained quality problems. Routine telemetry (per-frame image saves, guider, autofocus) is **not** pushed to you β€” every wake prompt names a `get_events(since=…)` window covering the quiet gap, so read it before deciding. `QUALITY-WATCHDOG: …` in a wake means frame quality degraded and held. Device connect/disconnect events wake **only while a sequence is running**; if you hear nothing about a device, that is by design, not a missed event.

## Detailed procedures live in `.agents/skills/`:
- `safety-interrupt-recovery` β€” on any new event or interval check: NINA liveness, equipment/safety checks, stowing.
- `night-loop` β€” on interval check or `RUN-FINISHED`: pick and start the next target, or end the night.
- `nina-lifecycle` β€” start/stop/status of the NINA application.

## Emoji Communication Guidelines
- You will use emojis liberally to communicate status updates, milestones, and emotional context as things happen.

πŸ“œ PROGRESS.md β€” Shared Progress File

PROGRESS.md is a shared observatory progress file in the project directory. The active session reads it on start to restore context before acting, and appends a timestamped entry after each significant action, recording what was done, the observed equipment and safety state, and any decisions. This keeps history shared across sessions.

πŸ“‹ opencode.json β€” Opencode v2 Sample Configuration

.opencode/opencode.json

{
  "$schema": "https://opencode.ai/config.json",
  "instructions": [ ".opencode/rules/*.md" ],
  "plugins": [
    {
      "package": "./local/plugins/nina_notify",
      "options": {
        "pluginLogs": "C:/Windows/Temp/nina-notify-debug.log"
      }
    }
  ],
  "mcp": {
    "servers": {
      "nina-planner": {
        "type": "local",
        "environment": {
          "NINA_PLANNER_LOG": "C:/Windows/Temp/nina-planner-debug.log"
        },
        "command": [
          "uv", "run", "--project", ".opencode/local/mcps/nina_planner", "-m", "nina_planner"
        ]
      }
    }
  }
}

Registers the opencode plugin and the nina_planner MCP server so both run together. The MCP server provides the tools (load_sequence, get_site_equipment_status, etc.) that the plugin-prompted agent calls.


πŸŽ›οΈ Plugin Options

πŸ”” nina-notify (opencode v2 plugin)

Configured in the plugin's options configuration. (see opencode.json).

Option

Type

Default

Description

ninaEndpoint

string

127.0.0.1:1888

N.I.N.A. host and port (no scheme) for the websocket event stream, e.g. 192.168.0.24:1888. The plugin connects to ws://<ninaEndpoint>/v2/socket.

intervalCheckMinutes

number

45

Minutes between autonomous status-check prompts. Accepts a float; converted to milliseconds (Γ—60,000) only when greater than 0; with 0 or less no timer is scheduled. Fires regardless of the event-socket state, so a dead event stream still gets attention.

qualityWatchdog

boolean

true

Wake the agent as an operational fault when a frame-quality-diagnostics standing trigger β€” HFR > 3β€³ or fewer than 10 detected stars β€” holds for 3 consecutive light frames. Polls GET /v2/api/image-history on each IMAGE-SAVE, accepts a record only when its Date advanced, and gates on the record's ImageType as well as on the step kind β€” so darks, biases and flats never trigger it; re-arms once the metric recovers.

pluginLogs

string

(none)

File path appended on each plugin event (websocket open/message/close/error, every injected prompt, watchdog checks, interval trigger failures). Set to a writable path (e.g. /tmp/nina-plugins.log) to capture diagnostic output. Omit or leave empty to disable file logging.


πŸ”§ Environment Variables

πŸ–₯️ nina_planner (MCP server)

Variable

Default

Description

NINA_ENDPOINT

127.0.0.1:1888

N.I.N.A. host and port for the REST API (host:port). For WSL, try 192.168.0.24:1888. When this points at a remote host (not the MCP server's own machine), get_nina_status().lifecycle_control_available is false and start_nina/stop_nina cannot operate on NINA.

NINA_DRIVE_MOUNT

/mnt/<drive>

Local mount root for the drive letter in the profile's image_save_path, used by get_imaging_metadata to resolve the imaging directory on Linux, WSL, and macOS. The value must be the local path where the Windows drive root is mounted (C:\Users\me\img becomes <value>/Users/me/img), e.g. /mnt/c in WSL, or wherever you mounted the share on macOS (such as /System/Volumes/Data/Network). The mount point is arbitrary and the drive need not be C:; the drive letter in the profile path is replaced by this value whatever it is. Defaults to /mnt/<drive> (e.g. /mnt/c). On Windows (win32) the profile image_save_path is used directly. The resolved value is also exposed as the derived local_image_save_path field on the profile returned by get_site_profile.

NINA_PLANNER_LOG

(none)

File path where the server appends each NINA request/response payload for debugging. Logging is disabled when unset. A Windows-style path (e.g. C:/Windows/Temp/nina-planner-debug.log) on a non-Windows host is redirected to the system temp dir under the same file name.

NINA_FLATS_ALTITUDE

80

Altitude in degrees for flat panel calibration frames

NINA_FLATS_AZIMUTH_DAWN

270

Azimuth in degrees pointing west for dawn flats

NINA_FLATS_AZIMUTH_DUSK

90

Azimuth in degrees pointing east for dusk flats

NINA_EXE_PATH

C:\Program Files\N.I.N.A. - Nighttime Imaging 'N' Astronomy\NINA.exe

Windows path to NINA.exe used by start_nina to launch NINA.

🐧 Setting up WSL

Make sure mirroring is setup in WSL to allow you to connect to NINA directly through localhost (otherwise you have to use your machines' IP address). Do this by editing wslconfig and setting networking mode to mirrored.

$ vi $HOME/.wslconfig
[wsl2]
networkingMode=mirrored

In addition, to get the system tools to work to stop and restart NINA should it stop, make sudo passwordless with:

$ echo "$USER ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/90-user

πŸͺŸ Setting up Windows

I installed the following packages to start:

winget install git.git      # bash
winget install Oven-sh.Bun  # bun
winget install jqlang.jq    # jq
winget install BurntSushi.ripgrep.MSVC  # rg
winget install astral-sh.uv # uv

Next I installed opencode v2 with:

$ bun install -g --trust @opencode/cli

Then to run opencode I ran in windows:

$ export OPENCODE_SERVER_PASSWORD=pass
$ opencode serve --port 4096
$ opencode --server http://127.0.0.1:4096

Available Tools

14 tools
get_eventsA

Returns recent timestamped NINA observatory events and their event-specific details from the last since seconds. Use it to reconstruct sequence, equipment, safety, imaging, and error activity. This tool is read-only; use get_site_equipment_status and sequence_get_state for current status.

NameRequiredDescriptionDefault
sinceNo

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It explicitly states 'This tool is read-only', which is a critical behavioral trait. It also implies a time-window behavior via 'since' seconds. However, it does not disclose details like ordering, pagination, or limits on returned events, which would be useful for a tool that fetches historical data. Given the read-only nature, this is a minor gap, so a 4 is appropriate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with zero redundancy. It front-loads the primary purpose and then gives usage guidance. Every sentence earns its place, and it is easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only getter with one parameter and no output schema, the description covers the essential information: what it returns, when to use it, and that it is safe. It could mention whether events are sorted or if there are any maximum limits, but these are not critical for a tool of this simplicity. The mention of alternatives is a strong plus.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has one parameter `since` with 0% description coverage. The description fully explains its meaning: 'from the last `since` seconds'. This adds clear semantic meaning beyond the type and default, making it easy for an agent to know what value to supply.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Returns recent timestamped NINA observatory events and their event-specific details'. It also clarifies the scope (time window) and differentiates from siblings by naming them ('use get_site_equipment_status and sequence_get_state for current status'). This leaves no ambiguity about what the tool does and how it differs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly tells when to use the tool ('reconstruct sequence, equipment, safety, imaging, and error activity') and when not to, with named alternatives for current status. The guidance is actionable and covers both inclusion and exclusion conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_imaging_metadataA

Returns imaging metadata parsed from the ImageMetaData.csv files in the frame folders (LIGHT, DARK, BIAS, FLAT, etc.) of the mounted N.I.N.A imaging directory. Each row is tagged with Date, FrameType, and Source. When an AcquisitionDetails.csv is present in the same session directory, its fields (e.g. TargetName, FocalLength) are injected into each image row unless the image row already has a value for that field. Defaults to lights frames for star-quality checks; pass another image type (light, dark, bias, flat β€” case-insensitive). Optionally filter to a single YYYY-MM-DD date.

NameRequiredDescriptionDefault
dateNo
image_typeNolight

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses the merging behavior (injecting AcquisitionDetails.csv fields unless already present), the default image type, and case-insensitivity. It does not explicitly state read-only safety, but the verb 'Returns' implies it, and it adds valuable behavioral context about the data transformation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a multi-sentence paragraph but every sentence carries useful information: source, tagging, injection rule, defaults, and filter. It is front-loaded with the main purpose and does not waste words, though it could be slightly more compact without losing substance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read tool with a documented output schema, the description covers the essential behaviors: source location, row tagging, field injection, default frame type, and date filter. It does not mention error cases or what happens if the directory is missing, but those are minor given the output schema and the tool's read-only nature.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It explains both parameters: image_type with allowed examples and case-insensitivity, and date with a specific YYYY-MM-DD format. This adds meaning well beyond the bare schema and provides concrete guidance for values and format.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Returns' and the resource 'imaging metadata parsed from the ImageMetaData.csv files', and specifies the source directory and file type. It also distinguishes itself from siblings (which are about site status, sequences, logs) by being the only tool focused on imaging metadata extraction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context: it defaults to lights frames for star-quality checks and allows passing other image types, and optionally filtering by date. However, it does not explicitly name alternative tools or state when not to use it, so it lacks explicit exclusions but provides enough situational context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_logsA

Returns recent NINA application log entries, including informational messages, warnings, and errors with source and timestamp details from the last since seconds. Use it to diagnose sequence failures, equipment communication problems, and unexpected behavior. This tool is read-only and takes no action.

NameRequiredDescriptionDefault
sinceNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It explicitly states the tool is read-only and takes no action, which is important behavioral disclosure. It also describes the content of the returned entries, covering the key behavioral traits an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three crisp sentences: what it returns, when to use it, and its safety profile. It is front-loaded with the core behavior and every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only tool with one optional parameter and no output schema, the description adequately covers return content, use cases, and non-mutating behavior. It could specify output limits or ordering, but nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, but the description compensates by explaining that logs come from 'the last `since` seconds', giving clear meaning to the only parameter. It does not state the default value, but that is already present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns recent NINA application log entries with source and timestamp details, which is a specific verb and resource. It does not explicitly differentiate from get_events, but the resource is specific enough to avoid most ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description names concrete diagnostic use cases: sequence failures, equipment communication problems, and unexpected behavior. It gives clear context for when to use the tool, though it does not mention alternatives or exclusion conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

_get_site_equipment_statusD
NameRequiredDescriptionDefault
profileYes

Output Schema

NameRequiredDescription
domeNo
mountNo
cameraNo
guiderNo
focuserNo
rotatorNo
weatherNo
filter_wheelNo
safety_monitorNo

TDQS

D1/5.0
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Tool has no description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness1/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Tool has no description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Tool has no description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Tool has no description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose1/5

Does the description clearly state what the tool does and how it differs from similar tools?

Tool has no description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tool has no description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_site_equipment_statusA

Returns the current connection, operating state, measurements, and capabilities of active observatory equipment, including weather and safety-monitor status (whether the enclosure is open and it is safe to unpark), and mount capabilities (can_park, can_find_home) that determine how the scope can be stowed. Use it for live operational and safety checks. This tool is read-only and takes no action.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
domeNo
mountNo
cameraNo
guiderNo
focuserNo
rotatorNo
weatherNo
filter_wheelNo
safety_monitorNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses that it is read-only and takes no action, which is a key behavioral trait. Since no annotations are provided, the description carries the full burden, and it adequately covers the safety and side-effect profile.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences with no redundancy. It front-loads the primary function, then adds usage and behavioral notes, each earning its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has an output schema, so return values are defined there. The description explains the purpose, usage, and behavioral constraints, and mentions specific capabilities like can_park and can_find_home, making it complete for an agent to decide when to call it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema already covers everything. The description adds no parameter-specific information, but with 0 parameters, the baseline of 4 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns current connection, operating state, measurements, and capabilities of observatory equipment, with specific examples like weather, safety-monitor status, and mount capabilities. This distinguishes it from siblings like get_site_profile and get_events by focusing on live status.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states 'Use it for live operational and safety checks,' providing clear context for when to invoke it. However, it does not mention alternatives or when not to use it, so it lacks explicit exclusion guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_site_profileA

Returns the active NINA observatory profile and static configuration, including site location, optics, camera geometry, filters, plate solvers, and image-save path. Use get_site_equipment for live equipment state. This tool is read-only and takes no action.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
siteYes
opticsYes
filtersYes
equipmentYes
profile_idYes
descriptionNo
file_patternNo
plate_solverNo
profile_nameYes
image_save_pathYes
blind_plate_solverNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description explicitly states the tool is read-only and takes no action, which is valuable behavioral disclosure. Since no annotations are provided, the description carries the full burden, and it does so by clarifying the non-mutating nature and that it returns static configuration rather than live state.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no wasted words. The first sentence front-loads the tool's purpose and content, and the second sentence provides the sibling distinction and safety profile. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete for a zero-parameter read-only tool. It names the sibling for live state, lists the content returned, and states the read-only nature. The output schema exists, so return values need not be described in prose. Minor gap: it doesn't mention whether the profile is cached or freshly fetched, but this is not essential for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema is trivially complete. The description adds context about what the returned profile contains, which is more useful than parameter documentation in this case. Baseline 4 for zero-parameter tools is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns the active NINA observatory profile and static configuration, listing specific content areas (site location, optics, camera geometry, filters, plate solvers, image-save path). It also distinguishes itself from the sibling get_site_equipment_status by noting that tool is for live equipment state.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly names the alternative get_site_equipment for live equipment state, providing a clear when-to-use distinction. It doesn't exhaustively enumerate all alternatives or exclusions, but the key sibling differentiation is present.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

observation_plan_get_progressA

Returns per-frame-type acquisition progress for an observation-plan JSON file: for each exposure group, the total_count from the plan, the acquired_count attributed to this plan from the imaging metadata (ImageMetaData.csv + AcquisitionDetails.csv), and the remaining_count. Lights are attributed via the plan_id embedded in the sequence target name (falling back to the target name for legacy frames); flats/darks/bias are matched by image type, filter, and exposure. Pass max_hfr and/or min_detected_stars to exclude light frames that fail quality thresholds (frames missing the quality fields are excluded when a threshold is set). Use this before sequence_load_plan to decide what still needs acquiring.

NameRequiredDescriptionDefault
max_hfrNo
file_pathYes
min_detected_starsNo
max_guiding_rms_arcsecNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses the attribution logic (plan_id embedded in target name, fallback to target name, matching by image type/filter/exposure) and the exclusion behavior for quality thresholds. It implies a read-only operation but does not explicitly state so, nor does it mention potential edge cases like missing files. Still, it is substantially transparent about its behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is reasonably concise given the complexity, with each sentence adding essential detail. It is front-loaded with the core purpose, then explains attribution, parameters, and usage in a logical order. It avoids redundancy but is slightly long due to necessary technical detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has an output schema, so return values need not be described. The description covers the input parameters, the logic, and the usage context. It lacks explicit error handling or assumptions about file existence, but for an agent it provides enough to decide when to call and what to expect. It is complete for typical use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate. It explains file_path as the observation-plan JSON file and explains max_hfr and min_detected_stars as quality thresholds. However, it does not mention max_guiding_rms_arcsec at all, leaving that parameter semantically unexplained. This partial coverage warrants a score of 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Returns') and resource ('per-frame-type acquisition progress for an observation-plan JSON file') with detailed metrics (total_count, acquired_count, remaining_count). It clearly distinguishes itself from siblings by focusing on progress computation and explicitly routes to sequence_load_plan as the next step.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states 'Use this before sequence_load_plan to decide what still needs acquiring,' giving a clear when-to-use directive. It also explains when to pass quality thresholds (max_hfr/min_detected_stars) and how they affect results, covering the main use case without ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

observation_plan_write_fileA

Validates and writes an observation plan to a JSON file for later use by sequence_load_plan. The plan defines target coordinates, acquisition intent, light and calibration frames, batching, cooling, autofocus, guiding, and observing constraints. This tool only creates the plan file; it does not load or start a sequence.

NameRequiredDescriptionDefault
planYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds useful context beyond the schema by stating the tool validates the plan, writes it to a file, and does not load/start a sequence. It also mentions the plan_id is 'stamped on write' indirectly through schema, but the description itself doesn't describe side effects of overwriting an existing file, permission requirements, or return behavior. With no annotations provided, it could still disclose more.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Compact and front-loaded: first sentence covers the action, resource, and key limitation; second sentence reinforces scope. No filler, every clause carries meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex nested plan object with no schema-level parameter descriptions, the description gives a high-level overview of what the plan should include, plus a critical caveat about not loading the plan. Given there is an output schema, return values are covered. Minor gaps: no mention of file path, overwrite behavior, or validation failure outcomes, but the core agent task is well supported.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. The description summarizes the plan's contents (target coordinates, acquisition intent, light/calibration frames, batching, cooling, autofocus, guiding, constraints), but it doesn't explain how the single parameter (plan) relates to those fields or that it must be a complete ObservationPlan object. The schema's own field descriptions are sparse, so the description helps but doesn't fully compensate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool's action ('Validates and writes'), the resource ('observation plan to a JSON file'), and its downstream purpose ('for later use by sequence_load_plan'). It explicitly distinguishes this tool from sequence_load_plan and sequence_start, making its scope obvious even among siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage β€” this is the tool to use when you want to create a plan file but not execute it β€” and explicitly states it does not load or start a sequence. However, it doesn't explicitly say 'use this when you need to persist a plan before loading it' nor does it contrast when to use observation_plan_get_progress or other siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sequence_enter_safety_standbyA

Loads a non-acquisition standby sequence that keeps NINA sequence-level safety and stow guardrails active while the observatory is idle. After loading, call sequence_start. Stop it before loading an acquisition or teardown sequence. Use whenever equipment is deployed and no other sequence is running.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the behavioral disclosure burden. It does well by explaining that this tool only loads a sequence, does not start it, keeps safety guardrails active, and must be stopped before loading other sequences. It does not mention side effects of neglecting the stop step, but the core behavior is transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences carry all essential information: what the tool does, what to do next, and when to stop/avoid it. Every sentence earns its place, and the most important context is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given zero parameters)Skip? Given zero parameters, an output schema, and clear sibling context, the description covers the complete call workflow: load, start, stop before replacing, and the operational condition for using it. Nothing critical is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and schema coverage is 100%, so the schema already exhaustively documents the inputs. Per the rubric, 0 parameters earns a baseline of 4; the description adds no unnecessary parameter detail and needs none.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ('Loads'), a specific resource ('non-acquisition standby sequence'), and the purpose ('keeps NINA sequence-level safety and stow guardrails active while the observatory is idle'). This clearly distinguishes it from sibling tools that load or execute acquisition or teardown sequences.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit operational guidance: call sequence_start after loading, stop it before loading an acquisition or teardown sequence, and use it whenever equipment is deployed with no other sequence running. This fully orients an agent on when and how to invoke the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sequence_execute_teardownA

Loads and starts the non-acquisition teardown sequence, safely stowing the telescope (park or home, per mount capability) while NINA's sequence-level safety guardrails remain active. Use for end-of-observation close-down β€” when you are done observing and want to shut down the scope β€” or before leaving the observatory unattended. Allow it to complete without interruption. This is separate from sequence_stop, which halts the current sequence but does not stow the scope.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses the stowing behavior, the park/home variation, the retained safety guardrails, and that interruption should be avoided. It does not mention failure modes or prerequisites such as mount connectivity, but the core operational behavior is well covered, which is strong for a zero-parameter tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Each sentence adds distinct value: action, usage, caution, and sibling distinction. There is no redundant content; the description is tightly structured and easy to parse, with the main verb and resource front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter tool with an output schema, this description covers purpose, usage, and operational cautions. It does not need to describe return values since an output schema exists, and nothing necessary for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and the schema already documents this with 100% coverage, so baseline 4 applies. The description adds no parameter-specific meaning, but none is needed since there is nothing to explain.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Loads and starts the non-acquisition teardown sequence') and the resource (telescope stow), and explicitly distinguishes from sequence_stop. This clearly separates it from all sibling tools without needing to inspect schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly tells when to use: 'when you are done observing and want to shut down the scope' or 'before leaving the observatory unattended,' and clarifies that sequence_stop does not stow. It also gives the operational instruction to 'Allow it to complete without interruption,' leaving no ambiguity about when to invoke it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sequence_get_stateA

Returns the loaded sequence structure and the current status of its containers, instructions, conditions, and triggers. Use it to determine whether a sequence is loaded, running, completed, failed, or waiting. This tool is read-only and takes no action.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description itself discloses that the tool is read-only and takes no action. This is important behavioral context for an agent deciding whether to invoke it. It also conveys what kind of information will be returned.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three short sentences with no filler. It front-loads the core purpose, then adds usage context, then the critical read-only behavior. Every sentence contributes value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter read-only inspection tool, the description covers what it returns, what it can be used to determine, and that it has no side effects. It does not mention error cases or prerequisites, but the tool is simple enough that this is not a major gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and the schema is empty, so there is nothing for the description to add about parameter meaning. The baseline of 4 applies because no parameter documentation burden exists.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Returns') and names the exact resource: the loaded sequence structure and the status of its containers, instructions, conditions, and triggers. This clearly distinguishes the tool from mutation siblings like sequence_start, sequence_stop, and sequence_load_plan.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly tells the agent when to use this tool: to determine whether a sequence is loaded, running, completed, failed, or waiting. It does not name exclusions or alternative tools, but the intended context is clear enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sequence_load_planA

Loads an acquisition sequence with safety guardrails from an observation-plan JSON file. Select the frame type: light, dark, bias, dawn_flat, or dusk_flat. In the default remaining mode the sequence only acquires frames still needed (total minus frames already attributed to this plan in the imaging metadata); if nothing remains it reports the plan as complete and loads nothing. max_hfr and/or min_detected_stars exclude light frames failing quality thresholds from the acquired count. Pass mode="full" to acquire the entire plan again. This tool only loads the sequence; call sequence_start afterward.

NameRequiredDescriptionDefault
modeNoremaining
max_hfrNo
file_pathYes
frame_typeNolight
min_detected_starsNo
max_guiding_rms_arcsecNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral disclosure burden, and it does so well: it states side-effect limitations ('loads nothing' when complete), mode-dependent behavior, and the effect of quality thresholds. It doesn't fully describe every edge case or the behavior of max_guiding_rms_arcsec, but it is substantially transparent for an unannotated tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is six sentences of dense, non-redundant information. It front-loads the core purpose and guardrails, then explains modes and quality filters, and ends with the essential follow-up instruction. Every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers purpose, modes, frame types, quality filters, and the load-versus-start distinction, and an output schema exists to cover return values. It misses explicit meaning for `max_guiding_rms_arcsec` and does not mention prerequisites or error conditions, but overall it is largely complete for a 6-parameter tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It explains `mode` (remaining/full), enumerates all `frame_type` choices, and clarifies `max_hfr` and `min_detected_stars` effects. However, it omits any meaning for `max_guiding_rms_arcsec`, which is a small but notable gap given the 0% schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action and resource: 'Loads an acquisition sequence with safety guardrails from an observation-plan JSON file.' It also differentiates itself from the sibling sequence_start by explicitly stating 'This tool only loads the sequence; call sequence_start afterward,' so an agent can separate loading from starting.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage context: the default `remaining` mode acquires only needed frames, `mode="full"` reacquires the entire plan, and quality thresholds affect light-frame counting. It also provides a clear boundary and follow-up instruction: 'This tool only loads the sequence; call sequence_start afterward,' which routes the agent to the correct next step.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sequence_startA

Starts or resumes the currently loaded sequence, activating its acquisition or safety workflow. Call sequence_get_state afterward to verify that it is running.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the behavioral disclosure burden. It discloses that the tool activates an acquisition or safety workflow and that the sequence may not be immediately confirmed as running, since the agent is told to call sequence_get_state afterward. It stops short of describing error behavior or idempotency, but for a zero-parameter command tool this is meaningful disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no filler. The primary action and its effect are front-loaded, and the follow-up verification instruction is a single useful addition. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter lifecycle tool with an output schema available, the description is complete: it states the action, the resource, the effect, and the immediate next step. The 'currently loaded sequence' phrasing implies the load prerequisite, and no additional context is needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and the schema coverage is 100%, so there are no parameter semantics to clarify. The baseline for a no-parameter tool is 4, and the description appropriately focuses on behavior rather than arguments.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Starts or resumes') and a clear resource ('currently loaded sequence'), and says what it activates ('acquisition or safety workflow'). It also names the sibling sequence_get_state, which helps differentiate this tool from state-checking and lifecycle tools like sequence_stop and sequence_load_plan.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear procedural context: use this to start/resume a loaded sequence, then verify with sequence_get_state. It does not explicitly list exclusions or when to prefer a sibling, but the loaded-sequence qualifier implies a prerequisite and the named follow-up tool gives enough orientation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sequence_stopA

Stops the currently running NINA sequence immediately, leaving the telescope where it currently is β€” it does not stow the scope. Use for an urgent halt, to interrupt a stuck/looping sequence, or when the running sequence isn't what you wanted. If you then want to park/home the telescope, run sequence_execute_teardown separately. Note that sequence_load_plan and the teardown/standby entry tools already stop any running sequence before loading, so an explicit stop is only needed when you want to halt without loading anything new. Avoid stopping an in-progress teardown during the stow maneuver unless safety requires it, since interrupting mid-slew can leave the scope in an unsafe position. Call sequence_get_state afterward to confirm the sequence has stopped.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It clearly conveys side effects: immediate stop, telescope left in place, no stowing, and the potential unsafe position from interrupting a mid-slew teardown. It also advises confirmation via sequence_get_state. This fully covers the relevant behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than a simple one-liner, but every sentence earns its place: primary behavior first, then use cases, exclusions, a safety warning, and a verification step. The structure is logical and front-loaded with the most critical information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, safety-relevant control tool, the description covers all key operational context: what happens, when to call it, when not to call it, what to do instead, and how to verify the outcome. An output schema exists, so the description need not detail return values. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters and the schema is empty, so there is no parameter semantics to document. The 0-parameter baseline of 4 applies; the description appropriately does not invent unnecessary parameter details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Stops the currently running NINA sequence immediately.' It clarifies the scope by explicitly noting the scope is not stowed and by distinguishing itself from sequence_execute_teardown, making sibling differentiation clear.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use scenarios (urgent halt, stuck/looping sequence, unwanted sequence) and when not to use it (before the teardown/standby entry tools, during an in-progress teardown unless safety requires). It also names the alternative sequence_execute_teardown for parking/homing and the follow-up sequence_get_state. This is exemplary routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 14 tool updatesv0.1.0
    • First observed_get_site_equipment_status
    • First observedget_events
    • First observedget_imaging_metadata
    • First observedget_logs
    • First observedget_site_equipment_status
    • First observedget_site_profile
    • First observedobservation_plan_get_progress
    • First observedobservation_plan_write_file
    • First observedsequence_enter_safety_standby
    • First observedsequence_execute_teardown
    • First observedsequence_get_state
    • First observedsequence_load_plan
    • First observedsequence_start
    • First observedsequence_stop

TDQS

B3.2/5.0

Scored across 14 tools

Disambiguation4/5

Most tools are clearly separated by domain: status, profile, events, logs, imaging metadata, plan progress, and sequence control are all distinct. The duplicate `_get_site_equipment_status` tool with no description creates real ambiguity, but it is the only significant overlap.

Naming Consistency4/5

Tool names follow predictable snake_case patterns such as `get_*`, `sequence_*`, and `observation_plan_*`. The leading-underscore duplicate `_get_site_equipment_status` breaks the convention and should be removed, but the rest of the naming is consistent.

Tool Count4/5

Fourteen tools is within a reasonable range for an observatory planning and operations server, and each major area has dedicated coverage. The duplicate tool slightly inflates the count, but the real scope is well-sized.

Completeness4/5

The set covers the full observation workflow: diagnostics, plan creation, progress tracking, sequence loading/starting/stopping, teardown, and safety standby. Minor gaps like plan-file inspection/editing or pause/resume commands are workaroundable, but no severe dead ends exist.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    A 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.
    13
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    An MCP server that enables LLMs to query data from various NASA APIs, allowing access to astronomical data, space weather information, Earth imagery, and exoplanet information directly from compatible AI clients.
    21
    6
    MIT