Skip to main content
Glama
osviel91

geo-osiris-mcp

by osviel91
README.md
# geo-osiris-mcp

Thin, agent-facing MCP adapter over the OSIRIS Geo Hub admin API
(`/api/v1/admin/*`). Geo Hub remains the only business-logic authority:
validation, duplicate detection, provenance, freshness, and publication rules
all stay there. This server only validates agent input, translates it into Geo
Hub requests, and returns compact results.

**Phase 6D scope:** one image supports either local stdio or authenticated
Streamable HTTP. Read mode exposes 14 read tools; staging adds 8 controlled
staging tools, including `request_import_publication`. Neither mode exposes
approve, publish, commit, archive, delete, cancel, or source sync.

## Requirements

- Python 3.12
- Network access to a Geo Hub instance reachable from this process

## Configuration

| Variable | Required | Default | Purpose |
| --- | --- | --- | --- |
| `GEO_API_URL` | yes | — | Geo Hub base URL |
| `GEO_READ_TOKEN` | read mode | — | Geo API `geo.read` bearer; server-side only |
| `GEO_STAGE_TOKEN` | staging mode | — | Geo API `geo.read` + `geo.stage` bearer; server-side only |
| `GEO_HUB_MCP_MODE` | no | `read` | `read` or `staging`; anything else aborts startup |
| `GEO_MCP_TRANSPORT` | no | `stdio` | `stdio` or authenticated Streamable HTTP (`http`) |
| `GEO_MCP_HOST`, `GEO_MCP_PORT` | HTTP only | — | Listener bind address and port |
| `GEO_MCP_PATH` | no | `/mcp` | Streamable HTTP path |
| `GEO_MCP_ALLOWED_HOSTS` | HTTP only | — | Exact allowed Host headers, comma-separated |
| `GEO_MCP_CLIENT_TOKENS_FILE` | HTTP only | — | Read-only JSON file containing client-token SHA-256 hashes |

See `.env.example`. Unknown or empty `GEO_HUB_MCP_MODE` fails closed (exit 2).

## Install and run

```sh
python3.12 -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
GEO_API_URL=http://localhost:8000 GEO_READ_TOKEN=... geo-osiris-mcp
```

`stdio` remains the default for local development and the existing Docker-exec
workflow. HTTP mode requires an exact host allowlist and hashed client
credentials. Generate a client token outside the container, give its plaintext
only to one Hermes host, and put only its SHA-256 in `deploy/client-tokens.json`:

```sh
python -c 'import hashlib,secrets;t=secrets.token_urlsafe(32);print(t);print(hashlib.sha256(t.encode()).hexdigest())'
```

`deploy/client-tokens.json` is gitignored. Hermes-Zima and Hermes-Mac must use
separate credentials; do not place them in profile text, source control, or
container environment variables.

## Tools

**Read (14 tools, both modes):** `list_layers`, `get_layer`, `list_features`,
`get_feature`, `list_imports`, `get_import`, `list_import_rows`, `list_sources`,
`get_source`, `inspect_layer`, `summarize_import`, `infer_csv_mapping`,
`compare_dataset_to_layer`, `health`.

**Staging (8 tools, `GEO_HUB_MCP_MODE=staging` only):** `create_managed_layer`,
`update_layer`, `create_feature`, `patch_feature`, `stage_csv_import`,
`stage_geojson_import`, `resolve_import_row`, `request_import_publication`.

All collection tools return `{items, next_cursor, has_more}` with opaque
cursors. `summarize_import.commit_ready` is informational only; there is no
commit tool. Staging tools force `mode=managed` on layer creation, reject
`published`/`archived` feature statuses before any HTTP request, and never
auto-resolve duplicate candidates. Import staging accepts inline content only
(bounded to the Geo Hub limits); Geo Hub owns parsing, conversion, validation,
and candidate generation.

### Orchestration

`infer_csv_mapping` is deterministic and local: it inspects headers and sample
rows, matches latitude/longitude/external_id aliases, and suggests a mapping
with per-field confidence and notes. It never calls a model and never stages
anything. Ambiguous or missing coordinates return `null` with an explanation.

`compare_dataset_to_layer` is conservative:

- `stage=false` (advisory, **zero writes**) inspects the layer and dataset
  shape and returns `authoritative: false` with advisory labels only
  (`advisory_existing_external_id`, `advisory_identity_match`). It does not
  claim duplicates or validity — Geo Hub has not evaluated the dataset.
- `stage=true` (staging mode only) creates **exactly one** staging import and
  returns `authoritative: true` with a bounded candidate preview (max 10 rows).
  It never resolves candidates, commits, cancels, or publishes.

`summarize_import` is the single authoritative implementation of
`commit_ready`; orchestration reuses it rather than reimplementing readiness.

## Docker deployment

`deploy/compose.yml` retains the idle `geo-osiris-mcp` stdio container and adds
two server-fixed HTTP services for staging validation:

- `geo-osiris-mcp-read`: read mode, `GEO_READ_TOKEN` only,
  `127.0.0.1:8765/mcp`.
- `geo-osiris-mcp-stage`: staging mode, `GEO_STAGE_TOKEN` only,
  `127.0.0.1:8766/mcp`.

Both HTTP services read the same hash-only `deploy/client-tokens.json` file.
Clients cannot choose the mode, and neither service receives approval,
publication, admin, database, Docker-socket, or host-filesystem credentials.
The stdio container is a trusted local-development compatibility shim and holds
both read and stage backend credentials so its separately launched process can
select the matching credential. Do not deploy that shim in production.

```sh
docker compose -f deploy/compose.yml --env-file <env-file> up -d --build
```

The staging services use `GEO_API_URL=http://geo-api:8000` on the external
`osiris-staging_osiris-staging` Docker network. Production must use immutable
GHCR digests, bind ports only to the private LAN/VPN interface, and place host
firewall rules in front of those ports. Do not use Nginx Proxy Manager for MCP.

## Hermes integration

Stdio remains available for local development:

```yaml
mcp_servers:
  geo-osiris:
    command: docker
    args:
      - exec
      - -i
      - -e
      - GEO_HUB_MCP_MODE=read      # staging profile uses =staging
      - geo-osiris-mcp
      - python
      - -m
      - geo_osiris_mcp.server
    enabled: true
```

For private-network HTTP, configure separate profile-scoped bearer secrets and
remote MCP endpoints, for example default → `http://<private-host>:8765/mcp`
and geo-curator → `http://<private-host>:8766/mcp`. The `Authorization: Bearer`
value is the client MCP token, never `GEO_READ_TOKEN` or `GEO_STAGE_TOKEN`.

## Secrets

`GEO_READ_TOKEN` and `GEO_STAGE_TOKEN` are injected server-side into their
respective containers. The agent must not read, print, log, echo, or commit
them. Do not put backend credentials in Hermes config, the README, or chat.

## Staging acceptance example

Against local staging (`amateur-radio-repeaters-es`: 2 features, managed,
duplicate detection `callsign` + 300 m):

1. Read profile: `inspect_layer amateur-radio-repeaters-es` → `feature_count 2`,
   `mode managed`, `identity_properties ["callsign"]`, `coordinate_radius_m 300`.
2. Staging profile: `infer_csv_mapping` → `create_managed_layer` →
   `stage_csv_import` (2-row CSV) → `summarize_import` → `total 2, valid 2,
   invalid 0, commit_ready true`; the import stays `validated` and no feature is
   published.
3. Publication boundary: asking to commit/publish must fail — the tool does not
   exist.

## Tests and lint

```sh
ruff check .
ruff format --check .
pytest
```

TDQS

B3.2/5.0

Scored across 14 tools

Disambiguation4/5

Most tools are clearly separated by resource and action (list_*, get_*, infer_*, compare_*). A few operational summaries like inspect_layer and summarize_import overlap somewhat with their get_* counterparts, but the descriptions provide enough distinction to avoid serious misselection.

Naming Consistency4/5

Tool names overwhelmingly follow a consistent verb_noun snake_case pattern such as list_layers, get_feature, and list_import_rows. The only minor deviation is health, which is a single noun rather than a verb_noun form, but it is a conventional health-check name and does not create confusion.

Tool Count5/5

Fourteen tools is well within the ideal range for a domain-specific server. Each tool covers a distinct resource or operation in the Geo Hub/import workflow, and none feel redundant or excessive.

Completeness3/5

The tool surface is strong for read-only inspection, listing, CSV inference, and advisory comparison. However, there are notable gaps around the import lifecycle: no tool commits, publishes, or archives an import, and there is no create/update/delete coverage for layers or sources, leaving the workflow incomplete for agents that need to act rather than only inspect.

Maintenance

ActivityMaintained
ResponsivenessNo issues