whitney-mcp-ts
An MCP server for the Whitney Museum's public API. Search the collection, artists, exhibitions, events and audio guides from Claude Desktop or any MCP client.
TypeScript, stdio only, friendly parameters mapped onto the API's Ransack syntax, travels with attribution, and responses trimmed to keep them manageable in a model's context.
The Whitney's API is open and unauthenticated; no API key needed.
Install
npx whitney-mcp-ts
Or from source:
git clone https://github.com/mildlydiverting/whitney-mcp-ts.git
cd whitney-mcp-ts
npm install
npm run build
Node 20 or newer.
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"whitney": {
"command": "npx",
"args": ["-y", "whitney-mcp-ts"]
}
}
}
From source, point at the build instead:
{
"mcpServers": {
"whitney": {
"command": "node",
"args": ["/absolute/path/to/whitney-mcp-ts/dist/index.js"]
}
}
}
Quit and reopen Claude Desktop. The config doesn't expand ~, so use a full path.
Tools
What to expect
Search returns slim records.
Whitney records carry long HTML descriptions and biographies, so search gives you the basics and whitney_get_* gives you everything. Prose is truncated at 2,000 characters, whole responses at 25,000.
Pagination is 30 per page.
That's the API's page size. page picks the page, limit trims it further — the default of 10 stops a broad search flooding your context.
Search syntax is Ransack
Predicates nest under q (_eq, _cont, _cont_all_split, _gteq and friends), sorting under q[s]. The typed tools map friendly arguments onto these. whitney_query exposes them raw, for anything the typed tools don't reach.
Artist and artwork fields are mapped explicitly.
Exhibitions, events, guides and pages go through a generic summariser instead: HTML stripped, timestamps shortened to dates, relative URLs made absolute, internal foreign keys dropped. The Museum says its field set may change, so nothing is hard-coded that doesn't need to be. Run a search with limit: 1 to see the real field names, then use filters for anything specific.
Attribution
Every response ends with a source line naming the Museum and linking their terms. Detail records also carry two attribution fields:
rights— the copyright notice, extracted from the Whitney's own description textcitation— a ready-made markdown line, eg:
Edward Hopper, Early Sunday Morning, 1930. Whitney Museum of American Art, © Heirs of Josephine N. Hopper/Licensed by Artists Rights Society (ARS), New York.
Note the rightsholder is usually not the artist. In a sample of ten works,
nine differed — estates, foundations, and rights societies like ARS and VAGA.
So rights is worth reading rather than assuming; it's the notice the
Whitney's terms ask you to keep.
Works with no copyright notice — older material, mostly — get a citation ending after the Museum link.
Bulk Data
For anything at scale, don't page through the API. The Whitney publishes artist and artwork CSVs at https://github.com/whitneymuseum/open-access, released under CC0.
Known API quirks
sort: "random"doesn't reliably randomise. Asking for three random works returned three consecutive accession numbers by the same artist. If you need genuine randomness, request a random page number instead.display_dateon artworks is free text — "1915–1931, printed 1976–1977" — so date ranges don't work there. Exhibitions and events have real timestamps, andstarts_on_or_after/starts_on_or_beforefilter on those.- Living artists have
death_year: "0". Don't do arithmetic on it. - Ransack ignores predicates it doesn't recognise rather than erroring, so a misspelled field silently returns everything. Check your result counts.
outputSchemawas dropped from ten of the eleven tools (all butwhitney_query). The MCP SDK (v1.30.0) converts a Zod v3 output shape to JSON Schema via a vendoredzod-to-json-schemathat always stamps$schema: draft-07, which some MCP clients reject outright ("unsupported dialect" — they validateoutputSchemaagainst 2020-12 only). Structured content is still returned in every response; it's just no longer pre-advertised in the tool list.
Security
The Whitney's API needs no key, so there's no credentials, nothing to leak and nothing to configure.
Every tool is read-only. The server makes GET requests to whitney.org and nothing else — no filesystem access, no shell, no writes. Endpoint names come from a fixed list, record IDs are URL-encoded, and filter parameters go through URLSearchParams.
One known limitation: the HTML-to-markdown conversion uses regular expressions with nested quantifiers, which could backtrack badly on deliberately malformed input. The input comes from the Whitney's own CMS rather than from users, so if this happens, something has gone very wrong in NYC and it's probably not my fault.
If you find something, open an issue.
Development
npm test # unit tests, offline
npm run test:canary # live checks against the Whitney API
npm run inspect # MCP Inspector against the built server
The canary suite runs monthly in CI. It exists to catch the Whitney changing a field name — the failure mode that unit tests can't see, since they run against saved fixtures.
Licences
Code: MIT License.
The Whitney provides the API with the following note:
The data accessed through this API may be protected by copyright, and other restrictions, of the Whitney Museum of American Art and third parties. You may use this data for noncommercial educational and personal use and for "fair use" as authorized under law, provided that you also retain all copyright and other proprietary notices contained on the materials and cite the author and source of the materials.
Read their terms at https://whitney.org/about/website/api before you build anything public on it.
Note the CSV datasets linked above are CC0, which may be more permissive than the API terms.
Not affiliated with the Whitney Museum of American Art. This is a third-party wrapper around their public API, which is documented at https://whitney.org/about/website/api.
Thanks
See also Sam Parsons' whitney-museum-mcp — a Python/FastMCP server over the same API, with Docker and HTTP transport.
To the Whitney's digital team for publishing an API at all, and for documenting it properly. And for writing nice blog posts that explicitly say they use claude code, because that makes me feel less guilty for vibe coding this whole shebang.