MCP server: One MCP server.

met-museum-mcp-server

@cyanheadsMCP server · External

Search the Met collection, browse by department or update date, fetch full records and CC0 images.

Art & culturev0.9.0Apache-2.0TypeScript
Freeno charge per call

One address for any MCP client. No sign-in needed on this server.

What it does

4 read

Grouped by tool name. At most: only reads or looks up information.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

Tools

4 · grouped by tool name · checked 20 Oct 2026

Read · 4Look things up

  • met_list_departments
  • met_search_collections
  • met_list_objects
  • met_get_object
Try it in the Inspector
README8 min read

@cyanheads/met-museum-mcp-server

Search the Metropolitan Museum of Art collection, browse it by department or update date, fetch full artwork records and open-access images via MCP. STDIO or Streamable HTTP.

4 Tools

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


Overview

The Metropolitan Museum of Art's collection of 500,000+ works spanning 5,000 years. Search by keyword and filters, browse by department, and fetch full object records with provenance and CC0 open-access images.

Tools

ToolDescription
met_list_departmentsReturn all 19 curatorial departments with their numeric IDs and display names
met_search_collectionsSearch 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
met_list_objectsList every object ID in a department, every object created or revised since a date, or both, without a keyword
met_get_objectFetch 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

Capability reference

met_list_departments tool

  • No input; returns all 19 curatorial departments, each a numeric departmentId with its displayName (e.g., "Egyptian Art")
  • departmentId values are the valid input for the departmentId filter on met_search_collections and met_list_objects
  • Typed errors: upstream_blocked, upstream_unavailable (the Met answered 500/502/503/504 through every retry), upstream_rate_limited (the Met answered 429), and retry_deadline_exceeded (the call's time budget ran out)

met_search_collections tool

  • Keyword q (required), matched across all text fields unless matchField narrows it to "title" or "tags"; "*" matches everything, for a search by filters alone. Filters: departmentId, medium (case-sensitive, e.g. "Paintings"), dateBegin/dateEnd (integer years, negative = BCE, set together), one geoLocation, hasImages, isOnView, and isHighlight (true only)
  • Up to 500 IDs per page (default 20), paged by offset / nextOffset through the first 10,000 matches; total reports the full count. departmentId 7 (The Cloisters) and 17 (Medieval Art) share one result set at the Met, so a page can hold fewer than limit IDs, which a notice flags
  • Zero matches is a result with a notice, not an error; every result echoes the applied query as effectiveQuery. Typed errors: invalid_date_range, invalid_filter (a blank q, medium, or geoLocation), invalid_department, upstream_blocked, upstream_unavailable, upstream_rate_limited, retry_deadline_exceeded

met_list_objects tool

  • Filters departmentId (from met_list_departments) and updatedSince (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 by offset / nextOffset with no depth limit
  • An empty list is a result with a notice, not an error; every result echoes the applied filters as effectiveQuery. Typed errors: invalid_department, invalid_date (an impossible date such as 2026-02-30), upstream_blocked, upstream_unavailable, upstream_rate_limited, retry_deadline_exceeded
  • Lists are cached for up to an hour, so a record revised in the last hour may not appear yet

met_get_object tool

  • Fetches 20 distinct IDs per call, from met_search_collections or met_list_objects; a list of up to 500 is accepted, and the IDs past the first 20 come back in remainingIDs. The index can list IDs the Met no longer serves, so drop an ID that 404s
  • Partial success: per-ID errors land in failed[], and the call fails only when every ID does (all_not_found, upstream_blocked, upstream_rate_limited, retry_deadline_exceeded, upstream_unavailable, or all_failed). Records past a 60,000-byte structuredContent budget are listed in deferred[] with their sizes, to re-request
  • Non-public-domain objects (isPublicDomain / hasCC0Image) return empty primaryImage, primaryImageSmall, and additionalImages. includeImages: true attaches the CC0 image (about 600 px) of the first 3 records that have one as image content in content[] only, with each record's outcome in images[]

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_object
  • Linked data on every object — Getty ULAN and AAT URLs, Wikidata entity URLs for artists, tags, and works
  • Met API failures surface on every tool as typed errors (upstream_blocked for a 403 firewall block, upstream_unavailable for 5xx, upstream_rate_limited for 429), never as the Met's raw error page
  • met_get_object stops requesting the rest of a batch once the Met is blocking, down, or throttling, so a bad batch costs only a few requests

Agent-friendly output:

  • Provenance on every record: isPublicDomain and hasCC0Image flags show which images can be displayed
  • Partial failure reporting: met_get_object returns objects and failed arrays, so successes arrive alongside per-ID errors
  • Truncation signaling: met_search_collections and met_list_objects return total, returned, truncated, remaining, nextOffset, and the resolved offset
  • Byte-budget disclosure: met_get_object lists deferred[] records with their sizes when a batch is too large

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:

json
{
  "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.

json
{
  "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):

json
{
  "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:

json
{
  "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:

sh
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

  • Bun v1.4.0 or higher (or Node.js v24+).
  • No API key required — the Met Collection API is public and unauthenticated.

Installation

  1. Clone the repository:
sh
git clone https://github.com/cyanheads/met-museum-mcp-server.git
  1. Navigate into the directory:
sh
cd met-museum-mcp-server
  1. Install dependencies:
sh
bun install
  1. Configure environment:
sh
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.

VariableDescriptionDefault
MCP_TRANSPORT_TYPETransport: stdio or httpstdio
MCP_HTTP_PORTHTTP server port3010
MCP_SESSION_MODEHTTP session mode: auto, stateful, or statelessstateless
MCP_AUTH_MODEAuthentication: none, jwt, or oauthnone
MCP_LOG_LEVELLog level (debug, info, warning, error)info
LOGS_DIRDirectory for log files (Node.js only)<project-root>/logs
LOG_TOOL_FAILURE_PAYLOADSLog each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted.false
OTEL_ENABLEDEnable OpenTelemetry instrumentationfalse
MET_BASE_URLMet Collection API root; override for local stubshttps://collectionapi.metmuseum.org/public/collection
MET_REQUEST_TIMEOUT_MSPer-request HTTP timeout in milliseconds10000
MET_CALL_DEADLINE_MSTime budget in milliseconds for one tool call, retries included; exceeding it fails with retry_deadline_exceeded30000
MET_BATCH_CONCURRENCYMax parallel fetches in met_get_object5

See .env.example for the full list of optional overrides.

Running the server

Local development

  • Build and run:

    sh
    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Run checks and tests:

    sh
    bun run devcheck   # Lint, format, typecheck, security
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Docker

sh
docker build -t met-museum-mcp-server .
docker run --rm -p 3010:3010 met-museum-mcp-server

The 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

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools and inits the Met service.
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/toolsTool definitions (*.tool.ts) — met_list_departments, met_search_collections, met_list_objects, met_get_object.
src/services/metMet Collection API client — HTTP, call deadline, response normalization, cached object-ID lists.
tests/Unit and integration tests mirroring src/.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
  • Register new tools via the arrays in createApp() in src/index.ts
  • Wrap 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:

sh
bun run devcheck
bun run test

Data attribution

Data from The Metropolitan Museum of Art Collection API (CC0).

License

Apache-2.0 — see LICENSE for details.

Similar modules

Browse Art & culture