Umbra open SAR archive

io.github.reesehammerPublished by GitHub account reesehammerv0.1.4Updated Oct 30, 2026

Search, preview, and measure Umbra open SAR from any MCP client. Umbra ships no search API.

VerifiedStreamable HTTPWeb executableWeb Search & ScrapingData & AnalyticsMedia & Design

Overview

AI-generated overview

Search, preview and measure Umbra's open SAR satellite archive, with change composites, stack statistics and scene readings.

What you can do
  • Search the archive by bounding box, polygon, place name or task, and rank the most repeat-imaged sites
  • Fetch one item's full metadata card, quicklook images and asset downloads with a size check
  • Build change composites, timescan activity images, stack statistics and provenance checks across passes
  • Pick the two passes worth narrating and report only acquisitions new since the last site check
  • Find visually or textually similar scenes, and describe or narrate scenes with optional model help
When to use it
Useful when you want an assistant to explore open SAR imagery without writing glue code: finding acquisitions over an area, seeing quicklooks, and measuring or narrating change at a site over time. Also handy for repeat-site monitoring and building change-detection inputs.
Requirements
Either the hosted remote endpoint or a local process run with uvx from the umbra-py package (Python 3.10+). No credentials needed for open data. Optional secrets: ANTHROPIC_API_KEY or OPENAI_API_KEY for server-side vision and text-embedding tools, UMBRA_CANOPY_TOKEN to query Umbra's authenticated commercial archive instead of the open bucket. An optional local catalog index (UMBRA_INDEX_DB) makes searches fast; without it each search walks the public bucket. Network access required.
Before you install
Setting UMBRA_CANOPY_TOKEN switches searches to a commercial archive and sends that token to the server. Vision keys, if set, mean scene images are sent to Anthropic or OpenAI for interpretation. download_asset writes files to local disk and SAR products can be multiple gigabytes, so confirm sizes first. The hosted API is best-effort with no SLA and rate limits. Data is CC BY 4.0 and requires keeping the Umbra attribution with derived products.

Installation

In SourceWeft

  1. Open Umbra open SAR archive in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/io.github.reesehammer%2Fumbra-mcp/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config.

{
  "mcpServers": {
    "umbra-open-sar-archive": {
      "type": "http",
      "url": "https://api.umbra-py.space/mcp"
    }
  }
}

README

umbra-py

[License] [Python] [CI] [codecov] [Docs]

Search, preview, load, and convert Umbra open SAR data.

Umbra publishes 16–25 cm SAR as CC BY 4.0 open data, but no search API — only a 17+ TB S3 bucket and a static STAC tree. umbra-py is that layer: search, preview, download, and analysis-ready arrays without the usual 500 lines of glue. A community STAC API (umbra serve) and MCP server sit on the same host, so pystac-client and Claude can query the archive with nothing installed.

📖 Docs: umbra-py.space · Showcase: browse the archive in the browser (no install) · Already listing the bucket? Replace your crawler (HTTP, pystac-client, or DuckDB over the weekly Parquet)

Status: v0.1.4. Discovery, download, xarray loading, SICD → geocoded COG, change/timescan composites, chips, a STAC API (umbra serve, with a community host), and an MCP server all ship. This is not an InSAR toolbox (phase is not preserved through convert). Not affiliated with Umbra Lab, Inc.

Hosted API (api.umbra-py.space): best-effort 99% monthly uptime, no SLA, 120 requests/minute, page size 10,000. Limits, CORS, and the parquet fallback.

Install

bash
pip install umbra-py              # core: search + download + metadatapip install "umbra-py[load]"      # + xarray / rasteriopip install "umbra-py[viz]"       # + quicklooks, maps, galleriespip install "umbra-py[convert]"   # + SICD → geocoded COGpip install "umbra-py[all]"       # convert + load + viz + export

Python 3.10+. Other extras (dask, serve, mcp, ai, langchain, llamaindex) are listed in the install guide.

Five minutes to a scene

Fetch the weekly catalog snapshot, then search and preview offline. A live walk of the bucket (umbra search without --local) works but is slow.

bash
pip install "umbra-py[viz,load]"umbra index fetchumbra search --local --area Centerfield --product GEC --limit 3umbra gallery --local --area Centerfield --limit 6 --out gallery.html --db
python
from umbra_py import CatalogIndex, to_xarray
with CatalogIndex.from_release() as index:    item = next(iter(index.search(area="Centerfield", product_types=["GEC"], limit=1)))
# Stream a downsampled window over HTTP — no multi-GB download. Needs [load].da = to_xarray(item, max_size=1024, db=True)print(item.summary())

If the snapshot is missing, the same search against the live bucket is UmbraCatalog().search(...) / umbra search --area Centerfield.

What you can do

More detail, options, and caveats live in the docs.

Search by bbox, place name, polygon, or Umbra task (area=). --local reads the snapshot; omit it to walk S3.

python
from umbra_py import UmbraCatalog
for item in UmbraCatalog().search(area="Centerfield", product_types=["GEC"], limit=5):    print(item.summary())

Preview without downloading the scene: umbra gallery, umbra quicklook <stac-url> --out scene.png --db, umbra view <stac-url> (full-res tiles), or umbra change --area Centerfield --out change.png.

Load a geocoded GEC into xarray or a GeoTIFF (to_xarray, to_geotiff, to_stack). Needs [load].

Convert a SICD to a north-up amplitude COG (sicd_to_geocoded_cog, umbra convert) — phase is discarded. Needs [convert]. Open products generally have no radiometric metadata, so --calibrate / --noise-model measured refuse rather than invent numbers. See limitations and the complex-product handoff.

Chip scenes into georeferenced ML tiles for SR / ATR-style benchmarks from open Umbra GEC/SICD: umbra chips --area Centerfield --out chips/. See the ISR training-set cookbook and Used in research.

Drive it from an agent. Copy-paste recipes for Claude Desktop and Claude Code: Connect Claude (MCP).

Zero-install remote MCP (no uvx):

bash
# Claude Codeclaude mcp add --transport http umbra https://api.umbra-py.space/mcp --scope user
json
{  "mcpServers": {    "umbra": {      "url": "https://api.umbra-py.space/mcp"    }  }}

Paste that JSON into Claude Desktop (claude_desktop_config.json). Claude Code needs "type": "http" on the same URL — see the MCP page.

Local stdio (server on your machine):

bash
uvx --from 'umbra-py[mcp]' umbra-mcp
json
{  "mcpServers": {    "umbra": {      "command": "uvx",      "args": ["--from", "umbra-py[mcp]", "umbra-mcp"]    }  }}

That command is published to the MCP registry as io.github.reesehammer/umbra-mcp. STAC for pystac-client / QGIS is https://api.umbra-py.space/ (not /mcp). docker compose -f deploy/docker-compose.yml up is the one-command self-host.

What the data looks like

AssetWhat it isUse it for
GECGeocoded cloud-optimized GeoTIFFMap-ready imagery. Start here.
CSIColor sub-aperture GeoTIFFQuick-look RGB, not a measurement
SIDDGeocoded detected image (NITF)Detected imagery in a standard format
SICDComplex slant-plane image (NITF). Open archive: RGAZIM/PFA.Phase-preserving downstream. Download; do not convert.
CPHDCompensated phase historyCustom formation outside umbra-py (download; do not convert). Not an image.

umbra-py downloads SICD/CPHD. umbra convert geocodes a SICD to amplitude and discards phase. It does not form interferograms or compute coherence. For a processor that needs the complex pixels, see Complex products (SICD/CPHD).

Data license & attribution

Umbra's imagery is CC BY 4.0. If you use or redistribute the data or derived products you must attribute Umbra. The copyright notice Umbra's terms require is quoted under Citing umbra-py. umbra-py also stamps this shorter line on its own outputs:

Contains Umbra open data, licensed under CC BY 4.0.

umbra-py itself is Apache 2.0 (LICENSE). The two licenses are independent and compatible.

Citing umbra-py

[DOI]

Cite all versions with the concept DOI 10.5281/zenodo.23676715 (Zenodo's Cite all versions? value). It resolves to the latest archived release. The v0.1.4 version DOI is 10.5281/zenodo.23676716. Machine-readable metadata lives in CITATION.cff (GitHub's Cite this repository button) and .zenodo.json (what Zenodo archives).

bibtex
@software{hammerstrom2026umbrapy,  author  = {Hammerstrom, Reese},  title   = {umbra-py},  year    = {2026},  version = {0.1.4},  doi     = {10.5281/zenodo.23676715},  url     = {https://github.com/reesehammer/umbra-py},  license = {Apache-2.0}}

Imagery is © Umbra Space, licensed CC BY 4.0. Umbra's terms require this copyright notice on or adjacent to adapted materials, with [YEAR] the year the image was first created:

SAR-derived data © [YEAR] Umbra Lab, Inc.

Also credit Umbra, link the license, and say if you changed the data. The open-data program is umbra.space/open-data.

Repo layout

PathRole
src/umbra_py/Package source
docs/Published user manual (mkdocs → umbra-py.space)
docs/schemas/Public JSON contracts (also in the wheel)
.github/TODO.mdMaintainer ledger of scoped-out follow-ups
deploy/Dockerfiles, docker-compose.yml, entrypoint
railway.tomlRailway Config-as-Code (dockerfilePath → deploy/Dockerfile.mcp)

Self-host: docker compose -f deploy/docker-compose.yml up (build context stays the repo root). More in docs/README.md and the deploy guide.

Community

Acknowledgements

Built on the SAR open-source community, including sarpy and Umbra's open data program. Not affiliated with or endorsed by Umbra Lab, Inc.

Source: README.md at commit 62ed4a8

Tools

19

search_catalog

Search Umbra's catalog and return compact context cards. Filters (all optional, combine freely): ``bbox`` as ``[min_lon, min_lat, max_lon, max_lat]`` in WGS84 degrees; ``intersects`` as a GeoJSON polygon object (a ``Polygon`` / ``MultiPolygon`` geometry, or a ``Feature`` / ``FeatureCollection`` wrapping one) to keep only footprints intersecting it -- a tighter spatial filter than the rectangular ``bbox``, and mutually exclusive with ``bbox`` / ``place``; ``place`` as a free-text name geocoded to a bbox; ``area`` as a substring of the Umbra task (site) name (set ``fuzzy`` to match it loosely -- word-order- and punctuation-independent and typo-tolerant, resolved deterministically with no model call, so ``"beet piler"`` still reaches ``"Beet Piler - ND"`` and ``"utah centerfield"`` still reaches ``"Centerfield, Utah"``; set ``semantic`` instead to treat ``area`` as a plain-language *description* of a site whose name you don't know and resolve it to the closest task names by *meaning* via the prebuilt embedding index, so ``"grain storage north dakota"`` reaches ``"Beet Piler - ND"`` -- a query that shares no word with the label, which ``fuzzy`` cannot and should not fake; ``semantic`` and ``fuzzy`` are mutually exclusive, and the resolved task name is returned as ``resolved_area`` with the ranked candidates in ``semantic_matches`` so you can audit and retry with a different one, and ``min_score`` -- a cosine threshold in ``[0, 1]`` -- drops weak aliases, so a description that matches nothing confidently returns an empty result rather than an arbitrary top pick); ``start``/``end`` as acquisition-date bounds -- an ISO ``YYYY-MM-DD`` date, a bare year/month (``2024``, ``2024-03``), or a relative expression (``today``, ``yesterday``, ``3 months ago``, ``last month``), resolved deterministically with no model call; ``products`` to restrict to product types (any of

Parametersendareabboxfuzzylimitlocalplacestartproductssemanticcanonicalmin_score+6 more

find_repeat_sites

Rank the archive's most repeat-imaged sites — where change detection has something to measure. The discovery step *before* every analysis verb. ``change`` / ``timescan`` / ``stack`` / ``pick_change_interval`` / ``narrate_change`` all answer *what* changed at a site, but each assumes the caller already knows *which* site to point them at. Umbra files every pass of an area under one task (site), so a site's coverage is just how many dated passes share its task; the sites with the most are exactly the ones where a time series exists to analyse. This tool ranks them, so ``find_repeat_sites → pick_change_interval → narrate_change`` is a complete chain a model can drive with no site known in advance — the deterministic answer to "where is there something to look at?" (the discovery moat; no model is called). Reach for this (or ``search_catalog(area=..., fuzzy=True)``) before ``semantic=True`` when the site name is roughly known. The filters scope the *pool* of acquisitions the ranking is computed over, and are the same ones ``search_catalog`` takes: ``bbox`` (``[min_lon, min_lat, max_lon, max_lat]`` WGS84), ``intersects`` (a GeoJSON polygon, mutually exclusive with ``bbox`` / ``place``), ``place`` (free-text, geocoded to a bbox), ``area`` (a task-name substring, loosened by ``fuzzy``), ``start`` / ``end`` date bounds, ``products`` (GEC / SICD / SIDD / CPHD), and the SAR property filters ``polarizations`` / ``min_incidence`` / ``max_incidence`` / ``max_resolution``. ``top`` caps how many sites are returned, and ``min_passes`` is how many passes a site needs to qualify (2 is the minimum a change composite can use; raise it to find only deeply-revisited series), counted on the depth ``rank_by`` measures -- raw dated passes by default, the usable (comparable) series' depth under ``rank_by="comparable"``. ``rank_by`` chooses the order: ``"passes"`` (the default) ranks by

Parametersendtopareabboxfuzzylimitlocalplacestartrank_byproductsintersects+13 more

get_item

Return the full context card for one item. ``url`` is the JSON URL of an open-data STAC sidecar, a GEC/SICD (or other product) filename such as ``2025-05-17-17-43-46_UMBRA-08_GEC.tif``, or the acquisition stem ``2025-05-17-17-43-46_UMBRA-08``. A filename or stem is resolved through the local catalog index: the stem is matched against asset hrefs, which is what distinguishes ``sar-data/tasks/``, ``sar-data/task-data/``, and ``open-data/`` — the name itself does not. Zero matches is an error. More than one match lists the candidate STAC URLs; pass one of those. The hosted server resolves a filename or stem with the catalog index it already loaded. A local server does the same after ``umbra index fetch``. When the index contains a STAC URL, the indexed snapshot is returned — the same document ``search_catalog`` already answered from — rather than a live re-fetch that can disagree on asset filenames or product types. When the server is configured with a token (``$UMBRA_CANOPY_TOKEN``) and ``url`` is a bare acquisition id (no ``://``, and not a filename or stem), it is instead looked up in Umbra's authenticated commercial archive by a keyed STAC search — the retrieval complement to ``search_catalog`` over the paid catalog, since archive cards carry an ``id`` (not a fetchable sidecar URL) as their hand-off pointer. A filename or stem still resolves from the local open-data index when a token is set. On the public host a URL is fetched only when it is https on Umbra's public open-data bucket. Any other URL is refused before a request. A bare id that is not in the local index is refused the same way. The card (:meth:`UmbraItem.to_llm_context`) carries the id, ISO datetime, place, bbox, resolution, polarization *with the change-detection caveat*, the per-product-type explanations and asset URLs, and the mandatory attribution line — everything a model needs to reaso

Parametersurlrequired

geocode_place

Resolve a free-text place name to a bounding box via Nominatim. Returns ``{"bbox": [min_lon, min_lat, max_lon, max_lat], "display_name": ...}`` so the agent can inspect the box before searching, or pass it straight to ``search_catalog``'s ``bbox``.

Parametersqueryrequired

index_stats

Report on the local catalog index, if one has been built or fetched. Lets a long-running agent decide whether searches will be instant (index present) or pay a live S3 walk, and how stale the snapshot is. ``end`` is the newest acquisition that is not ``datetime_suspect``. ``datetime_suspect_count`` is how many kept rows carry that flag. ``quarantined_future_count`` is how many source rows the last build dropped for a datetime more than a day ahead (they are not in the index). ``unique_captures`` is how many rows are the canonical copy of their collect; it equals ``items`` until republished copies have been linked.

Takes no parameters.

quicklook

Return a scene's quicklook PNG as an image block. Prefers a baked catalog preview (``preview="auto"``, the default) so a public host can show the radar scene without proxying Umbra COGs. **A baked preview is typically 512 px** (the published weekly sidecar). ``max_size`` applies only to a fresh ``preview="render"`` stream; when a cached thumbnail is smaller than requested, the caption says ``max_size`` was ignored *and* includes a JSON ``{"image": {source, width, height, requested_max_size, substituted}}`` sidecar. For a higher-resolution picture (typically 1024 px), run a local ``uvx --from 'umbra-py[mcp]' umbra-mcp`` server, or download the GEC href from ``get_item``. ``preview="baked"`` refuses rather than stream; ``preview="render"`` always streams (needs ``[viz]``, refused on ``umbra serve --public``).

Parametersdburlrequiredassetpreviewmax_size

change_composite

Composite 2-3 passes of one site into a change image. Pass the STAC URLs of two or three acquisitions of the *same* site in time order. Colors encode change: bright green = signal appeared after the first date, magenta = signal vanished, grey = unchanged. Refuses to mix polarizations (HH vs VV are not comparable). Returns the image block plus a caption naming the color semantics and attribution -- and, when a pass carries no polarization metadata so same-polarization could not be *verified*, a structured caution text block naming that gap.

Parametersdburlsrequiredassetmax_size

timescan

Summarize a whole time series into one activity image. Pass the STAC URLs of a site's passes; the timescan encodes per-pixel temporal statistics so bright/colored areas are *where activity happened* over the series. Returns the image block plus attribution.

Parametersdburlsrequiredassetmax_size

stack_stats

Measure how much a site changed across its passes, as numbers. The quantitative counterpart to ``change_composite`` / ``timescan``: those return a picture of *where* change is, this returns the *how much*. Pass the STAC URLs of a site's passes (2 or more, any order); they are co-registered onto one shared grid and reduced to a per-pass statistics series plus a net baseline-to-latest record — mean/median/spread of each pass, the signed decibel change against the pass before it, the fraction of ground that moved past ``change_threshold_db``, and that fraction as an **area in km²**. Change is always reported in decibels (a ratio of backscatter is a difference on the log scale). The default ``crs="utm"`` puts the grid in the site's UTM zone so every cell covers the same ground and the area figures mean something; pass ``crs="EPSG:4326"`` for a lon/lat grid, and the areas come back ``None`` rather than wrong. ``extent="intersection"`` (the default) compares only ground every pass covers; ``"union"`` keeps all of it and compares per cell where two passes overlap. ``bbox`` (lon/lat ``[min_lon, min_lat, max_lon, max_lat]``) narrows the measurement to a sub-area, e.g. one facility inside the scene. ``blocks=N`` adds the *where* to the *how much*: the scene is cut into an N x N grid (6 is a good default; 0, the default, skips it) and the extra ``spatial`` key reports each block's own net change, a compass label and lon/lat centre to locate it by, and the consecutive pair of passes that block moved most between — plus ``peak_block`` and an ASCII ``grid_text`` heat-map of the net change. Ask for it whenever the question is *where on this site* something happened, or when a scene-wide mean looks small but the picture from ``change_composite`` shows an obvious hot spot: a change confined to one corner is averaged away scene-wide and stands out per block. ``block_series=True

Parameterscrsbboxurlsrequiredassetblocksextentmax_sizeblock_seriesspeckle_filterspeckle_windowchange_threshold_db

stack_provenance

Check whether a site's passes are one measurement, before measuring them. ``stack_stats`` refuses a selection whose rasters were made by *different* ``umbra convert`` settings — a calibrated pass differenced against an uncalibrated one puts the difference between two conversions on the time axis and reports it as change on the ground. This is the question that refusal answers, asked ahead of it: it reads each acquisition's ``UMBRA_*`` conversion record straight from its raster header (kilobytes per pass, no pixels, no co-registration) and groups the selection by what its pixel values *are*. Returns ``agrees`` (whether ``stack_stats`` would accept the selection), ``groups`` largest-first — each with the conversion ``record`` its members share, their ``item_ids`` and the ``hrefs`` to re-run on — plus ``shared`` (the record they all agree on) when they agree, ``refusal`` (verbatim the error ``stack_stats`` would raise) when they do not, and ``unreadable`` for any source that could not be opened at all, which is an incomplete answer rather than a mixed one. Reach for it in two situations. **After** a ``stack_stats`` call fails with a provenance refusal: ``groups[0]["hrefs"]`` is the largest set of passes that *do* agree, so re-running ``stack_stats`` on those is a measurement rather than a guess — the refusal itself cannot name that subset. And **before** measuring a long series you did not assemble yourself, since the check costs a header read per pass and saves the whole cube when the answer is no. A series of Umbra's published products as they ship (the common case) agrees trivially: every pass reads as ``"(unrecorded)"``, which is one value, so ``agrees`` is true and ``shared`` is empty. **No model is called.**

Parametersurlsrequiredasset

pick_change_interval

Scan a site's whole series and name the two passes worth narrating. The deterministic first half of the "watch a site over time" story, and the step that turns a wall of passes into an answer. Given the STAC URLs of a site's passes (2 or more, any order), it co-registers them into a ``to_stack`` cube, reduces it with ``stack_stats``, and returns the *one* consecutive pass-pair whose measured change stands **furthest clear of the speckle detection floor** — the pair a reader should look at first. No model is called: a number picks the frames (the determinism boundary applied to frame selection), so the choice is reproducible and safe to quote. Why this exists as its own tool: ``narrate_change`` reads a *pair* of passes, but a site has many, and "which two?" is a measurement question, not a model one — a picture past three dates encodes nothing a model can separate, so the pair has to be chosen before the model is called. This answers it. The returned ``urls`` are the two chosen passes in time order, ready to hand straight to ``narrate_change`` (or ``change_composite`` to see them): the two tools chain into scan → narrate with no server and no model in the selection. Returns ``selected_interval`` (the interval's ``earlier_id`` / ``later_id``, their datetimes, the ``changed_fraction`` and signed ``mean_delta_db``, the cube's ``false_alarm_fraction`` speckle floor, the ``excess`` of the change over that floor, and ``stands_clear`` — whether the change cleared the floor by a usable margin), ``urls`` (the earlier then the later pass's STAC URL), and the license/attribution. When ``stands_clear`` is false the series' own largest change is still inside the speckle, so the pair is offered but the reader is warned the difference may be interference rather than change. ``selected_interval`` is ``None`` (and ``urls`` empty) when the series has fewer than two comparabl

Parameterscrsurlsrequiredassetextentmax_sizechange_threshold_db

download_asset

Download one asset of an item to local disk, gated by a size check. SAR products can be multiple GB, so this is a two-step handshake: call it first with ``confirm=False`` (the default) to get the byte size without downloading, then again with ``confirm=True`` to fetch it. On a local or stdio server, ``confirm=True`` returns ``{asset, path, bytes}``. ``confirm=False`` returns a size-and-hint object and does not write a file. On a public host (``umbra serve --public``, COG streaming off), ``confirm=True`` does not transfer the file. It returns a link plan: ``mode`` ``"link"``, ``downloaded`` false, ``asset``, ``filename`` (a basename), the direct public ``href``, ``bytes``, and ``how_to_fetch``. When the href is https on the public open-data bucket, ``how_to_fetch`` is ``curl -L -C - -o <filename> <href>`` and ``umbra download ITEM --asset ASSET --dest DIR``. ``bytes`` is the STAC ``file:size`` or ``size`` when that is a non-negative integer; otherwise the host HEADs that href for ``Content-Length``, with no redirects and no retries. A href that is not on that bucket is not requested: ``how_to_fetch.curl`` and ``how_to_fetch.umbra`` are null, ``bytes`` is null, and ``note`` says the commands were omitted. A 404 or 410 from an allowlisted HEAD raises. Any other HEAD failure, including a full quick-request slot, leaves ``bytes`` null and still returns the plan. The hosted server never transfers the file. A local ``uvx --from 'umbra-py[mcp]' umbra-mcp`` server downloads to your own disk. ``confirm=False`` on this host returns the same size-and-hint object and does not write a file. It HEADs for ``Content-Length`` only when the href is https on the public open-data bucket, with no retries, and it does not follow redirects. A 404 or 410 raises, and so does any other non-redirect HEAD error. A redirect leaves ``bytes`` null. A href outside the bucket is not requeste

Parametersurlrequiredassetconfirmdest_dir

watch_site

Report only the acquisitions **new** since the last check of this site. SAR's value for monitoring is its cadence, so the natural workflow is *standing*: run the same search each time and act only on what changed. This tool packages that idempotent delta — call it in one turn, come back a day (or a session) later and call it again, and it returns just the passes published in between, not the whole list again. The scheduler is you: ask the agent to check the site, and it reports the delta. Define the site with the same filters as ``search_catalog`` — ``place`` (geocoded to a bbox), ``area`` (task/site-name substring, loosened by ``fuzzy``), ``bbox``, ``products``, and ``start``/``end`` date bounds. The watch is identified by a stable ``name`` derived from those filters (pass an explicit ``name`` to run several independent watches over the same site); ``reset=True`` re-establishes the baseline, reporting everything as new. State persists in the local catalog index's ``meta`` table (created on first use), so a watch survives across sessions with no extra setup. ``local`` selects the search backend exactly as in ``search_catalog`` — leave it unset to use the on-disk index when present and a live S3 walk otherwise; for true monitoring a live walk (``local=false``) catches freshly published passes. **No model is called** — this is pure set arithmetic over the deterministic search — and the returned ``new_items`` are context cards ready to hand straight to ``change_composite`` / ``timescan``, closing the standing-analyst loop (new pass → composite → describe) without leaving the conversation.

Parametersendareabboxnamefuzzylocalplaceresetstartproducts

find_similar

Find archived scenes that **look like** the acquisition at ``url``. This is the one search that lives in the pixels, not the metadata: where ``search_catalog`` filters by date/bbox/task-name, this renders the query item's quicklook, embeds it, and ranks the pre-embedded scene index by visual (cosine) similarity — "find scenes that look like this flooded field / crowded berth / runway". The query item is excluded from its own results. Requires a scene-embedding index built ahead of time with ``umbra embed build`` (a sidecar ``catalog.embed.db`` beside the catalog index) and a user-supplied multimodal embedding key (``OPENAI_API_KEY``, optionally ``OPENAI_BASE_URL`` / ``UMBRA_SCENE_EMBED_MODEL``) — the only model call is turning the query image into a vector; ranking is deterministic. ``asset`` picks which product's quicklook to embed (default ``GEC``; match how the index was built); ``model`` must match the index's embedding model. Returns the ranked matches as compact records (each with the acquisition's ``href``, so hand it to ``get_item`` / ``quicklook`` / ``change_composite`` next) plus the attribution line.

Parametersurlrequiredassetmodeltop_kmin_score

find_similar_text

Find archived scenes matching a plain-language ``query`` over their imagery. Text-to-scene search: embeds ``query`` ("ships at a berth", "a flooded field") and ranks the stored *image* vectors by cosine similarity. It only works when the index was built and this query is embedded with the **same** joint CLIP-family model whose text and image encoders share a vector space (a dimension mismatch is reported as an error; ``model`` selects it). Requires the same prebuilt ``catalog.embed.db`` (``umbra embed build``) and embedding key as ``find_similar``; the only model call is turning the text query into a vector. Returns the ranked matches as compact records (each with the acquisition's ``href`` for ``get_item`` / ``quicklook``) plus attribution.

Parametersmodelqueryrequiredtop_kmin_score

describe_scene

Return a SAR-literate reading of the scene at ``url``. The picture and the metadata card are produced deterministically; a model only interprets them. How that model is reached depends on whether this process holds a vision key: - **No key** (the public community host): this tool does **not** call a vision model. It returns the PNG plus a reading kit (SAR rules, the metadata card, the JSON schema). Produce the JSON, then call :func:`stamp_description` — that is the validation boundary (provenance-stamped ``{summary, observed_features, confidence, caveats}``). Until you stamp it, your prose is not pipeline output. A baked preview is typically 512 px; the kit names the size and that a local server can stream a higher-resolution GeoTIFF. - **Key configured** (local stdio): sends the picture to that vision model, validates through ``parse_description``, and returns the stamped object directly. The packaged SAR rules are the value over just looking at ``quicklook``: brightness is backscatter not sunlight, speckle is not structure, a dark patch may be radar shadow not water, one frame is not change. ``preview`` defaults to ``"auto"``: a baked catalog preview if this server has one, else a fresh render. ``"render"`` is refused on the public host.

Parametersdburlrequiredassetmodelpreviewmax_size

narrate_change

Return a grounded reading of *what changed* between passes. Sibling of :func:`describe_scene`. The composite and the per-block dB grid are produced deterministically; a model only narrates change those numbers support. Pass the STAC URLs in time order; mixed polarizations are refused. This tool **streams Umbra COGs** to build the composite, so a public community host refuses it (use a local ``uvx --from 'umbra-py[mcp]' umbra-mcp`` server, or describe each pass with :func:`describe_scene` / :func:`quicklook` on baked previews). On a local server: - **No vision key:** returns the composite as an MCP image block plus a reading kit (SAR change primer, the dB grid, the JSON schema). **You** (the client's model) narrate, grounded in the numbers. - **Key configured:** sends picture + grid to that vision model, validates through ``parse_narration``, and returns a provenance-stamped ``{summary, changes[], confidence, caveats[], change_stats}`` object.

Parametersdburlsrequiredassetmodelmax_size

stamp_description

Validate a client-produced scene reading and stamp provenance. This is the interpretation boundary for the no-key kit path: you read the picture ``describe_scene`` returned, produce ``{summary, observed_features, confidence, caveats}``, and pass it here. The library checks the shape, stamps CC-BY and the AI-interpretation note (the model never sets those), and — when ``image`` is the record from the kit — appends a deterministic caveat if the picture was a smaller baked preview. Until this returns, the reading is unvalidated prose, not pipeline output.

Parametersassetimagemodelitem_idreadingrequired

stamp_narration

Validate a client-produced change narration and stamp provenance. Sibling of :func:`stamp_description` for the no-key ``narrate_change`` kit. Pass the JSON you produced plus the ``change_stats`` grid from the kit so the stamped object carries the numbers the prose was grounded in.

Parametersassetmodelreadingrequireditem_idschange_stats

Version history

3
  1. v0.1.4LatestOct 29, 2026
  2. v0.1.3Oct 16, 2026
  3. v0.1.2Sep 16, 2026

Score

100% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts