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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions