Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ Refactors, CI, and formatting land in the git history, not here.

## [Unreleased]

## [3.0.0b2] — 2026-07-14

Beta iteration: drops the local-download surface (a beta contract change) and fixes
`source=gcs` manifests.

### Removed

- **The local-download surface: `POST /v3/download` (REST) and the `download_cohort` MCP tool**
Expand Down Expand Up @@ -67,5 +72,6 @@ served by a different backend and v3 lives only under `/v3/*`.
(NCI security policy). Max-age is configurable via `IDC_API_HSTS_MAX_AGE` — default one year;
dev/test deploys use 3600.

[Unreleased]: https://github.com/ImagingDataCommons/IDC-REST-MCP/compare/v3.0.0b1...HEAD
[Unreleased]: https://github.com/ImagingDataCommons/IDC-REST-MCP/compare/v3.0.0b2...HEAD
[3.0.0b2]: https://github.com/ImagingDataCommons/IDC-REST-MCP/compare/v3.0.0b1...v3.0.0b2
[3.0.0b1]: https://github.com/ImagingDataCommons/IDC-REST-MCP/releases/tag/v3.0.0b1
11 changes: 5 additions & 6 deletions dev/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ version is always reported at `/v3/version`.
## Optional: remote MCP service (HTTP)

Deploy the same image with the MCP command to expose the tools over MCP streamable-http
(download is disabled in hosted mode — manifests/URLs only):
(retrieval is manifests/URLs only — the server never transfers files):

```bash
gcloud run deploy idc-mcp-v3 \
Expand Down Expand Up @@ -195,8 +195,7 @@ The MCP endpoint is then `https://<service-url>/mcp` (note the `/mcp` path).
> API** — no session affinity or single-instance pin needed. This is safe because the server
> exposes only client-initiated tools + static resources (no server→client sampling,
> elicitation, subscriptions, or streamed progress, which are the only things that would need a
> persistent session). The local **stdio** MCP remains the primary path for end users — it's the
> only mode that can download files to the user's machine.
> persistent session).

## Notes

Expand Down Expand Up @@ -552,12 +551,12 @@ curl -sI https://dev-api.canceridc.dev/v3/version | grep -i server # want: Goo
#### Remote MCP over the shared domain — verified behavior

`/mcp` is functional **end-to-end**, not just the handshake — verified on `dev-api.canceridc.dev`:
`initialize`, `tools/list` (19 tools), and `tools/call get_idc_version` (real result) all succeed.
`initialize`, `tools/list` (18 tools), and `tools/call get_idc_version` (real result) all succeed.
It runs **stateless** (`stateless_http=True`) — no `Mcp-Session-Id`, no session affinity, so it
autoscales like REST. Caveats:

- **Downloads are disabled** on the hosted endpoint by design (manifests/URLs only); only the local
**stdio** MCP transfers files.
- **Retrieval is manifests/URLs only** on every transport — the server never transfers files;
callers download directly from the public S3/GCS buckets (`idc` CLI / s5cmd).
- **Both `…/mcp` and `…/mcp/` are served directly** — no redirect either way. FastMCP registers one
exact-path route at `/mcp`, so out of the box Starlette 307s `/mcp/` onto it; `http_app()` in
[mcp/server.py](../src/idc_api/mcp/server.py) registers the trailing-slash form as a real route
Expand Down
8 changes: 4 additions & 4 deletions dev/developer_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ upgrade).

```bash
uv run idc-api # REST API → http://127.0.0.1:8000 (Swagger at /v3/docs)
uv run idc-mcp # MCP server over stdio (local; download enabled)
uv run idc-mcp --http --port 8080 # MCP over streamable-http (hosted; download disabled)
uv run idc-mcp # MCP server over stdio (local)
uv run idc-mcp --http --port 8080 # MCP over streamable-http (hosted)
```

Inspect the MCP tools interactively:
Expand All @@ -50,7 +50,7 @@ uv run --directory . pytest tests/test_backend_guards.py -q # one file
|---|---|
| [test_backend_guards.py](../tests/test_backend_guards.py) | SQL sandbox: read-only, external access blocked, single-statement, row cap, timeout |
| [test_services_golden.py](../tests/test_services_golden.py) | **Golden:** results equal `idc-index` (IDCClient) on the same Parquet |
| [test_rest.py](../tests/test_rest.py) | REST endpoint shapes, 404/501 mapping, SQL guard, OpenAPI |
| [test_rest.py](../tests/test_rest.py) | REST endpoint shapes, error mapping, SQL guard, OpenAPI |
| [test_mcp.py](../tests/test_mcp.py) | Tools registered, prescriptive descriptions, calls, clean errors, resources |
| [test_parity.py](../tests/test_parity.py) | **Parity:** core service == REST == MCP for the same filter |
| [test_specialized_indices.py](../tests/test_specialized_indices.py) | Specialized indices fetched + exposed to SQL + joinable to `index` |
Expand Down Expand Up @@ -91,7 +91,7 @@ src/idc_api/
backend/
base.py # QueryBackend interface
duckdb_backend.py # read-only DuckDB over idc-index Parquet
services/ # discovery, cohort, query, clinical, manifest, viewer, citations, licenses, download
services/ # discovery, cohort, query, clinical, manifest, viewer, citations, licenses
rest/app.py # FastAPI app + routes
mcp/server.py # FastMCP tools + resources + entrypoint
tests/ # pytest suite
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "idc-api"
version = "3.0.0b1"
version = "3.0.0b2"
description = "LLM-first REST API and MCP server for NCI Imaging Data Commons, backed by idc-index Parquet + DuckDB."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
3 changes: 2 additions & 1 deletion src/idc_api/core/services/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ def download_info(
manifest_truncated=truncated,
note=(
"URLs point to public AWS S3 (and GCS) buckets; no credentials needed. Easiest: "
"the `idc` CLI commands above (also handles either cloud). Driving it yourself: "
"the `idc` CLI commands above (`pip install idc-index`; handles either cloud). "
"Driving it yourself: "
"`s5cmd --no-sign-request` against these s3:// URLs for AWS; for GCS, request "
"source=gcs from the get_cohort_urls tool (MCP) or POST /v3/cohort/manifest.txt "
"(REST) — still s3:// (GCS's S3-compatible endpoint) — and add `--endpoint-url "
Expand Down
7 changes: 4 additions & 3 deletions src/idc_api/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ def get_cohort_urls(
"returned": len(urls),
"truncated": truncated,
"urls": urls,
"note": "Anonymous public URLs; append '/*' is a series prefix wildcard for s5cmd.",
"note": "Anonymous public URLs; append '/*' as a series prefix wildcard for s5cmd.",
}


Expand Down Expand Up @@ -506,8 +506,9 @@ def get_licenses(terms: dict | None = None, ranges: dict | None = None) -> dict:
'unlimited' value — for bulk *series* use the cohort/manifest tools, not raw `run_sql` rows.
4. *Get the data:* `get_cohort_urls` (public s3:// URLs; `source=gcs` reaches GCS via
its S3-compatible endpoint, still s3://) or the ready-to-run
`idc` CLI commands already in the `build_cohort` response — both transfer directly from
S3/GCS with no server involved, and work the same whether this server is hosted or local.
`idc` CLI commands (`pip install idc-index`) already in the `build_cohort` response — both
transfer directly from S3/GCS with no server involved, and work the same whether this
server is hosted or local.
This server never moves files itself; hand the user the commands/URLs to run.
5. *Be a good citizen:* check `get_licenses` (CC BY vs CC BY-NC) and, when publishing, include
`get_citations` output — both the per-dataset `citations` and `idc_acknowledgment` (the IDC
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.