Skip to main content
Glama
cyanheads

@cyanheads/met-museum-mcp-server

by cyanheads

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://met-museum.caseyjhand.com/mcp


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

Tool

Description

met_list_departments

Return all 19 curatorial departments with their numeric IDs and display names

met_search_collections

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

met_list_objects

List every object ID in a department, every object created or revised since a date, or both, without a keyword

met_get_object

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 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:

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

git clone https://github.com/cyanheads/met-museum-mcp-server.git
  1. Navigate into the directory:

cd met-museum-mcp-server
  1. Install dependencies:

bun install
  1. Configure 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

MCP_TRANSPORT_TYPE

Transport: stdio or http

stdio

MCP_HTTP_PORT

HTTP server port

3010

MCP_SESSION_MODE

HTTP session mode: auto, stateful, or stateless

stateless

MCP_AUTH_MODE

Authentication: none, jwt, or oauth

none

MCP_LOG_LEVEL

Log level (debug, info, warning, error)

info

LOGS_DIR

Directory for log files (Node.js only)

<project-root>/logs

LOG_TOOL_FAILURE_PAYLOADS

Log 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_ENABLED

Enable OpenTelemetry instrumentation

false

MET_BASE_URL

Met Collection API root; override for local stubs

https://collectionapi.metmuseum.org/public/collection

MET_REQUEST_TIMEOUT_MS

Per-request HTTP timeout in milliseconds

10000

MET_CALL_DEADLINE_MS

Time budget in milliseconds for one tool call, retries included; exceeding it fails with retry_deadline_exceeded

30000

MET_BATCH_CONCURRENCY

Max parallel fetches in met_get_object

5

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:http
  • Run 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-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

Directory

Purpose

src/index.ts

createApp() entry point — registers tools and inits the Met service.

src/config

Server-specific environment variable parsing and validation with Zod.

src/mcp-server/tools

Tool definitions (*.tool.ts) — met_list_departments, met_search_collections, met_list_objects, met_get_object.

src/services/met

Met 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:

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.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    A 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.
    4
    243 npm
    35
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Federated, 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.
    5
    53 npm
    14
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Search artists, releases, recordings, works, and labels; traverse relationships; resolve ISRC/ISWC/barcode; fetch cover art via MCP. STDIO or Streamable HTTP.
    95 npm
    1
    Apache 2.0
  • A
    license
    A
    quality
    F
    maintenance
    MCP server for searching museum collections and viewing artwork images and metadata from multiple museums, including the Met, Art Institute of Chicago, Rijksmuseum, and more.
    4
    4 npm
    2
    MIT