Skip to main content
Glama
README.md
# pixinsight-mcp

**pixinsight-mcp** gives an AI agent hands inside PixInsight: a toolbox of about 80 PixInsight operations exposed
over MCP, used by **agent chat sessions** and by the **skills** that guide them.

**It contains no processing workflow.** No pipeline, no ordering, no recommended parameters, no opinion
about what makes a good image. Deciding what to do is the job of you, your chat session and your own
skills, and that knowledge stays in your repositories, public or private, under your control.

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#1e40af', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#1e3a8a',
  'secondaryColor': '#5b21b6', 'secondaryTextColor': '#ffffff', 'lineColor': '#64748b', 'textColor': '#f8fafc', 'clusterBkg': '#1e293b', 'clusterBorder': '#475569', 'edgeLabelBackground': '#334155'}}}%%
flowchart LR
    classDef yours fill:#5b21b6,stroke:#4c1d95,stroke-width:1px,color:#ffffff
    classDef harness fill:#0e7490,stroke:#155e75,stroke-width:1px,color:#ffffff
    classDef repo fill:#1e40af,stroke:#1e3a8a,stroke-width:1px,color:#ffffff
    classDef app fill:#334155,stroke:#1e293b,stroke-width:1px,color:#f8fafc
    subgraph decides["Decides what to do — yours, not this repo"]
        You("You, in a chat session"):::yours
        Skills("Your skills<br/>order, tuning, gotchas"):::yours
    end
    subgraph agent["Any MCP agent harness"]
        Harness("Claude Code · Codex · Cursor<br/>OpenCode · Windsurf · VS Code<br/>Gemini CLI · Zed"):::harness
    end
    subgraph conn["pixinsight-mcp — this repo: operates PixInsight, nothing more"]
        Tools("~80 capability tools<br/>open · stats · run_process · pixelmath<br/>masks · stretch · blend · measure"):::repo
        Bridge("Bridge<br/>starts PixInsight and its<br/>script, talks over files"):::repo
    end
    PI("PixInsight<br/>1.9.5+"):::app
    You --> Harness
    Skills -.->|guides| Harness
    Harness -->|MCP tool calls| Tools
    Tools --> Bridge
    Bridge <-->|PJSR| PI
```

## Install

Every harness runs the same stdio process: `npx -y github:mxcoppell/pixinsight-mcp` (Node 22+ and `git` on PATH; append `#<tag>` to pin a release, e.g. `#v1.2.1`).
Nothing to clone, no path to configure: PixInsight and its watcher start on the first tool call. Check: `npx -y github:mxcoppell/pixinsight-mcp doctor`.

**Target folder.** The tools work in the folder `set_workspace` names, else `PIXINSIGHT_MCP_WORKSPACE` (`~/` allowed), else the launch folder; Claude Desktop has no useful one, so set the bundle's *Target folder* option or call `set_workspace`. PixInsight cannot run a script from a path containing a comma (or a double quote), so such a target is refused: rename the folder or set `PIXINSIGHT_MCP_STATE` to a folder without one.
The connector writes only `<target>/agentic/` (scratch, the bridge and its watcher script, logs) and `<target>/output/`, nothing in your home folder; `PIXINSIGHT_MCP_STATE` replaces `agentic/` (resolved against the target; an absolute path moves it, bridge and watcher script included, outside the target: the one explicit exception); PixInsight keeps its own settings, swap and temp files where it always does (ImageSolver's star lists, for one, when `run_pjsr` code calls it; `run_plate_solve` puts them under `agentic/scratch/`).
Every tool call, with the PJSR it sent and PixInsight's reply, is logged as JSONL in `<target>/agentic/logs/` (paths and code: check before sharing); `PIXINSIGHT_MCP_LOG=0` turns this off. The log file opens in the workspace of the first call that uses one (a `set_workspace` is logged in the workspace it sets), so a launch folder you leave before any call uses it gets nothing; calls made before that are written into the file that opens, or nowhere if none does. `workspace_info` and `doctor` show every folder in use.

| Harness | Config | Where |
|---|---|---|
| Claude Code | `claude mcp add -s user pixinsight -- npx -y github:mxcoppell/pixinsight-mcp` | `~/.claude.json` |
| Codex CLI | `[mcp_servers.pixinsight]`, `command = "npx"`, `args = ["-y", "github:mxcoppell/pixinsight-mcp"]` | `~/.codex/config.toml` |
| Cursor | `mcpServers` | `~/.cursor/mcp.json` or `.cursor/mcp.json` |
| OpenCode | `mcp` → `"type": "local"`, `"command": ["npx", "-y", "github:mxcoppell/pixinsight-mcp"]` | `~/.config/opencode/opencode.json` |
| Windsurf | `mcpServers` | `~/.codeium/windsurf/mcp_config.json` |
| VS Code / Copilot | `servers` | `.vscode/mcp.json` or the user profile `mcp.json` |
| Gemini CLI | `mcpServers` | `~/.gemini/settings.json` |
| Zed | `context_servers` | `~/.config/zed/settings.json` |
| Cline | `mcpServers` | `~/.cline/data/settings/cline_mcp_settings.json` |
| Kiro | `mcpServers` | `~/.kiro/settings/mcp.json` |
| Claude Desktop | `mcpServers`, or double-click the `.mcpb` bundle attached to a GitHub release | `claude_desktop_config.json` |

Every `mcpServers` entry is `{"pixinsight": {"command": "npx", "args": ["-y", "github:mxcoppell/pixinsight-mcp"]}}`,
plus an optional `env`. Three shapes differ: **Codex** is TOML under `mcp_servers` (underscore);
**OpenCode** wants `command` as an array; **VS Code** names the top-level key `servers`.

| OS | PixInsight looked for at | Status |
|---|---|---|
| macOS | `/Applications/PixInsight/PixInsight.app/Contents/MacOS/PixInsight` | verified |
| Windows | `C:\Program Files\PixInsight\bin\PixInsight.exe` | unverified — set `PIXINSIGHT_BIN` if it differs |
| Linux | `/opt/PixInsight/bin/PixInsight` | unverified — set `PIXINSIGHT_BIN` if it differs |

<!-- crossplatform:start -->
## Cross-platform

macOS, Windows and Linux are all first-class, for running this connector and for developing it.
A change that works on one OS and breaks another is a bug, not a limitation.

- **No shell pipelines.** No `ps`, `grep`, `awk`, `wc`, `df` or `&&` in `src/` or in npm scripts.
  OS-specific behaviour goes behind `src/platform.mjs` (paths) or `src/process-probe.mjs` (process
  inspection), one implementation per OS.
- **No POSIX path literals.** Use `path.join`. Use forward slashes only when handing a path to
  PixInsight, which accepts them everywhere.
- **CI runs the full suite on Linux, macOS and Windows.** Green on one is not green.
- **Degrade, never fail.** If an OS cannot supply something optional — a memory reading, a process
  start time — carry on without it. Never report it as a crash.
- **Overrides always win:** `PIXINSIGHT_BIN` for the executable, `PIXINSIGHT_DIR` for the install root.

Tests need no PixInsight and no astronomy software, so you can develop on any of the three.
<!-- crossplatform:end -->

## Models

| Needs | Why |
|---|---|
| **Vision — required** | `save_preview` returns a JPEG path; the agent has to look at it. Without vision it works blind |
| Sonnet-class or better | weaker models lose tool-call reliability and error discipline first |
| Long multi-step tool use | tens to a hundred sequential calls, some taking minutes, all state inside PixInsight |
| Long context | skill text, ~80 tool schemas and a transcript of numeric results |

Closed: the Claude Sonnet/Opus, GPT-5 and Gemini Pro families. Open-weight: multimodal lines such as Kimi, MiniMax
and Qwen's vision checkpoints. The trap: pick a family's **multimodal** checkpoint, not its coding one — top open coding models are often text-only.

## Tools

<!-- tools:start -->
78 tools. Generated by `npm run docs:tools` from the live catalog; do not edit by hand.

| Tool | Group | What it does |
|---|---|---|
| `run_plate_solve` | Astrometry | Plate solve an open image with ImageSolver against the local Gaia DR3/SP database (offline) |
| `copy_astrometric_solution` | Astrometry | Copy the astrometric solution (WCS) and observation keywords from a source image file to a target view |
| `combine_channels` | Channels | Combine 3 mono views into a single RGB color image using ChannelCombination |
| `align_to_reference` | Channels | Align a target image to a reference image using StarAlignment |
| `run_per_channel_abe` | Channels | Run AutomaticBackgroundExtractor (ABE) separately on the R, G and B channels of a color view, then recombine them into the view with ChannelCombination |
| `lrgb_combine` | Channels | Replace the lightness of an RGB view with a grayscale L view using LRGBCombination, in place on the RGB view |
| `multi_scale_enhance` | Detail | Masked three-scale LocalHistogramEqualization on a view, in one call, with an optional HDRMultiscaleTransform pass |
| `shell_detail_enhance` | Detail | Protected high-pass detail enhancement at two scales, in place |
| `run_pjsr` | Execute | Run a PJSR (JavaScript, V8 engine) snippet inside PixInsight and return its console output |
| `run_pixelmath` | Execute | Run an arbitrary PixelMath expression in place on a view |
| `pixelmath_new_image` | Execute | Run PixelMath to create a NEW image from expressions that reference other open views by id |
| `run_process` | Execute | Instantiate any PixInsight process by its PJSR constructor name, assign JSON-valued parameters onto the instance, and execute it on a view (when view_id is given) or globally (when it is omitted) |
| `run_pjsr_file` | Execute | Run a PJSR (JavaScript, V8 engine) source file from disk inside PixInsight and return its console output |
| `open_image` | Images | Open an XISF/FITS image file in PixInsight |
| `close_image` | Images | Close an image window to free memory |
| `list_open_images` | Images | List all currently open images in PixInsight with their dimensions and color status |
| `rename_view` | Images | Rename an image view to a different view ID |
| `clone_image` | Images | Clone an image to a backup view, which can be restored from later with restore_from_clone |
| `restore_from_clone` | Images | Restore an image from a backup clone, replacing all changes since the clone was made |
| `crop_image` | Images | Crop pixels off the edges of an image, in place |
| `get_image_dimensions` | Images | Get dimensions, channel count, and color status for one or more views |
| `get_image_stats` | Images | Get image statistics: median, MAD, min, max, per-channel medians |
| `export_image` | Images | Write an image to a file in the workspace's output or state folder |
| `list_processes` | Introspect | List every PixInsight process available on this installation, by its PJSR constructor name (e.g. "SCNR", "PixelMath") |
| `describe_process` | Introspect | Describe one PixInsight process by its PJSR constructor name: whether it can run on a view and/or globally, its current parameter values and their types, and any named numeric constants it exposes for those parameters |
| `create_luminance_mask` | Masks | Create a luminance mask from a color view: Y = 0.2126R + 0.7152G + 0.0722B, then an optional blur and shadow clip |
| `apply_mask` | Masks | Apply a mask to a target view |
| `remove_mask` | Masks | Remove the current mask from a view |
| `close_mask` | Masks | Close and delete a mask window to free memory |
| `create_zone_masks` | Masks | Create core, shell and halo masks from three fixed luminance thresholds |
| `create_adaptive_zone_masks` | Masks | Create three masks from percentiles of the image's own luminance |
| `measure_uniformity` | Measure | Measure background uniformity via 4-corner median stddev |
| `measure_stars` | Measure | Measure the stars of a view by pixel sampling |
| `measure_star_layer` | Measure | Measure a star layer (a mostly black view holding stars) |
| `measure_ringing` | Measure | Measure concentric oscillation around the brightest region |
| `measure_sharpness` | Measure | Measure sharpness as the mean Sobel gradient energy (gx² + gy²) of luminance over every 4th pixel of a region |
| `measure_core_clipping` | Measure | Measure how much of the brightest region is above a level |
| `measure_clipped_blocks` | Measure | Count image blocks with pixels above a level |
| `measure_highlight_texture` | Measure | Measure the texture of the bright subject zone |
| `measure_saturation` | Measure | Measure HSV saturation, (max - min) / max, of subject pixels of a colour view: every 8th pixel whose luminance is above the luminance of the channel medians + 5 x (median \|luminance - that\| on a 32 px grid) |
| `measure_tonal_presence` | Measure | Measure subject and background tones |
| `measure_bright_chroma` | Measure | Measure chroma, (max - min) / max, of the bright pixels of a colour view: every 8th pixel whose mean of R, G and B is above brightness_threshold |
| `measure_subject_detail` | Measure | Measure subject brightness, detail and contrast |
| `ha_inject_red` | Narrowband | Add Ha to the red channel of an RGB view in place, where Ha exceeds R by a given fraction |
| `ha_inject_luminance` | Narrowband | Raise the luminance of an RGB view in place where Ha exceeds it, keeping colour ratios |
| `extract_pseudo_oiii` | Narrowband | Create a mono view from the B channel of an RGB view minus its scaled R channel |
| `continuum_subtract_ha` | Narrowband | Subtract the scaled R channel of an RGB view from an Ha view, in place |
| `dynamic_narrowband_blend` | Narrowband | Add Ha and OIII (mono views) to an RGB view in place, through a temporary luminance mask |
| `create_synthetic_luminance` | Narrowband | Create a mono view from a weighted sum of Ha and OIII |
| `save_preview` | Preview | Save a JPEG preview of a view and return the file path |
| `save_and_show_preview` | Preview | Alias for save_preview |
| `run_scnr` | Processes | Run SCNR (Subtractive Chromatic Noise Reduction) to remove a green colour cast from a view |
| `run_gradient_correction` | Processes | Run GradientCorrection on a view to remove a background gradient |
| `run_background_neutralization` | Processes | Run BackgroundNeutralization to equalize the background level across channels |
| `run_nxt` | Processes | Run NoiseXTerminator to reduce noise on a view |
| `linear_fit` | Processes | Run LinearFit to scale a linear image to match a reference view |
| `run_lhe` | Processes | Run LocalHistogramEqualization (contrast-limited local histogram equalization) on a view |
| `run_bxt` | Processes | Run BlurXTerminator on a view |
| `run_sxt` | Processes | Run StarXTerminator to separate stars from a view, replacing it in place with the starless result and producing a separate stars view |
| `run_abe` | Processes | Run AutomaticBackgroundExtractor (ABE) on a view, replacing it in place with the corrected result |
| `run_hdrmt` | Processes | Run HDRMultiscaleTransform on a view |
| `run_curves` | Processes | Apply a CurvesTransformation to a view |
| `find_filters` | Processes | Search PixInsight's built-in filter and camera QE database by name (case-insensitive substring) |
| `run_spfc` | Processes | Run SpectrophotometricFluxCalibration: writes the flux metadata that run_mgc requires |
| `run_spcc` | Processes | Run SpectrophotometricColorCalibration (SPCC) |
| `run_mgc` | Processes | Run MultiscaleGradientCorrection using the MARS reference database |
| `pixinsight_info` | Session | Report the resolved PixInsight installation paths for this platform and the connector version |
| `star_protected_blend` | Stars | Blend a stars-only image into a starless one in place, as a screen blend that turns colour-preserving in bright star cores |
| `restore_star_color` | Stars | Restore the colour ratios of a reference view in the bright areas of a target, in place, keeping the target's luminance |
| `robust_median_stretch` | Tone | Stretch a linear view in place so its median lands on target_median |
| `stretch_stars` | Tone | Stretch a linear star image in place with a pedestal subtraction and a repeated midtones transfer function |
| `auto_stretch` | Tone | Stretch a view in place with PixInsight's auto-stretch (the ScreenTransferFunction Auto Stretch computation), applied as a HistogramTransformation |
| `continuous_clamp` | Tone | Compress bright values in place above a knee that varies per pixel |
| `scan_workspace` | Workspace scan | Scan the working folder (recursively, any subfolder name) for XISF/FITS files and report each one's FILTER header value, geometry, exposure, whether it has an astrometric solution (WCS keywords CTYPE/CRVAL with a CD, CDELT or PC matrix, or PixInsight's PCL:AstrometricSolution properties), and its INSTRUME, TELESCOP, FOCALLEN, XPIXSZ, YPIXSZ and XBINNING keywords verbatim (null when absent) |
| `workspace_info` | Server | Report the workspace folder, the state directories under it (scratch, bridge, logs) and the output folder |
| `set_workspace` | Server | Set the workspace folder this session's files go under (scratch files, the bridge, call logs) |
| `resume_bridge` | Server | Allow PixInsight commands again after the user pressed Pause/Abort |
| `list_packs` | Server | List the runtime tool packs discovered at server startup, with load status, tool counts, why any pack was skipped, and which core tools packs replaced |
<!-- tools:end -->

## Packs

A pack is an ES module outside this repo that adds tools, or replaces core ones, in every session. List its
folder or `.mjs` file in `PIXINSIGHT_MCP_PACKS`; `list_packs` and `doctor` show what
loaded and why. **A pack is arbitrary code running with your privileges:** only packs you configure load,
and nothing is ever fetched. Writing and loading one: [CONTRIBUTING.md](CONTRIBUTING.md#developing-a-pack).

## Skills are where technique lives — yours or the community's

The connector ships capability only: every tool takes the values that shape the picture as inputs and
reports numbers, never a verdict. Which tools, in what order, with which values, and what counts as good
enough is technique, and it lives in **skills**: markdown (a validated order, what to ask the user first,
the values and thresholds to use, what to check at each step) in repositories anyone can publish or keep
private. A pack can still add your own formulas as tools. A workflow that works becomes a repository others
install in one line.

**Have one that works? Add it to [COMMUNITY.md](COMMUNITY.md) in a one-line PR.**

## Contributing

- **A PixInsight capability with no tool yet** → [CONTRIBUTING.md](CONTRIBUTING.md): one module in `src/tools/`, no registry.
- **A pack or a skill** → [COMMUNITY.md](COMMUNITY.md); no connector change needed.
- Humans and agents both welcome. `npm test` needs no PixInsight.

## Credit, and why this was rearchitected

This project began as **[aescaffre/pixinsight-mcp](https://github.com/aescaffre/pixinsight-mcp)** by
Alain Escaffre, who built the file-IPC bridge, the PJSR watcher, and most of the tool catalog this
connector inherits. MIT then, MIT now.

That project aimed at a fully autonomous processing pipeline: connector, LLM orchestrator and a large body of
processing knowledge in one repository. For a connector that meant hard-won, personal knowledge (tuned parameters,
orderings, blend formulas) shared publicly rather than on its authors' terms, no install without cloning (in practice
for one harness), and a 3,000-line catalog to navigate to add one tool. So the pipeline was retired and the repository
split: the connector stays generic and installable; the knowledge moved to repositories its authors control.

## Troubleshooting

| Symptom | Cause and fix |
|---|---|
| `Could not find a PixInsight installation`, or `Watcher did not start` | Not at the default path: set `PIXINSIGHT_BIN`, and `PIXINSIGHT_DIR` (the install root) if `doctor`'s `imagesolver` check fails |
| A long call is dropped after about a minute | The harness timed out. The server sends progress keepalives only when the harness requests progress (sends a `progressToken`); if it does not, or ignores them, raise its MCP tool timeout |
| `View not found: …` | A view id was mistyped or the view was closed; the error lists the views that are open |
| `run_plate_solve` fails | It needs an RA/Dec seed near the true center (`ra_deg`, `dec_deg`); a wrong seed fails to solve |
| `PixInsight is running but this target's watcher never started` | PixInsight runs one script at a time: a session in another target folder (or a 1.0 session, or a long script) may hold it, so retry when it is free and restart any 1.0 session after upgrading. Or PixInsight could not open the watcher script: its Process Console says why |
| `STOPPED BY USER` | Pause/Abort was pressed in PixInsight; nothing runs until you say continue and the agent calls `resume_bridge` |

TDQS

B3.3/5.0

Scored across 78 tools

Disambiguation2/5

Several tools have genuinely blurred boundaries: save_preview and save_and_show_preview are exact duplicates, the measure_* family has multiple Sobel-based texture/detail/sharpness variants, and run_pjsr/run_pjsr_file plus run_process/run_pixelmath form overlapping fallback clusters. The individual descriptions are detailed, but an agent will struggle to reliably pick between these near-neighbor tools.

Naming Consistency3/5

The dominant snake_case verb_prefix pattern (run_*, measure_*, create_*, get_*, list_*) is mostly followed, but there are clear deviations: linear_fit lacks the run_ prefix used by every other process wrapper, pixelmath_new_image sits awkwardly beside run_pixelmath, and several tools lead with the object or adjective (ha_inject_red, lrgb_combine, auto_stretch) rather than a verb. The set is readable but not consistently predictable.

Tool Count1/5

At 78 tools this is an extreme count for an MCP surface, even given PixInsight's broad domain. The set contains many narrow near-variants that could be consolidated, such as 14 measure_* functions, multiple narrowband blending tools, and a duplicate preview alias. This volume will overwhelm context windows and make tool selection considerably harder.

Completeness4/5

The surface covers the main astro-image processing workflow well: opening images, measuring, stretching, background/color correction, star handling, plate-solving, masking, workspace management, and exporting. A few natural operations such as image calibration, stacking, and resampling are not first-class tools, but run_process, list_processes, and run_pjsr provide workable fallbacks.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive