Skip to main content
Glama
README.md
# ODV MCP

Connect MCP-compatible AI assistants to **Ocean Data View**.

✓ Inspect ODV collections and saved views
✓ Export scientific data (ODV spreadsheet / netCDF)
✓ Render saved views, or bounded derived views, to PNG/JPG
✓ Multi-version-aware ODV compatibility, with a local capability probe
✓ **Your original collections are never opened by ODV**
✓ Works through standard MCP — no Python, no command line

Tested against **ODV 5.8.0.0 (64-bit, Windows x64)** on Windows 11.

**Ocean Data View is not included.** ODV MCP drives the ODV you already have. Download ODV from
[odv.awi.de](https://odv.awi.de) if you do not have it yet.

> This project is not affiliated with or endorsed by Ocean Data View or the Alfred Wegener
> Institute.

---

## Download and install

1. Download **`ODV-MCP-Setup-x64.exe`** from the
   [latest release](https://github.com/BD4XDA/ODV-MCP/releases/latest) and double-click it.
2. Setup asks you four things:
   * where **Ocean Data View** is (it looks for you, or you can browse)
   * which folders hold **your ODV collections**
   * where to save **figures and exports**
   * which **AI assistant** to connect
3. It finishes by running a system check and tells you whether you are ready.

No Python, no pip, no PATH changes, no JSON editing. If ODV is not installed, ODV MCP installs
anyway and tells you what to do.

**Prefer not to install?** `ODV-MCP-portable-x64.zip` runs from any folder, with no installation
and no registry changes. Useful on lab machines where you cannot install software.

### Change your settings later

Start Menu → **ODV MCP Settings**. You never need to reinstall to point ODV MCP at a different ODV
installation, add a data folder, change the export folder, or connect another AI assistant.

### Check that it works

Start Menu → **ODV MCP System Check**, or run `odv-mcp check`. It reports what works, what does
not, and what to do about it.

### If you need help

Start Menu → **ODV MCP Support Report** writes a diagnostic file you can attach to a bug report.
It contains no usernames, no machine names, no file paths and no scientific data — and nothing is
ever sent anywhere unless you send it yourself.

---

## Connecting your AI assistant

Setup can connect **Claude Desktop** and **Claude Code** for you. It reads your existing
configuration, makes a backup next to it, adds one entry, and leaves every other MCP server
untouched. Uninstalling removes only that entry.

For any other MCP client, setup shows you the two lines to paste:

```json
{
  "mcpServers": {
    "odv-mcp": {
      "command": "C:\\Program Files\\ODV MCP\\odv-mcp.exe"
    }
  }
}
```

The client needs to know only how to start the server. All configuration lives in ODV MCP's own
settings file, so adding a data folder never means editing your AI client's JSON.

---

## Example

Once connected, ask your assistant something like:

> *List my ODV collections, describe the SAVE collection, and render its station map.*

The assistant will call `odv_list_collections`, then `odv_describe_collection`, then
`odv_render_view`, and hand you a PNG. Nothing in your data folder is modified — every operation
runs on an isolated copy.

---

## Using ODV MCP without installing it

Everything above is also available from a source checkout, for development:

```bash
python -m pip install -e ".[dev]"
python -m odv_mcp            # start the MCP server
python -m odv_mcp.app check  # run the system check
```

See [Development](#development) for the full workflow.

---

## What this project does

| Capability | Tool | Launches ODV? |
|---|---|---|
| Server liveness check | `ping` | no |
| Report the detected ODV environment | `odv_status` | no |
| Bounded end-to-end diagnostics | `odv_doctor` | yes (isolated, bundled sample only) |
| Discover `.odv` collections under allowed roots | `odv_list_collections` | no |
| Parse a collection's metadata | `odv_describe_collection` | no |
| List/parse saved `.xview` views | `odv_list_views` | no |
| Export data as ODV spreadsheet or netCDF | `odv_export_data` | yes (isolated working copy) |
| Render a saved view, or a **bounded view specification**, to PNG/JPG | `odv_render_view` | yes (isolated working copy) |

**8 tools, 1 resource, 2 resource templates.** The frozen contract is documented in
[`docs/MCP_API.md`](docs/MCP_API.md).

### This server is a software layer, not a science API

It knows the ODV software: how to launch it safely, which commands are allowed, how to read its
status logs, how to verify its side effects, and how to express a bounded view specification. It
does **not** know what nitrate, iron or chlorophyll mean.

A scientific variable is **data, not an API operation**, so there is deliberately no
`render_nitrate` or `render_iron`. A science layer resolves which ODV variable id is
scientifically appropriate and asks the server to apply that decision:

```text
odv_render_view(collection_ref=..., view_spec={
    "template_view": "Lg2_SingleSection",
    "mutations": [
        {"kind": "section_variable", "var_id": "7"},     # chosen by the science layer
        {"kind": "depth_range", "depth_min": 0, "depth_max": 300},
        {"kind": "value_range", "value_min": 0, "value_max": 30},
        {"kind": "samples", "visible": True},
    ],
    "dpi": 120,
})
```

The specification is a **bounded, validated** list of software-level mutations. It accepts no raw
XML and no ODV command text; unknown kinds, unknown fields, inverted ranges, markup in text,
template paths and unbounded mutation counts are all refused. Applied changes come back with
target, before, after and reason. See [`docs/MCP_API.md`](docs/MCP_API.md) for the full
vocabulary.

### Marine workflows live in a separate skill layer

`skill_pack/` holds the **ODV Marine Skill Pack**: vertical sections, station/cruise maps and
multi-variable panels, plus variable semantics that refuse to conflate distinct measurements
(`nitrate` never resolves to `nitrate+nitrite`; `iron` is reported **ambiguous** rather than
guessed between dissolved, particulate and total).

It drives the MCP **only** through the public contract - a separate process, JSON tool calls over
stdio - and an enforceable test fails if it ever imports an MCP internal module. See
[`skill_pack/README.md`](skill_pack/README.md).

Read-only MCP **resources** mirror the inspection layer:

```text
odv://server/status
odv://collection/{collection_ref}/metadata
odv://collection/{collection_ref}/views
```

## What this project intentionally does **not** do

There is **no** tool for:

- arbitrary ODV command execution, or running a raw `.cmd` file,
- `delete_collection`, `delete_stations_matching`, `create_collection`, `import_data`,
  `open_data_file`, `open_netcdf`, DIVA, `generopt`, palette editing, macros,
- GUI automation,
- authoring `.xview` files,
- mutating an original collection in any way.

There is deliberately **no escape hatch** (`execute_odv_command`, `run_raw_odv`, …). The
generated command vocabulary is a frozen 7-command allowlist, and a unit test fails the
build if a destructive command ever appears in it.

The server also does not reverse engineer ODV's undocumented binary structures
(`.Data/data`, `.Data/metadata`, `.Data/inventory`). Where a value cannot be read from
text/XML, the field is `null` and a warning explains why; the reliable way to get a row
count is a real export, which this server validates and reports.

## Prerequisites (packaged install: none)

1. **A complete ODV 5.x program tree.** ODV is not headless: `odv.exe` only runs with its
   sibling folders (`bin_w64`, `coast`, `include`, `data`) present. If your ODV lives on a
   read-only backup or a network share, copy the program tree to a writable location first —
   **never run ODV from a read-only backup in place**:

   ```powershell
   robocopy "<your-ODV-copy>" "D:\odv\odv-5.8.0" /E
   ```

2. **Python 3.10+** and the MCP SDK:

   ```powershell
   python -m pip install "mcp>=1.10" "pydantic>=2.6"
   # required for strict image rendering validation; metadata/export work without it:
   python -m pip install Pillow
   ```

   Or install this project (recommended — gives you the `odv-mcp` command):

   ```powershell
   python -m pip install -e .
   ```

3. **ODV version tested:** 5.8.0.0 (64-bit). `odv.exe` SHA-256
   `071baafc5d1be4ba6bdbae0e288cdc00db005622ae35b5ce84ffab164c4b99cd`.

## Configuration

Configuration comes from, in increasing priority: built-in defaults → a JSON config file
(`--config` / `ODV_MCP_CONFIG`) → environment variables. No tool ever accepts an
arbitrary path: collection reads are confined to `allowed_roots` and writes to
`export_roots` / `extra_export_roots` / the managed workspace.

| Environment variable | Meaning | Default |
|---|---|---|
| `ODV_MCP_ODV_EXE` | Full path to `<ODV install>/bin_w64/odv.exe` | auto-detect |
| `ODV_MCP_ODV_HOME` | ODV runtime home (resources, `.temp`) | `%USERPROFILE%\Documents\ODV` |
| `ODV_MCP_EXTRA_EXECUTABLE_NAMES` | Additional ODV executable file names to accept (legacy/renamed builds) | empty |
| `ODV_MCP_ALLOWED_ROOTS` | `;`-separated roots where collections may be read | empty (reads refused) |
| `ODV_MCP_EXPORT_ROOTS` | `;`-separated artifact destinations | the workspace root |
| `ODV_MCP_EXTRA_EXPORT_ROOTS` | Additional user-designated destinations | empty |
| `ODV_MCP_WORKSPACE_ROOT` | Root of the isolated working-copy workspace | `%TEMP%\odv-mcp` |
| `ODV_MCP_DISCOVERY_MAX_DEPTH` | Discovery depth limit (1–8) | `3` |
| `ODV_MCP_EXECUTION_TIMEOUT_SECONDS` | Hard wall-clock ceiling per ODV run | `600` |
| `ODV_MCP_COMPAT_PROBE_TIMEOUT_SECONDS` | Budget per capability-probe step | `120` |
| `ODV_MCP_ALLOW_UNKNOWN_ODV_VERSIONS` | Attempt operations on unverified ODV versions (UNKNOWN capabilities are tried and verified) | `true` |
| `ODV_MCP_KEEP_FAILED_WORKSPACES` | Preserve the workspace when a run fails | `true` |
| `ODV_MCP_KEEP_SUCCESSFUL_WORKSPACES` | Preserve the workspace on success | `false` |
| `ODV_MCP_LOG_LEVEL` | `DEBUG`/`INFO`/`WARNING`/`ERROR` (stderr only) | `INFO` |

`dataset_minimum_versions` (config file only) maps a dataset name to the minimum ODV version
it needs, e.g. `{"GLODAPv2.2023": "5.7.1"}`. It is reported as informational metadata and is
never inferred from collection bytes.

Example config file — see [`examples/config.example.json`](examples/config.example.json):

```json
{
  "odv_exe": "D:/odv/odv-5.8.0/bin_w64/odv.exe",
  "allowed_roots": ["D:/ODV/data"],
  "export_roots": ["D:/ODV-exports"],
  "workspace_root": "C:/odv-mcp-workspace",
  "discovery_max_depth": 4,
  "execution_timeout_seconds": 900
}
```

> **Workspace path tip (Windows).** ODV *silently ignores* a command file whose own path
> contains non-ASCII characters (measured; see §8). This server handles that automatically
> by exposing the workspace through an ASCII path view (`subst`, e.g. `Z:`), and records
> the mechanism in `provenance.isolation.ascii_path_view`. Pointing
> `ODV_MCP_WORKSPACE_ROOT` at an ASCII-only directory avoids the mapping altogether.

Check your configuration without starting the MCP loop:

```powershell
odv-mcp --check                 # JSON diagnostics report, exit code 0/1
odv-mcp --check --probe         # also run the capability probe on ODV's bundled sample
odv-mcp --print-config          # effective configuration only
odv-mcp --compat-report         # sanitized compatibility report about your ODV (no ODV run)
odv-mcp --compat-report --probe # ... after probing every capability locally
odv-mcp --allowed-root "D:/data" --odv-exe "D:/odv/odv-5.8.0/bin_w64/odv.exe"
```

`--compat-report` prints a Markdown block (or JSON with `--compat-json`) containing your ODV
version, family, capabilities, probe results and quirk identifiers — with paths, usernames,
machine names, collection names and data hashes removed. It is designed to be attached to a
GitHub issue to report that ODV MCP does or does not work with your ODV version
(see [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md) §5).

## Starting the MCP server

**Windows launcher (recommended for MCP clients — avoids shell/PATH quirks):**

```bat
scripts\start-odv-mcp.bat
```

It resolves the repository, sets `PYTHONPATH`, applies the environment variables from
`examples\client-configs\env.example.bat` when present, and runs `python -m odv_mcp`.

**Direct:**

```powershell
$env:ODV_MCP_ODV_EXE = "D:\odv\odv-5.8.0\bin_w64\odv.exe"
$env:ODV_MCP_ALLOWED_ROOTS = "D:\ODV\data"
$env:ODV_MCP_EXPORT_ROOTS = "D:\ODV-exports"
python -m odv_mcp           # speaks MCP over stdio
```

Diagnostics helper: `scripts\doctor.bat` runs `odv-mcp --check` and prints the report.

## Client configuration examples

Ready-to-edit files live in [`examples/client-configs/`](examples/client-configs/):

| Client | File |
|---|---|
| Claude Desktop | `claude_desktop_config.json` |
| Claude Code (`.mcp.json`) | `claude-code.mcp.json` |
| Cursor | `cursor.mcp.json` |
| VS Code (GitHub Copilot MCP) | `vscode.mcp.json` |
| Generic stdio client | `generic-stdio.json` |
| Windows environment template | `env.example.bat` |

Claude Desktop example:

```json
{
  "mcpServers": {
    "odv-mcp": {
      "command": "D:\\odv-mcp\\scripts\\start-odv-mcp.bat",
      "args": [],
      "env": {
        "ODV_MCP_ODV_EXE": "D:\\odv\\odv-5.8.0\\bin_w64\\odv.exe",
        "ODV_MCP_ALLOWED_ROOTS": "D:\\ODV\\data",
        "ODV_MCP_EXPORT_ROOTS": "D:\\ODV-exports",
        "ODV_MCP_WORKSPACE_ROOT": "C:\\odv-mcp-workspace"
      }
    }
  }
}
```

## First-run sequence

```text
ping                    -> {"status":"ok","server":"odv-mcp","version":"0.1.0"}
odv_status              -> detected ODV environment, roots, warnings (launches nothing)
odv_doctor              -> "can this server execute ODV workflows right now?"
odv_list_collections    -> discovery under the allowed roots
... real workflow: odv_describe_collection / odv_list_views / odv_export_data / odv_render_view
```

A typical successful conversation:

```text
1. odv_list_collections()
     -> collections[0].ref = "col_720f1a238ce8ae1d"  (SAVE.odv, ODVCF6, Ocean/Profiles, 7 variables)
2. odv_describe_collection(collection_ref="col_720f1a238ce8ae1d")
     -> meta + scientific variables, primary variable, quality-flag schema, sidecar, known views
3. odv_list_views(collection_ref="col_720f1a238ce8ae1d")
     -> 9 views incl. AllStationsMap (geographic_map, domain -66.03..22.61 x -61.06..10.40)
4. odv_export_data(collection_ref=..., output_format="netcdf")
     -> out/SAVE_export_<ts>.nc  (346 KB, netCDF-4) + <ts>_nc_variables.txt
        verification.passed = true, provenance.embedded.cross_check.data.matched = true
5. odv_render_view(collection_ref=..., view_name="AllStationsMap", image_format="png")
     -> out/SAVE_render_<ts>.png (875x706)
        verification.checks.requested_view_applied = true
```

## Safety model

Non-negotiable rules, each backed by measured evidence in the three baseline documents:

1. **Isolation by copy is mandatory and is infrastructure policy, not an agent choice.**
   Every ODV run happens on a disposable working copy; the original collection path is
   never passed to ODV, and every execution result reports whether the original stayed
   byte-identical. *Evidence:* every native ODV operation that opens a collection rewrites
   at least one state file in the collection directory, and `-access ReadOnly` is an
   ODV-semantic mode, not a filesystem zero-write mode.
2. **The process exit code is never a status signal.** ODV returns `0` for successes and
   failures alike, and stdout/stderr are always empty. Status comes from `<cmd>.log` beside
   the command file, plus postcondition verification.
3. **File existence is never success.** A failed `export_data` on an empty collection still
   writes a complete-looking header-only spreadsheet. Artifacts are structurally validated
   (provenance header, column row, non-zero data rows / netCDF container magic + companion
   map / image header + dimensions).
4. **Silent view fallback is treated as failure.** An invalid or missing `.xview` makes ODV
   fall back to the previously loaded view *without any error* while still producing an
   image. Before rendering, the server arms a sentinel in the working copy's `settings`
   view pointer and afterwards requires positive evidence that the requested view was
   applied (ODV's own recorded view pointer, or a new `~` previous-version variant). The
   result fails otherwise.
5. **No destructive command is reachable.** The generated vocabulary is
   `set_var`, `set_base_directory`, `open_collection`, `load_view`, `export_graphics`,
   `export_data`, `quit`; everything destructive is on an explicit forbidden list.
6. **Command-file path encoding.** ODV silently ignores a command file whose *own* path is
   non-ASCII (exit 0, no artifact, no log). Measured matrix:

   | command-file path | result |
   |---|---|
   | ASCII path, non-ASCII content paths | works |
   | non-ASCII path, non-ASCII content paths | **fails silently** |
   | non-ASCII working directory, ASCII command file | works |
   | `subst` ASCII drive, ASCII command file | works |

   The server therefore always hands ODV an ASCII path (mapping the workspace with `subst`
   when needed) and refuses to run otherwise.
7. **Path security.** Reads are confined to configured `allowed_roots`, writes to
   `export_roots` / `extra_export_roots` / the workspace. Paths are normalised before any
   trust decision; traversal attempts are rejected with `path_not_allowed`.
8. **Every invocation has a timeout.** On expiry the process tree is terminated, the
   termination is recorded, the workspace is preserved, and the run is never a success.

## Result envelope

Execution tools return a consistent structured envelope:

```json
{
  "status": "success",
  "operation": "odv_render_view",
  "source":   { "ref": "...", "collection": "...", "requested_view": "AllStationsMap.xview" },
  "output":   { "format": "png", "artifact": { "...": "..." }, "rendered_file": "..." },
  "verification": { "passed": true, "checks": { "requested_view_applied": true } },
  "provenance":   { "odv_version": "...", "isolation": { "isolated": true }, "embedded": {} },
  "execution":    { "exit_code": 0, "exit_code_trustworthy": false, "elapsed_ms": 1024 },
  "warnings": [],
  "diagnostics": {}
}
```

Provenance preserves ODV-native information (software version line, exported source path,
creator, timestamp, and the MD5/byte-size of the source `metadata`/`data` files), and the
server cross-checks those embedded values against the collection it actually exported from.
Nothing scientific is invented: whatever ODV did not state is reported as `null` with an
explanatory warning.

## Available tools

| Tool | Key arguments | Launch ODV | Notes |
|---|---|---|---|
| `ping` | – | no | liveness only |
| `odv_status` | – | no | executable path/version/availability, **ODV family + compatibility level + capability summary + platform support**, runtime home, workspace root, roots, warnings |
| `odv_doctor` | `include_vertical_slice=true` | isolated | program tree, home, config, workspace, allowlist, ASCII path view, `quit` probe, end-to-end probe on ODV's bundled sample, **version identification + capability probe + compatibility profile** |
| `odv_list_collections` | `root?`, `max_depth?` | no | bounded scan of allowed roots |
| `odv_describe_collection` | `collection_ref?`, `path?`, `include_payload_hashes=true` | no | direct `.odv` text parsing |
| `odv_list_views` | `collection_ref?`, `path?`, `include_standalone=true`, `include_backup_variants=false` | no | direct `.xview` XML parsing |
| `odv_export_data` | `collection_ref?`/`collection_path?`, `output_format` (`odv_spreadsheet`\|`netcdf`), `output_directory?`, `output_file_name?` | isolated | validated + provenance |
| `odv_render_view` | `view_name?`, `collection_ref?`/`collection_path?`, **`view_spec?`**, `image_format` (`png`\|`jpg`), `dpi`, `width?`, `height?`, `window`, `output_directory?`, `output_file_name?` | isolated | exactly one view source; postcondition verified; a `view_spec` reports every applied change |

Full reference with every field: [`docs/TOOL_REFERENCE.md`](docs/TOOL_REFERENCE.md). The frozen
contract, including the complete `view_spec` mutation vocabulary: [`docs/MCP_API.md`](docs/MCP_API.md).

## Available resources

| URI | Content |
|---|---|
| `odv://server/status` | the same environment report as `odv_status` |
| `odv://collection/{collection_ref}/metadata` | the same description as `odv_describe_collection` |
| `odv://collection/{collection_ref}/views` | the same listing as `odv_list_views` |

Resources never launch ODV. `{collection_ref}` is the opaque `ref` from
`odv_list_collections` (`col_<16 hex>`), which contains no reserved characters.

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| `odv_status` → `native_automation_available: false` | no complete ODV program tree found | set `ODV_MCP_ODV_EXE` to `<install>/bin_w64/odv.exe` inside a full ODV 5.x tree (copy it somewhere writable first) |
| `odv_list_collections` → `configuration_error` | `allowed_roots` empty | set `ODV_MCP_ALLOWED_ROOTS` to the directory holding your `.odv` files |
| `odv_describe_collection` → `path_not_allowed` | path outside every allowed root | add the containing directory to the allowed roots |
| `odv_export_data` → `path_not_allowed` | output directory outside the export roots | add it to `ODV_MCP_EXTRA_EXPORT_ROOTS` |
| `view_not_found` | the view is not in `<collection>.Data/views` | call `odv_list_views` first; ODV would silently fall back, so the server refuses |
| `render_validation_failed` (view specification) | the specification is not valid: unknown mutation kind, unknown field, markup in text, inverted range, a template referenced by path, or both `view_name` and `view_spec` | read `error.details.problems` for the per-field reason; the accepted vocabulary is in [`docs/MCP_API.md`](docs/MCP_API.md) |
| `capability_not_supported` | the local ODV demonstrably lacks the primitive this operation needs (for example netCDF export on an old runtime) | read `error.alternatives` for formats that *are* available on this runtime; the server never substitutes a format silently. Run `odv_doctor` for the capability profile |
| `odv_status` reports compatibility `UNVERIFIED` | the ODV version is not in the verification matrix | run `odv_doctor` to upgrade it to `PROBED_COMPATIBLE`; operations still work, they are simply not formally verified |
| `odv_status` reports compatibility `LIMITED` | a probe demonstrated that at least one primitive is unavailable | see `compatibility.operations_unsupported`; the remaining operations work normally |
| `export_validation_failed` + `no_odv_error_log: false` | ODV reported `Data export failed` (e.g. an empty collection) | confirm the collection actually holds data |
| Export/`render` reports `timed_out: true` | the collection is large | raise `ODV_MCP_EXECUTION_TIMEOUT_SECONDS`; the workspace is retained for diagnosis |
| `ascii_command_file_path` fails in `odv_doctor` | the workspace path is non-ASCII and no drive letter is free | free a drive letter or point `ODV_MCP_WORKSPACE_ROOT` at an ASCII-only directory |
| Exports land inside the workspace instead of your folder | the destination was not permitted | add it to `ODV_MCP_EXTRA_EXPORT_ROOTS` |

## Development

The distribution is self-contained, but the project is a normal Python package for development.

```powershell
python -m pip install -e ".[dev]"
python -m pytest tests -q                        # core MCP suite
python -m pytest skill_pack\tests -q             # Marine Skill suite
python -m pytest tests\distribution -q           # packaging, installer and adapter behaviour
python -m pytest tests\unit -q                   # no ODV launch, fast

# Source-level acceptance
python scripts\acceptance.py --collection "D:\...\SAVE.odv"
python scripts\mcp_e2e.py --collection "D:\...\SAVE.odv"
python scripts\skill_acceptance.py
scripts\doctor.bat --full

# Distribution
python scripts\build_distribution.py             # build + verify the packaged application
python scripts\mcp_e2e.py --collection "D:\...\SAVE.odv" --server-command dist\odv-mcp\odv-mcp.exe
```

`--server-command` runs the **same** end-to-end acceptance against the packaged binary, because
"the source version works, so the packaged version probably works" is not evidence.

Integration tests need either `ODV_MCP_ODV_EXE` or a development runtime at
`runtime/odv-5.8.0/bin_w64/odv.exe`; otherwise they skip.

Public source excludes local scientific fixtures and development history. Tests that need the
missing sample explicitly skip; see [`docs/PUBLIC_RELEASE.md`](docs/PUBLIC_RELEASE.md) for local
fixture preparation and the public privacy gate.

| Document | Contents |
|---|---|
| [`docs/DISTRIBUTION.md`](docs/DISTRIBUTION.md) | packaging choice, installer, layout, discovery, adapters, upgrade/uninstall |
| [`docs/MCP_API.md`](docs/MCP_API.md) | the frozen 8-tool contract and the `view_spec` vocabulary |
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | layers, execution model, safety invariants |
| [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md) | ODV version support matrix and capability model |
| [`docs/PUBLIC_RELEASE.md`](docs/PUBLIC_RELEASE.md) | public source boundaries and privacy verification |
| [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) | dependency licences and ODV attribution |

## Marine Skill Pack (optional)

Marine science — vertical sections, station/cruise maps, multi-variable panels, and variable
semantics that refuse to conflate distinct measurements — lives in a separate layer that drives
the MCP through its public contract only. It is not part of the MCP server and is not bundled by
the installer. See [`skill_pack/README.md`](skill_pack/README.md).

## License

**MIT** — see [`LICENSE`](LICENSE). This matches the declaration in `pyproject.toml`
(`license = { text = "MIT" }`).

> **Human decision pending:** the `LICENSE` file and the `pyproject.toml` declaration now agree, but
> the licence choice itself is the maintainer's. If a different licence is intended, change
> `pyproject.toml`, `LICENSE`, this section and `packaging/odv-mcp.iss` together.

The packaged application also redistributes third-party components under their own licences — see
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) for the per-package inventory. Those components'
upstream licence texts ship inside the distribution itself, at `odv-mcp/licenses/`.

ODV itself is © Alfred Wegener Institute, and its licence does **not** permit redistribution
without prior written consent. ODV MCP therefore bundles nothing from ODV: you supply your own
installation. This project is not affiliated with or endorsed by Ocean Data View or AWI.