Skip to content

Refactor DatasetCatalog.load() to source from MCP instead of walking STAC locally #197

Description

@cboettig

Refactor DatasetCatalog.load() to source from MCP instead of walking STAC locally

Status — parked / backlog

Design captured below. Will be picked up when one of the forcing functions triggers, not before. The "MCP vs. direct STAC" question for DatasetCatalog.load() has been resolved twice already (#167, #171); the current production answer (direct-fetch + #192 inline-forwarding + #178's collection_url escape hatch) is adequate. Do not re-debate without a forcing function.

Decision history (so we don't go round in circles again)

Goal

Make mcp-data-server the single owner of STAC topology. DatasetCatalog.load() stops walking the catalog and instead asks MCP for the structured collection content of each entry in layers-input.json. Unifies Wyoming-style private-data deployments with the public-data path; removes the 1-level walk and the collection_url per-entry escape hatch.

Why it's parked (current state is adequate)

  • Public catalogs boot via direct fetch + 1-level child walk. Fast, parallel, no MCP dependency at boot.
  • Multi-level / private catalogs use per-entry collection_url (Wyoming pattern). Deterministic, deployment-portable.
  • MCP get_schema already gets cached STAC inline (get_schema: forward cached STAC content inline to MCP #192). No refetch.
  • "Cleaner ownership" and "non-browser MCP-client reach" are real benefits, but speculative — no measured pain point.

Forcing functions to unpark

Reopen active work when one of these triggers:

  1. A new deployment shape can't be expressed via collection_url — topology needs >1-level traversal and can't be flattened.
  2. A non-browser MCP client (CLI, notebook, second app) needs authenticated reach to in-app private metadata.
  3. mcp-data-server#117 ships for unrelated reasons (removes the biggest single prerequisite for free).
  4. An independent DatasetCatalog refactor opens this up at no extra cost.

Hard prerequisites

  1. mcp-data-server#117s3_key/s3_secret/s3_endpoint/s3_scope on the STAC tools. Without it, Wyoming-style apps still need inline forwarding for private leaves. Currently scoped at 2–3 days.
  2. Batch endpoint on mcp-data-server — get_collections(catalog_url, collection_ids: [...]) returning {id: collection_dict, ...}. Non-negotiable for perf at 30+ datasets (see Performance). File separately.
  3. mcp-credential-proxy extended to forward S3 creds on the new batch tool and the singular STAC tools.
  4. Soft-fail fallback in geo-agent — keep the existing direct-fetch path as a fallback when the batched MCP call errors. Lets this land before all upstream prerequisites are buffed.

Design

Proposed change

Replace the walk with a single batched MCP call:

async load(appConfig) {
    this.catalogUrl = appConfig.catalog;
    const ids = appConfig.collections.map(c =>
        typeof c === 'string' ? c : c.collection_id);
    const optionsMap = buildOptionsMap(appConfig.collections);

    const dicts = await mcpClient.callTool('get_collections', {
        catalog_url: appConfig.catalog,
        catalog_token: appConfig.catalog_token,
        collection_ids: ids,
    });

    for (const [id, collection] of Object.entries(dicts)) {
        const options = optionsMap.get(id) || {};
        await this.processCollection(collection, options);
    }
}

_rawStac cache populated from these responses, so #192's inline-forwarding path for get_schema keeps working unchanged.

Performance

Batching is non-negotiable. For ~30+ datasets:

  • Today: 1 root-catalog fetch + N parallel S3 leaf fetches → ~1–2 RTTs.
  • Per-id MCP calls (naive): N round-trips with full session/JSON-RPC/allowlist overhead. Scales poorly under concurrent app boots.
  • One batched MCP call: 1 RTT total. MCP serves cached entries from memory and parallelizes uncached S3 reads server-side.

Without batching, this is a perf downgrade.

Migration of layers-input.json

Most apps don't change. Top-level catalog + collections is exactly what the new path needs. Per-entry collection_url is deprecated but stays functional with a console warning. Per-entry catalog_token similarly stays.

Rollout

  1. Refactor lands with fallback active. All deployed apps continue working unchanged.
  2. mcp-data-server#117 + batch endpoint ship on their own timeline.
  3. Downstream apps opt in (Wyoming first) by removing collection_url overrides.
  4. After 1–2 release cycles, remove the fallback walk in a separate PR.

Test surface

  • Demo / @main-pinned apps boot with new path against public catalog (MCP cache hit for everything). No collection_url needed.
  • Wyoming (after migration) boots with s3://private-wyoming/... reached through mcp-data-server's authenticated S3 path.
  • Fallback: simulate MCP outage → verify layers still load via direct-fetch on apps with collection_url; verify clean failure for apps without.
  • Boot timing on a 30+ dataset app within 100ms of current.

Cross-repo sequence

mcp-data-server#117 → batch-endpoint issue (TBD) → mcp-credential-proxy extension → this issue → Wyoming layers-input.json simplification (separate PR in boettiger-lab/wyoming). Roll out via geo-agent-ops's normal pinned/unpinned validation order.

Collapses

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions