Skip to main content
Glama
molanojustin

Smithsonian Open Access MCP Server

by molanojustin

Smithsonian Open Access MCP Server

npm version NPM Downloads Docker

A Model Context Protocol (MCP) server for the Smithsonian Institution's Open Access collections. It lets AI assistants such as Claude Desktop search more than 14 million object records and 2.8 million archive records from Smithsonian museums, libraries, archives and research centers, find out what is on display now, and fetch full object records with images and links to the museum websites.

Ask, for example:

Which Muppets are on display right now at the National Museum of American History?

The assistant calls search_objects(query="muppet", museum="American History", on_view=true) and finds the objects currently on view, such as:

Objects

Exhibition

Elmo, Fozzie Bear, Oscar the Grouch and Rosita puppets

Entertainment Nation

Oscar the Grouch's trash can and Mr. Hooper's costume from Sesame Street

Entertainment Nation

The Muppets lunch box (1979)

Taking America To Lunch

Version 2.0 replaces the 28 tools of version 1.x with 5. See Migrating from 1.x and the changelog.

Contents

Related MCP server: Smithsonian MCP Server

Quick Start

You need:

  • A free API key from api.data.gov/signup

  • uv. uv downloads a compatible Python (3.10 or newer) if one is not already installed.

The server speaks MCP over stdio by default. MCP clients such as Claude Desktop start it on demand; you do not run it in the background yourself. For clients that connect over HTTP, it can also run as a long-lived server; see HTTP transport.

Add this to claude_desktop_config.json. It installs and runs the server straight from the GitHub repository, with no clone or virtual environment to manage:

{
  "mcpServers": {
    "smithsonian_open_access": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/molanojustin/smithsonian-mcp",
        "smithsonian-mcp"
      ],
      "env": {
        "SMITHSONIAN_API_KEY": "your_key_here"
      }
    }
  }
}

Restart Claude Desktop, then ask "What Smithsonian museums are available?"

Notes:

  • The package is not published on PyPI, so --from points uvx at the GitHub repository. Append @<tag or commit> to the URL to pin a version.

  • uvx caches the build. To pick up newer commits, run uvx --refresh --from git+https://github.com/molanojustin/smithsonian-mcp smithsonian-mcp once in a terminal.

  • If Claude Desktop reports that uvx cannot be found, use its absolute path as the command (which uvx on macOS/Linux, where uvx on Windows).

Other ways to run the server

All of these start the same smithsonian-mcp command and take the API key from the same env block.

npm/npx

The npm package is a small Node.js wrapper that uses uv to install the Python dependencies on first start. It requires Node.js 16 or newer and uv:

{
  "mcpServers": {
    "smithsonian_open_access": {
      "command": "npx",
      "args": ["-y", "@molanojustin/smithsonian-mcp"],
      "env": {
        "SMITHSONIAN_API_KEY": "your_key_here"
      }
    }
  }
}

You can also install it globally with npm install -g @molanojustin/smithsonian-mcp and run smithsonian-mcp. Run smithsonian-mcp --test to check your API key and connection.

The wrapper keeps the Python environment in a per-user cache directory, one per package version: ~/Library/Caches/smithsonian-mcp on macOS, ~/.cache/smithsonian-mcp (or $XDG_CACHE_HOME) on Linux, and %LOCALAPPDATA%\smithsonian-mcp on Windows. Set UV_PROJECT_ENVIRONMENT to use a different location. Older versions' environments there can be deleted safely.

From a local clone

git clone https://github.com/molanojustin/smithsonian-mcp.git
cd smithsonian-mcp
uv sync

uv sync creates .venv from uv.lock and installs the smithsonian-mcp command into it. Point Claude Desktop at the clone:

{
  "mcpServers": {
    "smithsonian_open_access": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/smithsonian-mcp", "run", "smithsonian-mcp", "--transport", "stdio"],
      "env": {
        "SMITHSONIAN_API_KEY": "your_key_here"
      }
    }
  }
}

Alternatively, use the installed command directly as "command": "/absolute/path/to/smithsonian-mcp/.venv/bin/smithsonian-mcp" (on Windows, .venv\Scripts\smithsonian-mcp.exe) with "args": ["--transport", "stdio"].

A server run from the clone also reads the clone's .env. --transport stdio keeps it in stdio mode even if that file sets MCP_TRANSPORT=http for HTTP mode.

Python virtual environment without uv

Requires Python 3.10 or newer:

git clone https://github.com/molanojustin/smithsonian-mcp.git
cd smithsonian-mcp
python3 -m venv .venv
.venv/bin/pip install -e .

Then use /absolute/path/to/smithsonian-mcp/.venv/bin/smithsonian-mcp as the command, with "args": ["--transport", "stdio"] as above. This installs the newest compatible dependencies rather than the versions pinned in uv.lock.

Docker

Build the image from a clone. The -i flag keeps stdin open for the stdio transport, and -e SMITHSONIAN_API_KEY passes the key from the env block into the container:

docker build -t smithsonian-mcp .
{
  "mcpServers": {
    "smithsonian_open_access": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "SMITHSONIAN_API_KEY", "smithsonian-mcp"],
      "env": {
        "SMITHSONIAN_API_KEY": "your_key_here"
      }
    }
  }
}

To run the container as an HTTP server instead, set MCP_TRANSPORT=http and publish port 8000 on this machine only:

docker run --rm -e SMITHSONIAN_API_KEY -e MCP_TRANSPORT=http -p 127.0.0.1:8000:8000 smithsonian-mcp

The HTTP endpoint has no authentication: anyone who can reach the port can call the tools and spends your API key's quota. -p 127.0.0.1:8000:8000 keeps the port on this machine; -p 8000:8000 would publish it on every interface of the host, and on Linux Docker's published ports bypass firewalls such as ufw. The image sets MCP_HOST=0.0.0.0 so that the published port reaches the server, and MCP_ALLOWED_HOSTS=localhost,127.0.0.1,::1, so requests naming any other host are refused. To reach the container by another name, add it with -e MCP_ALLOWED_HOSTS=localhost,127.0.0.1,::1,mcp.example.org and control access in front of it. See HTTP transport.

Automated Setup Scripts

For a local clone, the setup scripts install dependencies (with uv sync when uv is available, otherwise a Python 3.10+ virtual environment and pip), validate your API key and save it to .env, and can optionally add the server to your Claude Desktop config, generate an mcpo config, install a background service that serves HTTP (see Service Management) and run a health check.

On macOS or Linux:

chmod +x config/setup.sh
config/setup.sh

On Windows:

config\setup.ps1

API Key in .env

When you run the server from a clone, it also reads SMITHSONIAN_API_KEY from a .env file in the project root. Copy .env.example to .env and set your key. A key set in the MCP client's env block takes precedence.

Verify Setup

Check an installation from a clone:

uv run python examples/test-api-connection.py
uv run python scripts/verify-setup.py

HTTP transport

stdio is the default and the right choice when an MCP client starts the server itself. For clients that connect to a running server over HTTP, start it with the streamable HTTP transport:

smithsonian-mcp --transport http

It serves MCP at http://127.0.0.1:8000/mcp until you stop it with Ctrl+C (or SIGTERM). The same flags work with uvx --from git+https://github.com/molanojustin/smithsonian-mcp smithsonian-mcp, uv run smithsonian-mcp and npx -y @molanojustin/smithsonian-mcp.

Option

Environment variable

Default

Purpose

--transport

MCP_TRANSPORT

stdio

stdio or http.

--host

MCP_HOST

127.0.0.1

Address to listen on in HTTP mode.

--port

MCP_PORT

8000

Port to listen on in HTTP mode.

--allowed-hosts

MCP_ALLOWED_HOSTS

localhost,127.0.0.1,::1 and the --host address

Comma-separated Host header names that HTTP mode accepts.

Options on the command line take precedence over the environment variables, which can also be set in .env. A blank environment variable counts as unset; a blank --host is an error. Configurations in which an MCP client starts the server from a clone pass --transport stdio, so MCP_TRANSPORT=http in the clone's .env does not affect them. mcpo also defaults to port 8000, so pick another port with --port if you run both.

Notes:

  • The HTTP endpoint has no authentication: anyone who can reach the port can call the tools and spends your API key's quota. Keep the default 127.0.0.1 unless you put the server behind something that controls access.

  • Requests whose Host header is not an allowed name (or the address the connection arrived on) get HTTP 421, and requests whose Origin header names another site get 403. This blocks DNS rebinding from web pages, whatever address the server listens on. With --host 0.0.0.0, the allowed names are only localhost, 127.0.0.1 and ::1; add the names that clients use, such as --allowed-hosts localhost,127.0.0.1,mcp.example.org. Clients that connect by IP address need nothing extra.

  • The server is stateless: each request is handled on its own, so it keeps no MCP sessions and its memory does not grow with the number of clients. The tools only answer requests, so nothing needs a session.

  • Logs go to stderr in both modes, including the access log of each HTTP request, and the API key is still sent only in the X-Api-Key header to the Smithsonian API.

  • In Docker, set -e MCP_TRANSPORT=http and publish the port, as shown under Docker.

Tools

All five tools are read-only.

Tool

Use it to

search_objects

Find objects, or archive records, by keyword and filters, including what is on view now

get_object

Get the full record, images and web page of one object

list_museums

See which museums contribute, with their codes, record types and accepted names

explore_topic

Browse a varied sample of a topic across museums

get_collection_stats

Count what search can return, for the whole collection or one museum

A typical session calls search_objects, then get_object for the objects worth a closer look. Results leave out empty fields rather than listing them as null. Problems you can fix, such as an unknown museum name, a year outside 1000 to 2999 or an object id that does not exist, come back as an error message that says what to change.

search_objects

Search the collections, with optional filters.

Parameter

Type

Default

Description

query

string

""

Keywords, matched anywhere in a record, descriptions and notes included. Every word must match. AND, OR and quoted phrases are allowed, and lowercase or and and between two words work as operators too. Empty matches everything.

museum

string

none

Museum name or unit code, such as "American History", "NMAH", "Asian Art", "NMAA" or "Natural History". "Smithsonian" means every museum.

object_type

string

none

Object type, such as "Paintings" or "Puppets". Case and singular or plural forms are matched.

maker

string

none

Creator, such as "Winslow Homer", "Homer, Winslow", "Homer", "Katsushika Hokusai" or an organization name: the full name or the surname. Results then carry maker_match.

topic

string

none

Subject, such as "Civil War".

material

string

none

Material or medium, such as "bronze".

date_from

integer or string

none

Earliest year, such as 1860 or "1860s", with decade precision. Some records are dated by their subject, so later books about a period can match.

date_to

integer or string

none

Latest year, with decade precision.

has_images

boolean

false

Only objects with online images.

cc0_only

boolean

false

Only objects with CC0 (public domain) media.

on_view

boolean

none

true: only objects on physical exhibit now. false: only objects not on exhibit. Natural History publishes no exhibit data, so its objects never match true.

record_type

string

"objects"

"objects", or "archives" for archival collections and their folders and items, such as papers, photographs and recordings. The API searches the two separately.

limit

integer

10

Objects per page, 1 to 50.

offset

integer

0

Position of the first object. Pass next_offset to get the next page.

Output:

Field

Description

total_count

Number of matching records.

returned

Number of objects in this page.

offset

Offset of this page.

next_offset

Offset of the next page. Always present; null when there are no more results.

museum

{code, name} of the museum filter, when one was given.

note

Explains empty or doubtful results: every word in query must match, the filters (named with their values) match nothing together, query reads like a sentence, maker was matched as keywords at a museum that does not index creators, the offset is past the end, Natural History has no exhibit data (for on_view=true without a museum or at Natural History), a museum has no archive records, or museum="Smithsonian" applied no filter.

objects

Object summaries, described below.

Each object summary has:

Field

Description

id

Object id, for get_object.

title

Title, without HTML markup.

maker

Up to 3 makers. Makers are the creator roles a record names, such as artist, manufacturer, photographer or performer.

maker_match

With a maker filter: whether one of the object's makers matches it, ignoring word order, case, accents and life dates. false when the name matched something else, such as a sitter, owner or a description that mentions the person.

date

Date as the museum records it, such as "1984" or "ca 1995 - 1999".

museum_code, museum_name

The museum that holds the object.

object_type

Object type: the indexed term that the object_type filter matches, else the museum's own label.

on_view

Whether the object is on physical exhibit now. Always present.

exhibition_title, exhibition_location

The exhibition, such as "Japanese Art from the Collection", and its building, room and place, such as "Steven F. Udvar-Hazy Center, National Air and Space Museum, Chantilly, VA", when the object is on view.

collection

For archive records, the archival collection that holds the record.

thumbnail_url

Small image, when the record has one.

web_url

The object's page on the museum website: the record's own link, else the museum's URL pattern for the record id, else the record's persistent ark link, else its url field. Use it as given.

Example:

search_objects(query="muppet", museum="American History", on_view=true)

At the time of writing this finds 12 objects. The first page holds 10, of which two are shown, and offset=10 returns the last two:

{
  "total_count": 12,
  "returned": 10,
  "offset": 0,
  "next_offset": 10,
  "museum": {
    "code": "NMAH",
    "name": "National Museum of American History"
  },
  "objects": [
    {
      "id": "ld1-1643398912743-1643398932982-0",
      "title": "Elmo Puppet",
      "maker": ["Clash, Kevin", "Dillon, Ryan", "Henson, Jim"],
      "date": "1984",
      "museum_code": "NMAH",
      "museum_name": "National Museum of American History",
      "object_type": "Puppets",
      "on_view": true,
      "exhibition_title": "Entertainment Nation",
      "exhibition_location": "National Museum of American History, Washington, DC",
      "web_url": "https://americanhistory.si.edu/collections/object/nmah_1444757"
    },
    {
      "id": "ld1-1643399134763-1643399177676-0",
      "title": "The Muppets Lunch Box",
      "maker": ["King Seeley Thermos", "Thermos"],
      "date": "1979",
      "museum_code": "NMAH",
      "museum_name": "National Museum of American History",
      "object_type": "Lunchboxes",
      "on_view": true,
      "exhibition_title": "Taking America To Lunch",
      "exhibition_location": "National Museum of American History, Washington, DC",
      "web_url": "https://americanhistory.si.edu/collections/object/nmah_1182905"
    }
  ]
}

These records have no images in Open Access, so they have no thumbnail_url. The other objects are the Fozzie Bear, Oscar the Grouch and Rosita puppets, Oscar's trash can and pieces of Mr. Hooper's costume from Sesame Street, all in "Entertainment Nation". Elmo's makers are the performers Kevin Clash and Ryan Dillon, and Jim Henson. Without on_view, the same search finds about 70 Muppet-related objects.

get_object

Get the full record for one object.

Parameter

Type

Description

object_id

string

The id of an object from search_objects or explore_topic. A record id such as nmah_1444757 also works.

Output: every field of an object summary, with up to 10 makers instead of 3, plus:

Field

Description

record_id

The museum's record identifier, such as nmah_1444757.

description

Description, trimmed to 1,500 characters.

summary

Summary, trimmed to 800 characters.

notes

Further notes that do not repeat the description, trimmed to 1,000 characters.

dimensions

Physical dimensions.

materials, topics, place

Materials, subjects and places, up to 12 of each.

credit_line

How the museum acquired the object.

rights

Rights or usage statement.

is_cc0

Whether the object has CC0 media that can be reused freely. Always present.

images

Up to 10 images. Each has url, a screen-sized image that browsers display; download_url, the full-resolution file (a JPEG where one exists); thumbnail_url when it differs from url; iiif_url; caption; and is_cc0.

image_count

Total number of images, given only when there are more than 10.

As in search results, empty fields are left out. An id that does not exist returns an error.

Example, for the Elmo puppet found above:

get_object(object_id="ld1-1643398912743-1643398932982-0")

Returns, with the description and notes shortened here:

{
  "id": "ld1-1643398912743-1643398932982-0",
  "title": "Elmo Puppet",
  "maker": ["Clash, Kevin", "Dillon, Ryan", "Henson, Jim"],
  "date": "1984",
  "museum_code": "NMAH",
  "museum_name": "National Museum of American History",
  "object_type": "Puppets",
  "on_view": true,
  "exhibition_title": "Entertainment Nation",
  "exhibition_location": "National Museum of American History, Washington, DC",
  "web_url": "https://americanhistory.si.edu/collections/object/nmah_1444757",
  "record_id": "nmah_1444757",
  "description": "This Elmo puppet was used on Sesame Street from about 1984 until the early 2000s. ...",
  "notes": "Designed by the nonprofit Children's Television Workshop to teach basic reading, math, and life skills ...",
  "dimensions": "overall: 14 in x 16 in x 11 in; 35.56 cm x 40.64 cm x 27.94 cm",
  "materials": [
    "plastic (overall material)",
    "synthetic fur (overall material)",
    "foam (overall material)"
  ],
  "topics": [
    "Jim Henson",
    "Amusements",
    "Sesame Street",
    "Puppets",
    "Children's television programs",
    "Television broadcasts",
    "In Pursuit of Life, Liberty, and Happiness"
  ],
  "place": ["New York", "Queens", "United States"],
  "credit_line": "A Gift from the Family of Jim Henson: Lisa Henson, Cheryl Henson, Brian Henson, John Henson and Heather Henson",
  "is_cc0": false
}

The museum website shows photos of Elmo under usage conditions, but Open Access publishes none, so the record has no images or thumbnail_url and is_cc0 is false. Many other objects, such as the Asian Art tea bowls in Search tips, have CC0 images.

list_museums

List the Smithsonian units that contribute to Open Access. It takes no parameters and makes at most one request, cached for the life of the server.

Output: a list with one entry per unit:

Field

Description

code

Unit code, accepted by museum.

name

Unit name.

record_types

The record_type values of search_objects that return the unit's records: ["objects"], ["archives"] for the 14 archive-only units such as the Archives of American Art, or both for units such as the Smithsonian Institution Archives.

aliases

Lowercase names that museum accepts for the unit. Left out when the only alias would repeat the name.

The list has 49 entries: the unit codes in the search index, plus NMNH, which covers every Natural History department (NMNHPALEO, NMNHBOTANY and the others). It has no counts; use get_collection_stats, whose counts match search results. Clients that read structured tool output receive the list wrapped as {"result": [...]}.

Example: list_museums() returns entries such as these:

[
  {"code": "AAA", "name": "Archives of American Art", "record_types": ["archives"]},
  {"code": "NMAA", "name": "National Museum of Asian Art", "record_types": ["objects"], "aliases": ["freer", "sackler", "asian art"]},
  {"code": "NMAH", "name": "National Museum of American History", "record_types": ["objects"], "aliases": ["american history"]},
  {"code": "SIA", "name": "Smithsonian Institution Archives", "record_types": ["objects", "archives"], "aliases": ["smithsonian archives"]}
]

explore_topic

Get a varied sample of objects on a topic, for open-ended browsing.

Parameter

Type

Default

Description

topic

string

required

Topic keywords, such as "quilts". As in query, every word must match.

museum

string

none

Museum name or code, to explore one museum.

limit

integer

12

Number of objects, 1 to 30.

Output: the same fields as search_objects, plus facets:

Field

Description

facets.museums

Objects in the sampled pool by unit code, such as {"NASM": 18}.

facets.object_types

The 10 most common object types in the pool.

The tool takes the 100 most relevant matches with images (adding matches without images only when fewer than limit come back). Free-text matching also finds words in places and notes, so a lichen collected at Dinosaur National Monument matches "dinosaurs"; the tool therefore prefers objects whose title, type or subjects name the topic, and fills the sample with the other matches only when it runs out. It allocates picks to museums in proportion to their share of those objects, at least one each, and varies object types within a museum. The facets count the same objects. next_offset is always null, note says how many of the pool name the topic, and repeated calls can return different samples; use search_objects for a complete, paged list.

Example: in one run, explore_topic(topic="space exploration") returned 12 objects, among them a reconstructed Pioneer 10 mock-up, an engineering model of Mariner 2 and a model of the Hubble Space Telescope from Air and Space, a space-suit jumpsuit and a Flash Gordon comic strip from American History, a Palomar Observatory stamp plate proof from the Postal Museum and a print from Jules Verne's From the Earth to the Moon from the Libraries. Its facets were:

{
  "museums": {"NASM": 18, "NMAH": 6, "SIA": 5, "NPM": 3, "SIL": 1},
  "object_types": {
    "Uncrewed spacecraft": 11,
    "Archival materials": 5,
    "Certified plate proofs": 3,
    "Space suit": 2,
    "Crewed spacecraft": 2,
    "Lunchboxes": 2,
    "Testing equipment": 1,
    "Drawing; pen and ink": 1,
    "Models": 1,
    "Booklet, cereal box": 1
  }
}

get_collection_stats

Count what search can return, for the whole collection or one museum.

Parameter

Type

Default

Description

museum

string

none

Museum name or unit code. Leave it out for the whole collection.

Field

Description

museum

{code, name} of the museum, when one was given.

objects

Objects that search_objects can return: its total_count with no query.

archive_records

The same with record_type="archives".

objects_with_images

The same with has_images=true.

objects_with_cc0_media

The same with cc0_only=true.

Each figure is the total_count of the matching search_objects call, so counts always agree with search results. The four counts take four requests, cached for 6 hours per museum. The API's own statistics endpoint is not used: its per-museum totals include records that search cannot return and disagree with search by up to 2,000 times (2,360,167 for Air and Space against 1,012 searchable objects).

Example: get_collection_stats() returns:

{
  "objects": 14520188,
  "archive_records": 2820187,
  "objects_with_images": 7495314,
  "objects_with_cc0_media": 5254461
}

and get_collection_stats(museum="Air and Space") returns:

{
  "museum": {"code": "NASM", "name": "National Air and Space Museum"},
  "objects": 1012,
  "archive_records": 0,
  "objects_with_images": 995,
  "objects_with_cc0_media": 995
}

Resources

URI

Content

smithsonian://museums

The museum list from list_museums, as JSON.

smithsonian://objects/{object_id}

The record from get_object for that id, as JSON.

Clients that support resources can attach these to a conversation without a tool call.

Prompts

Prompt

Arguments

Purpose

collection_research

research_topic, focus_area (optional)

Research a topic across the collections.

object_analysis

object_id

Analyze one object in depth.

exhibition_planning

exhibition_theme, target_audience (optional), size (optional: small, medium or large)

Plan an exhibition from collection objects.

educational_content

subject, grade_level (optional), learning_goals (optional), session_minutes (optional: 10 to 480)

Build a lesson around collection objects. With session_minutes, the lesson features only as many objects as the session has time for, such as 2 or 3 for 60 minutes, and includes a timed agenda.

museum_on_view

museum, topic (optional)

Find out what is on view at a museum.

Search tips

  • Every word in query must match, so use 1 to 4 distinctive keywords and leave out questions and stop words. "Which Muppets are on display at the American History museum?" finds one puppet that is not on view, and the result's note says that the query reads like a sentence; query="muppet" with museum="American History" and on_view=true finds the 12 objects above. Dropping stop words does not help: "Muppets display American History museum" finds 3 objects, none on view.

  • Use OR for alternatives, as in query="quilt OR coverlet". Lowercase or between two words works too.

  • Put names in maker, not query. maker="Winslow Homer" also matches the indexed form "Homer, Winslow", and search_objects(maker="Winslow Homer", object_type="Paintings") returns works such as "Girl Shelling Peas" and "White Mountain Wagon" from Cooper Hewitt, each with object_type "Paintings". Art is well covered: object_type="Paintings" alone matches thousands of records.

  • query matches every part of a record, so a name in query also finds works that only mention the person: query="Hokusai" with museum="Asian Art" and on_view=true returns a Whistler painting whose notes mention Hokusai. maker="Hokusai" finds works by him, and maker_match is false on any result where the name is not one of the makers.

  • The National Museum of Asian Art and the National Museum of African Art do not index creator names; the indexed names at Asian Art are collectors and dealers such as Charles Lang Freer. At those two museums maker is matched as keywords anywhere in the record, in any order, so a single name such as "Hokusai" works. The match is precise for artists (159 of the 167 Asian Art records that mention Hokusai are by him), but total_count includes the rest, and the result's note says so.

  • museum accepts names or codes. "Asian Art", "Freer", NMAA and the retired code FSG all search the National Museum of Asian Art, "African American Museum" searches the National Museum of African American History and Culture, and "Natural History" or NMNH searches every Natural History department. Every distinctive word of a name must match, so an unknown name returns an error rather than a guess. "Smithsonian" and "Smithsonian Institution" mean every museum, so no filter is applied and the note says so.

  • Dates have decade precision, so date_from=1863 starts at 1860, and decades such as "1860s" are accepted. search_objects(query="Lincoln", museum="American History", date_from=1860, date_to=1869) returns items such as a Lincoln campaign flag from 1864 and a parade axe from 1860. Some records, notably library books, are dated by their subject, so date_from="1860s" alone also finds books published in 2008 about the period. Years must be from 1000 to 2999.

  • on_view=true returns objects on physical exhibit now, with exhibition titles and locations. Natural History publishes no exhibit data, so on_view=true with Natural History always returns nothing, and without a museum it never includes Natural History objects; the result's note says so in both cases.

  • Archive records (finding aids, folders and items such as letters and photographs) are searched with record_type="archives". 14 units, such as the Archives of American Art, publish only archive records; list_museums shows their record_types as ["archives"], and an object search limited to one of them returns an error that says to use record_type="archives". search_objects(query="letters", museum="Archives of American Art", record_type="archives") finds about 17,500 records, each with its collection.

  • cc0_only=true keeps objects whose media can be reused freely. search_objects(query="tea bowl", museum="Asian Art", cc0_only=true) returns Hagi and Raku ware tea bowls with CC0 images.

  • Never construct Smithsonian URLs; use web_url. URL formats differ by museum and are case-sensitive.

Migrating from 1.x

Version 2.0 replaces all 28 tools of 1.x with 5. Calls to a 1.x tool name fail, so update any prompts, scripts or mcpo endpoint URLs that use them.

Removed tools

Removed

Use instead

search_collections, simple_search, search_by_unit, get_search_context

search_objects

summarize_search_results, get_object_ids, get_first_object_id

No replacement needed; results are already compact

find_and_describe, search_and_get_first_details, search_and_get_details

search_objects, then get_object

get_object_details, get_object_context, validate_object_id, get_object_url, search_and_get_first_url

get_object

get_smithsonian_units, get_units_context, resolve_museum_name

list_museums; search_objects also accepts museum names

get_objects_on_view, find_on_view_items, get_museum_highlights_on_view, get_on_view_context

search_objects(on_view=true)

simple_explore, continue_explore

explore_topic

get_collection_statistics, get_stats_context

get_collection_stats

get_museum_collection_types, check_museum_has_object_type

search_objects(object_type=..., museum=..., limit=1); total_count answers it

Breaking changes

  • Tool names: every 1.x tool is gone, as listed above. Through mcpo the endpoints change too, so /smithsonian_open_access/get_smithsonian_units becomes /smithsonian_open_access/list_museums.

  • Counts: get_collection_stats reports counts that match search results instead of the API's statistics, and list_museums no longer reports counts.

  • Output shapes: searches return compact summaries instead of full records, and fields are renamed. unit_code is now museum_code, unit_name is museum_name, is_on_view is on_view and returned_count is returned. has_more is gone; next_offset is null on the last page. Links to object pages are in web_url. Empty fields are left out instead of being returned as null.

  • Parameters: museum takes names or codes and replaces unit_code. The is_cc0 filter is now cc0_only, limit defaults to 10 with a maximum of 50 (it was 500), date_from and date_to filter by date, and record_type="archives" searches archive records.

  • Asian Art is unit code NMAA. FSG is still accepted as an alias, but results report NMAA.

  • is_cc0 on an object now means the object has CC0 media. Records with CC0 text but restricted or no media, such as copyrighted objects at the National Museum of African American History and Culture, are no longer reported as CC0.

  • Prompts drop the _prompt suffix from their names, and six prompts that only restated tool usage are removed. See the changelog.

Integration

Claude Desktop

See Quick Start for Claude Desktop configurations using uvx, npm/npx, a local clone, a virtual environment or Docker. A ready-to-copy example is in examples/claude-desktop-config.json.

mcpo Integration (MCP Orchestrator)

mcpo is an MCP orchestrator that converts multiple MCP servers into OpenAPI/HTTP endpoints, ideal for combining multiple services into a single systemd service.

Installation

# Install mcpo as a uv tool
uv tool install mcpo

# Or run it without installing
uvx mcpo --help

Configuration

Copy examples/mcpo-config.json to mcpo-config.json in the project root and fill in your paths and API key, or let config/setup.sh generate it. The generated file contains your API key, so do not commit it. A minimal configuration:

{
  "mcpServers": {
    "smithsonian_open_access": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/molanojustin/smithsonian-mcp",
        "smithsonian-mcp"
      ],
      "env": {
        "SMITHSONIAN_API_KEY": "your_api_key_here"
      }
    },
    "memory": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-memory"]
    },
    "time": {
      "command": "uvx",
      "args": ["mcp-server-time", "--local-timezone=America/New_York"]
    }
  }
}

Running with mcpo

# Start mcpo with hot-reload
mcpo --config mcpo-config.json --port 8000 --hot-reload

# With API key authentication
mcpo --config mcpo-config.json --port 8000 --api-key "your_secret_key"

# Access endpoints:
# - Smithsonian: http://localhost:8000/smithsonian_open_access
# - Memory: http://localhost:8000/memory
# - Time: http://localhost:8000/time
# - API docs: http://localhost:8000/docs

Systemd Service

Create /etc/systemd/system/mcpo.service:

[Unit]
Description=MCP Orchestrator Service
After=network.target

[Service]
Type=simple
User=your-user
WorkingDirectory=/path/to/your/config
Environment=PATH=/path/to/venv/bin
ExecStart=/path/to/venv/bin/mcpo --config mcpo-config.json --port 8000
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
# Enable and start service
sudo systemctl enable mcpo
sudo systemctl start mcpo
sudo systemctl status mcpo

Troubleshooting mcpo

See TROUBLESHOOTING.md for detailed mcpo troubleshooting, including:

  • ModuleNotFoundError solutions

  • Connection closed errors

  • Port conflicts

  • Path configuration issues

VS Code

Open the clone with code .. After uv sync --group dev, .vscode/tasks.json provides tasks to start the server, run the tests, format and lint the code, and open the MCP Inspector, and .vscode/launch.json provides debugger configurations for the server and the tests.

Requirements

For uvx or a local clone:

  • uv, which installs Python 3.10 or newer if needed

  • API key from api.data.gov (free)

  • Internet connection for API access

For npm/npx installation:

  • Node.js 16.0 or higher

  • uv (the wrapper uses it to install the Python dependencies)

  • API key from api.data.gov (free)

  • Internet connection for API access

For a virtual environment without uv:

  • Python 3.10 or higher (CI tests 3.10 through 3.14)

  • API key from api.data.gov (free)

  • Internet connection for API access

Testing

Using npm/npx:

# Test API connection
smithsonian-mcp --test

# Run MCP server (stdio)
smithsonian-mcp

# Run MCP server over HTTP at http://127.0.0.1:8000/mcp
smithsonian-mcp --transport http

# Show help
smithsonian-mcp --help

From a local clone:

# Install runtime and development dependencies
uv sync --group dev

# Test API connection
uv run python examples/test-api-connection.py

# Run MCP server (stdio; normally your MCP client starts it)
uv run smithsonian-mcp

# Explore the server interactively with the MCP Inspector
npx @modelcontextprotocol/inspector .venv/bin/smithsonian-mcp

# Run the offline test suite (no API key or network needed)
uv run pytest tests/

# Run the opt-in live tests (they use your API key and its rate limit)
SMITHSONIAN_LIVE_TESTS=1 uv run pytest tests/ -m live

# Check formatting and lint, as CI does
uv run black --check smithsonian_mcp/ tests/ examples/ scripts/ .github/scripts/
uv run pylint smithsonian_mcp/

# Verify complete setup
uv run python scripts/verify-setup.py

The offline tests block outgoing network access and answer API requests from tests/fake_api.py, so they need no key. The test files are:

File

Covers

tests/test_tools.py

The five tools, resources and prompts, against the fake API

tests/test_query_building.py

Free-text parsing and the filter clauses of the q parameter

tests/test_client_behaviour.py

Record parsing, units, the shared client, logging and the entry points

tests/test_http_transport.py

The transport options, and servers started in HTTP mode, including the Host header checks

tests/test_on_view.py

On-view filters and exhibition fields

tests/test_utils.py

Museum name resolution, unit codes and page URLs

tests/test_api_client_error_handling.py, tests/test_key_obfuscation.py

API errors, and that the key stays out of URLs and logs

tests/test_basic.py

Configuration, models and client setup

tests/test_live.py, tests/test_tools_live.py

Live API checks of the client and the tools (opt-in)

Service Management

The setup scripts can register the server as a background service. The service runs the server with --transport http --host 127.0.0.1 --port 8000, so it stays up and serves MCP at http://127.0.0.1:8000/mcp for clients that connect over HTTP (see HTTP transport). It reads the API key from .env in the project root, which the scripts make readable by your user only. Running a setup script again rewrites the service and reloads or restarts it, so it picks up the new settings. MCP clients such as Claude Desktop start their own stdio server, so they do not need the service. To expose the tools as OpenAPI endpoints instead, run them behind mcpo.

Linux (systemd)

# Start service
systemctl --user start smithsonian-mcp

# Stop service
systemctl --user stop smithsonian-mcp

# Check status
systemctl --user status smithsonian-mcp

# Enable on login
systemctl --user enable smithsonian-mcp

When ~/.config/systemd/user does not exist, the script installs a system service instead; manage it with sudo systemctl and no --user.

macOS (launchd)

# Load service
launchctl load ~/Library/LaunchAgents/com.smithsonian.mcp.plist

# Unload service
launchctl unload ~/Library/LaunchAgents/com.smithsonian.mcp.plist

# Check status
launchctl list | grep com.smithsonian.mcp

The server's log is ~/Library/Logs/com.smithsonian.mcp.log.

Windows

# Start service
Start-Service SmithsonianMCP

# Stop service
Stop-Service SmithsonianMCP

# Check status
Get-Service SmithsonianMCP

smithsonian-mcp.exe is a console program, so the Windows service starts only when it is wrapped by a service host such as NSSM.

Troubleshooting

TROUBLESHOOTING.md covers:

  • API key and rate limit errors

  • Searches that return nothing, including on-view searches at Natural History

  • Old 1.x tool names that no longer work

  • Claude Desktop connection and server startup problems

  • HTTP mode, background services and Docker

  • Module import errors and mcpo setup

Documentation

  • README.md: setup and tool reference (this file)

  • TROUBLESHOOTING.md: common problems and fixes

  • CHANGELOG.md: changes between versions

  • examples/: Claude Desktop and mcpo configurations and an API connection test

  • scripts/: setup verification

Contributing

  1. Fork the repository

  2. Create a feature branch

  3. Make your changes

  4. Run the tests, black and pylint (see Testing); CI runs all three

  5. Submit a pull request

The package is organized by responsibility:

Module

Contents

main.py

Command line entry point, logging setup and the stdio and HTTP transports

app.py

The FastMCP server, its instructions and registration

tools.py

The five tools

formatting.py, notes.py, sampling.py

Result summaries and records, result notes, and the explore_topic sample

resources.py, prompts.py

MCP resources and prompts

api_client.py

HTTP client for the Open Access API and its error mapping

query.py

Free-text query parsing and the filter clauses of the q parameter

parsing.py

Records parsed into SmithsonianObject models

context.py

The shared API client and the server lifespan

models.py, constants.py, utils.py, config.py

Data models, static tables, museum names and URLs, settings

License

MIT License. See LICENSE.md.

Acknowledgments

  • Smithsonian Institution for the Open Access collections

  • api.data.gov for the API infrastructure

  • The FastMCP team for the MCP framework

  • The Model Context Protocol community

Available Tools

5 tools
explore_topicExplore TopicA
Read-onlyIdempotent

A varied sample of objects about a topic, spread across museums in proportion to their matches and across object types, with counts by museum and type. Prefers objects with images whose title, type or subject names the topic. Use it for open-ended browsing; use search_objects to find specific things. Calls can return different samples.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoNumber of objects to return.
topicYesTopic keywords, e.g. "dinosaurs" or "jazz". Every word must match.
museumNoOptional museum name or unit code.

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteNo
facetsNoCounts over the sampled pool of a topic exploration.
museumNoMuseum filter that was applied
offsetNo
objectsNo
returnedYes
next_offsetNoOffset of the next page; null when there are no more
total_countYesAll matches, not just this page

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnly/openWorld/idempotent hints by disclosing non-obvious behavior: results are a sample, results shift between calls, image-bearing objects are preferred, and matches are spread proportionally across museums and types. The only nuance is that 'calls can return different samples' sits in tension with idempotentHint=true, though per MCP semantics idempotency concerns side effects (this tool is read-only), so it is not a true contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences with no filler, front-loading the return shape and then the routing guidance. The final sampling-caveat sentence earns its place but the opening sentence is quite packed, slightly hurting scannability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and the annotations carry the safety profile. The description still adds the return shape (counts by museum and type) and the sampling caveat, leaving nothing an agent needs missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% (baseline 3), but the description adds real semantic value for the required topic parameter: matching prefers objects whose title, type, or subject names the topic, which tells the agent how loose/strict matching behaves beyond the schema's 'every word must match' note.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (explore/sample) and resource (objects about a topic), and precisely specifies the sampling behavior: proportionally spread across museums and object types with counts. It also explicitly distinguishes itself from search_objects, so an agent can pick it without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Use it for open-ended browsing; use search_objects to find specific things' gives an explicit when-to-use and names the alternative with the condition that selects it. Nothing is left to inference about routing between the two tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_collection_statsGet Collection StatsA
Read-onlyIdempotent

Counts of searchable objects, archive records, objects with images and objects with CC0 media, for the whole collection or one museum. Each count equals the total_count of the matching search_objects call.

ParametersJSON Schema
NameRequiredDescriptionDefault
museumNoOptional museum name or unit code.

Output Schema

ParametersJSON Schema
NameRequiredDescription
museumNo
objectsYessearch_objects total_count with no filters
archive_recordsYesSame, with record_type='archives'
objects_with_imagesYesSame, with has_images=true
objects_with_cc0_mediaYesSame, with cc0_only=true

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the safe, idempotent, read-only, open-world profile, so the description only needs to add context. It usefully clarifies what each count means and its provenance (equivalence to search_objects total_count), which is real value beyond the annotations, but says nothing about cost, caching, or freshness of the counts.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences with no filler; the enumeration of counted categories comes first and the equivalence note follows as a useful second beat. Nothing could be removed without losing information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need no explanation, and the description covers scope, count categories, and semantics of the numbers for this simple one-parameter tool. Only marginally incomplete for lacking any note on when counts might be stale or when to prefer search_objects directly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single optional 'museum' parameter is documented in-schema as 'museum name or unit code.' The description adds only the default-behavior meaning ('whole collection or one museum'), which is marginal but confirms what happens when the argument is omitted.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific operation (counts) over a specific resource (searchable objects, archive records, image-bearing objects, CC0-media objects) and names the scope options. It distinguishes itself from siblings by tying each count back to the total_count of a search_objects call, though it never explicitly frames itself as the alternative to search_objects.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: by stating each count equals the matching search_objects total_count, it hints that this tool is a shortcut for counting rather than paging results. There is no explicit when-to-use/when-not statement, nor guidance on choosing between whole-collection and single-museum scope beyond restating the parameter.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_objectGet ObjectA
Read-onlyIdempotent

Full record of one object: description, notes, materials, topics, rights, up to 10 images and the web_url of its page.

ParametersJSON Schema
NameRequiredDescriptionDefault
object_idYesAn id from search_objects or explore_topic results. Record ids such as "nmah_1448973" also work.

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYesPass to get_object for the full record
dateNo
makerNo
notesNo
placeNo
titleYes
imagesNo
is_cc0NoHas CC0 (public domain) media
rightsNo
topicsNo
on_viewNoOn physical exhibit now
summaryNo
web_urlNoObject page; use as given
materialsNo
record_idNo
collectionNoArchival collection of an archive record
dimensionsNo
credit_lineNo
descriptionNo
image_countNoTotal images, given when more exist than are listed
maker_matchNoGiven with a maker filter: whether a listed creator matches it. False when the name matched something else, such as a sitter, owner or description
museum_codeNo
museum_nameNo
object_typeNo
thumbnail_urlNo
exhibition_titleNo
exhibition_locationNo

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and openWorldHint, so the safety profile is covered. The description adds one useful behavioral detail, the 'up to 10 images' cap, but says nothing about permissions, error behavior, or pagination beyond that, and return-shape detail is largely redundant given an output schema exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One tight sentence, front-loaded with what is returned, no filler. Every clause names content the caller will receive.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With one required parameter fully documented, an output schema, and annotations covering safety, the definition is nearly complete for a single-record lookup. It could add one clause on when to prefer this over search_objects, but nothing essential to correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% for the single object_id parameter, including the accepted record-id format, so the schema carries the burden. The description adds no parameter-level meaning beyond what is already documented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource and scope: the full record of one object, enumerated by content type (description, notes, materials, topics, rights, images, web_url). The singular 'one object' implicitly contrasts with search_objects/list results, but no sibling is named, so it stops short of explicit differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no when-to-use guidance or named alternatives. Usage is only implied through the schema's note that object_id comes from search_objects or explore_topic results, which suggests the lookup-after-search workflow but is not stated in the description itself.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_museumsList MuseumsA
Read-onlyIdempotent

Smithsonian units in Open Access: codes, names, the record_type values that return their records, and the names the museum argument accepts. For counts use get_collection_stats.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and openWorldHint, so the safety profile is covered. The description adds that the returned names are the ones the museum argument accepts, which is genuinely useful cross-tool context, but most of the remaining text restates the payload rather than disclosing behavior beyond the structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, zero filler, with the payload enumerated first and the routing hint second. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and zero parameters, the definition needs only to state what is listed and where to go for counts, both of which it does. Nothing required to invoke this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the schema baseline is 4. The description's mention of 'the names the museum argument accepts' usefully ties the output to a parameter used by sibling tools, though it is not a parameter of this tool itself.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a concrete resource (Smithsonian units in Open Access) and enumerates the payload it surfaces: codes, names, record_type values, and the museum argument names. It is distinguishable from get_collection_stats via the explicit counts redirect, though the jargon (record_type, museum argument) assumes domain context.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It supplies one explicit alternative — 'For counts use get_collection_stats' — which routes the agent correctly for the counting case. It implies the primary use (looking up valid museum codes/names for other calls) without spelling out when-not-to-use or naming the search/object siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_objectsSearch ObjectsA
Read-onlyIdempotent

Search Smithsonian collection objects by keyword and filters. Returns compact summaries; pass an id to get_object for the full record. For "how many" questions use limit=1 and read total_count. For works by a person use maker, since query also matches records that only mention them.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoResults per page.
makerNoCreator name, full or surname, e.g. "Winslow Homer", "Homer", "Katsushika Hokusai". Results then carry maker_match.
queryNoKeywords, matched anywhere in a record, descriptions and notes included. Every word must match, so use 1-4 distinctive words, OR between alternatives ("muppet OR henson") and quotes for phrases. Leave out stop-words and questions. Empty matches everything.
topicNoSubject term, e.g. "Civil War".
museumNoMuseum name or unit code, e.g. "American History", "NMAH", "Asian Art", "Natural History".
offsetNoStart position; pass next_offset to get the next page.
date_toNoLatest year, matched by decade.
on_viewNotrue for objects on physical exhibit now, false for objects not on exhibit. Natural History (NMNH) has no exhibit data, so its objects never match true.
cc0_onlyNoOnly objects with CC0 (public domain) media.
materialNoMaterial or medium, e.g. "bronze".
date_fromNoEarliest year, such as 1860 or "1860s", matched by decade. Some records are dated by their subject, so later books about a period can match.
has_imagesNoOnly objects with images.
object_typeNoObject type, e.g. "Paintings", "Puppets".
record_typeNo"objects", or "archives" for archival collections and their folders and items (papers, photographs, recordings), which the API searches separately from objects.objects

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteNo
museumNoMuseum filter that was applied
offsetNo
objectsNo
returnedYes
next_offsetNoOffset of the next page; null when there are no more
total_countYesAll matches, not just this page

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/openWorld/idempotent, so the safety profile is covered. The description adds useful behavior beyond that: results are compact summaries, results carry maker_match when maker is used, and query matches descriptions/notes broadly. It does not detail pagination via next_offset, but that is documented in the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four tight sentences, front-loaded with the core capability, then routing hints in descending priority. Every sentence adds a distinct decision rule; no filler or restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 14-parameter search tool with an output schema and full schema coverage, the description covers scope, result type, counting workflow, and special-parameter caveats. Missing only fine-grained behavioral notes (pagination cadence, error conditions), which are minor given output schema and rich annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so parameter documentation already lives in the schema and the baseline of 3 applies. The description does reinforce two high-stakes semantics (maker vs query, limit=1 for total_count) but adds little syntax beyond what the schema already states for each parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource ('Search Smithsonian collection objects') and immediately scopes the result type ('compact summaries'), distinguishing it from get_object which returns full records. An agent can route between search_objects and get_object without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives three explicit routing rules: pass an id to get_object for full records, use limit=1 + total_count for counting questions, and use maker rather than query for works by a person. It even explains why (query also matches records that merely mention someone), which is exactly the when/why guidance that prevents wrong calls.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv2.1.0
    • First observedexplore_topic
    • First observedget_collection_stats
    • First observedget_object
    • First observedlist_museums
    • First observedsearch_objects

TDQS

A4/5.0

Scored across 5 tools

Disambiguation4/5

Each tool has a distinct primary purpose: search_objects for specific queries, get_object for full records, list_museums for museum metadata, explore_topic for open-ended browsing, and get_collection_stats for aggregate counts. However, search_objects also advertises using it for 'how many' questions via total_count, which overlaps with get_collection_stats and could cause misselection for count queries.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern (search_objects, get_object, list_museums, explore_topic, get_collection_stats). The verbs are descriptive and predictable, making the set easy to scan.

Tool Count5/5

With only 5 tools, the set is well-scoped for the Smithsonian Open Access domain. Each tool covers a distinct access pattern (search, retrieve, list, explore, aggregate) without redundancy, making it easy to navigate.

Completeness4/5

The tools cover core workflows: searching, retrieving full records, listing museums, browsing topics, and getting statistics. Minor gaps exist—such as no dedicated tool to list available topics or object types, and no direct media download—but these can be worked around via existing tools.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables AI agents to search and explore over 570,000 artworks from the Metropolitan Museum of Art and Art Institute of Chicago without needing an API key.
    9
    2
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables searching and exploring millions of items from the Smithsonian Institution's collections including artifacts, artworks, specimens, photographs, and more, using the Smithsonian Open Access API.
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables searching and retrieving detailed information about items from the Smithsonian Open Access collection, including metadata and images.
    391 npm
    MIT