@cyanheads/met-museum-mcp-server
Enriches artwork records retrieved from the Met Museum collection with Wikidata entity URLs for artists, tags, and works, letting agents follow linked-data references from objects to their Wikidata counterparts.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/met-museum-mcp-serverSearch for Monet paintings with open access images."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Public Hosted Server: https://met-museum.caseyjhand.com/mcp
Overview
The Metropolitan Museum of Art's public Collection API. Search the collection by keyword and filters, or browse it by department and update date, then fetch full object records — metadata, provenance, and CC0 open-access images — from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
Tool | Description |
| Return all 19 curatorial departments with their numeric IDs and display names |
| Search the collection by keyword, optionally matched in titles or tags only, with filters for department, date range, medium, geography, image availability, on-view status, and highlight designation |
| List every object ID in a department, every object created or revised since a date, or both, without a keyword |
| Fetch full records for one or more object IDs — metadata, provenance, artist info, CC0 image URLs, tags, and Wikidata links — optionally with up to 3 CC0 images as image content |
Related MCP server: open-museum-mcp
Capability reference
met_list_departments tool
No input; returns all 19 curatorial departments, each a numeric
departmentIdwith itsdisplayName(e.g., "Egyptian Art")departmentIdvalues are the valid input for thedepartmentIdfilter onmet_search_collectionsandmet_list_objectsTyped errors:
upstream_blocked,upstream_unavailable(the Met answered 500/502/503/504 through every retry), andretry_deadline_exceeded(the call's time budget ran out)
met_search_collections tool
Keyword
q(required), matched across all text fields unlessmatchFieldnarrows it to"title"or"tags"."*"matches every object, for a search by filters alone; an accession number from a label ranks its object first. Plus filters:departmentId,medium(a case-sensitive classification as the Met spells it —"Paintings", not"Oil on canvas"),dateBegin/dateEnd(integer years, negative = BCE, set together), onegeoLocation,hasImages,isOnView, andisHighlight(trueonly)departmentId7 (The Cloisters) and 17 (Medieval Art) share one search result set at the Met: each page keeps only the requested department's IDs, whiletotaland paging count both, so a page can hold fewer thanlimitIDs; anoticesays so, andmet_list_objectsgives exact department membershipUp to 500 IDs per page (default 20), paged by
offset/nextOffsetthrough the first 10,000 matches only;totalstill reports the full count, with anoticewhen it exceeds 10,000Zero matches is a result, not an error:
total: 0with anoticesaying whether the keyword matches nothing or the filters removed every match, and which filters to correct or drop. Every result echoes the applied query aseffectiveQueryTyped errors:
invalid_date_range,invalid_filter(a blankq,medium, orgeoLocation),invalid_department,upstream_blocked,upstream_unavailable,retry_deadline_exceeded
met_list_objects tool
Filters
departmentId(frommet_list_departments) andupdatedSince(YYYY-MM-DD— records created or revised on or after that day), alone or together; with neither, the whole collection (over 500,000 IDs). Up to 500 IDs per page (default 20), in ascending order, paged byoffset/nextOffsetwith no depth limitTyped errors:
invalid_department,invalid_date(an impossible date such as2026-02-30),upstream_blocked,upstream_unavailable,retry_deadline_exceeded; an empty list is a result, not an error, with anoticenaming the filters, and every result echoes the applied filters aseffectiveQueryEach list is cached for up to an hour, so a record revised within the last hour may not appear yet
met_get_object tool
1–20 IDs per call, from
met_search_collectionsormet_list_objects; a repeated ID is fetched and returned oncePartial success: per-ID errors land in
failed[], and the call fails only when every ID does —all_not_foundwhen every ID was a 404,upstream_blockedwhen the Met's firewall refused a request,retry_deadline_exceededwhen every fetch ran out of the call's time budget,upstream_unavailablewhen a fetch met a Met API outage, andall_failedotherwise; records past a 60,000-bytestructuredContentbudget are listed indeferred[]with their sizes, to re-requestisPublicDomain/hasCC0Imagegate image URLs — non-public-domain objects return emptyprimaryImage,primaryImageSmall, andadditionalImages; sparse fields are empty or null, never fabricatedincludeImages: trueattaches the CC0 web-display image (about 600 px on the long edge) of the first 3 returned records that have one, as image content after a caption naming the object, andimages[]gives each returned record's outcome (attached,no_cc0_image,over_cap,unavailable). The bytes ridecontent[]only, so a client that passes the model onlystructuredContentwon't show themThe Artist line carries the full attribution and role —
artistPrefix("Style of"), the name,artistSuffix, andartistRole("Patron") — so a follower's work or a patron never reads as the named artist's own;rightsAndReproductionnames the rights holder on the copyrighted works that carry one, besideaccessionYearmetadataDateis the record's last revision, the timestampmet_list_objectsupdatedSincecompares;departmentIdresolves the record's department name (five differ frommet_list_departments) to the IDmet_list_objectsandmet_search_collectionstake, ornullwhen the name is not recognizedThe search index can list IDs the object endpoint no longer serves, so a 404's guidance is to drop the ID rather than search for it again
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Met Museum-specific:
500K+ artworks spanning 5,000 years from the Met's public collection API
CC0 open-access data from The Metropolitan Museum of Art — free to use without permission or attribution
Parallel batch fetching with configurable concurrency for
met_get_objectLinked data on every object — Getty ULAN and AAT URLs, Wikidata entity URLs for artists, tags, and works
A 403 from the Met's firewall surfaces on every tool as
upstream_blocked, non-retryable: the block covers the server's address for minutes, so the recovery is to wait and send fewer requestsA 500, 502, 503, or 504 that outlasts the retries surfaces on every tool as
upstream_unavailable; once every fetch in flight has failed without the Met answering any ID,met_get_objectstops requesting the rest of the batch, so a 5xx outage costs at mostMET_BATCH_CONCURRENCY× 4 requests (20 at the default) however many IDs were asked forNo error carries the Met's HTML or JSON error page — an HTTP failure reaches the caller as its code, message, and status, or, inside a
met_get_objectbatch, as that ID'sfailed[]message
Agent-friendly output:
Provenance on every record —
isPublicDomainandhasCC0Imageflags distinguish CC0 objects from works with inaccessible images, so agents can reason about what they can actually displayPartial failure reporting —
met_get_objectreturnsobjectsandfailedarrays so callers receive successful records alongside structured per-ID error contextTruncation signaling —
met_search_collectionsandmet_list_objectsreturntotal,returned,truncated,remaining,nextOffset, and the resolvedoffset; the text marks each page(truncated),(complete), or(offset beyond result set), and search adds(window end)for a page that stops at its 10,000-match window short oftotalByte-budget disclosure —
met_get_objectreportsdeferred[]records with their sizes when a batch exceeds its serialized-response budget, so callers can size a follow-up call precisely
Getting started
Public Hosted Instance
A public instance is available at https://met-museum.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"met-museum-mcp-server": {
"type": "streamable-http",
"url": "https://met-museum.caseyjhand.com/mcp"
}
}
}Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"met-museum-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/met-museum-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"met-museum-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/met-museum-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with Docker:
{
"mcpServers": {
"met-museum-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/met-museum-mcp-server:latest"
]
}
}
}For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
No API key required — the Met Collection API is public and unauthenticated.
Installation
Clone the repository:
git clone https://github.com/cyanheads/met-museum-mcp-server.gitNavigate into the directory:
cd met-museum-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env as needed (all vars are optional)Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
Variable | Description | Default |
| Transport: |
|
| HTTP server port |
|
| HTTP session mode: |
|
| Authentication: |
|
| Log level ( |
|
| Directory for log files (Node.js only) |
|
| Log each failed tool call's arguments and result, redacted by key name and capped at |
|
| Enable OpenTelemetry instrumentation |
|
| Met Collection API root; each endpoint appends its own version ( |
|
| Per-request HTTP timeout in milliseconds |
|
| Wall-clock budget in milliseconds for one tool call, shared by every Met API request it makes, retries and backoff included. A call that runs out fails with |
|
| Max parallel fetches in |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:httpRun checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t met-museum-mcp-server .
docker run --rm -p 3010:3010 met-museum-mcp-serverThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/met-museum-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Met Collection API client — HTTP, call deadline, response normalization, cached object-ID lists. |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools via the arrays in
createApp()insrc/index.tsWrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testData attribution
Data from The Metropolitan Museum of Art Collection API (CC0).
License
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Art MCP — Metropolitan Museum of Art Collection API (free, no auth)
Unlock a world of art with the Met Museum MCP! Use the 'search_artworks' API to find stunning
Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.
Art Institute of Chicago MCP — wraps the ARTIC public API (free, no auth)
Related MCP Servers
- AlicenseAqualityBmaintenanceA MCP Server that lets user ask AI models to discover the collection of the Metropolitan Museum of Art. Adds the discovered art works as Resources on the server.4268 npm35MIT
- AlicenseAqualityAmaintenanceFederated, license-verified search across open-access museum collections — currently The Met, Cleveland, AIC, Wikimedia Commons, and Europeana, with more being added. Strict-default-deny rights gate accepts only CC0 / Public Domain Mark, returning reuse-safe artwork with citations in three styles.5140 npm13MIT
- AlicenseNot gradedqualityAmaintenanceSearch artists, releases, recordings, works, and labels; traverse relationships; resolve ISRC/ISWC/barcode; fetch cover art via MCP. STDIO or Streamable HTTP.120 npm1Apache 2.0
- AlicenseAqualityFmaintenanceMCP server for searching museum collections and viewing artwork images and metadata from multiple museums, including the Met, Art Institute of Chicago, Rijksmuseum, and more.2410 npm2MIT