inaturalist-mcp-server

v0.1.2 pre-1.0

Search wildlife sightings, read identification threads, rank species by area, chart phenology, and find look-alike taxa from iNaturalist via MCP. STDIO or Streamable HTTP.

inaturalist.caseyjhand.com/mcp
claude mcp add --transport http inaturalist-mcp-server https://inaturalist.caseyjhand.com/mcp
codex mcp add inaturalist-mcp-server --url https://inaturalist.caseyjhand.com/mcp
{
  "mcpServers": {
    "inaturalist-mcp-server": {
      "url": "https://inaturalist.caseyjhand.com/mcp"
    }
  }
}
gemini mcp add --transport http inaturalist-mcp-server https://inaturalist.caseyjhand.com/mcp
{
  "mcpServers": {
    "inaturalist-mcp-server": {
      "command": "bunx",
      "args": [
        "mcp-remote",
        "https://inaturalist.caseyjhand.com/mcp"
      ]
    }
  }
}
{
  "mcpServers": {
    "inaturalist-mcp-server": {
      "type": "http",
      "url": "https://inaturalist.caseyjhand.com/mcp"
    }
  }
}
curl -X POST https://inaturalist.caseyjhand.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'

Tools

10

inaturalist_list_reference

open-world

Decode the vocabularies the other iNaturalist tools take as input: annotation attributes and values, quality grades, license codes, taxonomic ranks, iconic taxa, and IUCN conservation-status codes. An unrecognized filter value is not rejected upstream — it silently returns nothing — so read the codes here before filtering. Note that the conservation codes are the normalised csi search filter; a taxon record’s own conservation_statuses[].status is authority-specific free text and reads differently. With topic controlled_terms and a taxon_id, the response also carries which annotations identifiers have actually recorded for that taxon, with counts.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_list_reference",
    "arguments": {
      "topic": "<topic>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "topic": {
      "type": "string",
      "enum": [
        "controlled_terms",
        "quality_grades",
        "licenses",
        "ranks",
        "iconic_taxa",
        "conservation_status_codes"
      ],
      "description": "Which vocabulary to decode. controlled_terms is fetched live and cached; the rest are spec-derived static tables."
    },
    "taxon_id": {
      "description": "Add observed annotation usage for this taxon, ranked by how often each attribute/value pair has been recorded. Valid only with topic controlled_terms. Resolve a name to an id with inaturalist_resolve_name.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "topic"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_resolve_name

open-world

Resolve a common or scientific name to a taxon id, or a place, project, or observer name to its id. Returns ranked candidates carrying the identifiers every other tool takes. A miss is a result rather than a failure: found comes back false with guidance naming why. Taxon lookup matches a name PREFIX, not words inside a name, so "monarch butterfly" misses where "monarch" hits.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_resolve_name",
    "arguments": {
      "q": "<q>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "q": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100,
      "description": "The name to resolve. On type \"taxon\" this is a name prefix or an exact taxon id; on the other types it is matched across the record text."
    },
    "type": {
      "default": "taxon",
      "description": "Which kind of record to resolve. \"taxon\" uses the taxon autocomplete; the rest use the scored cross-kind search, and \"any\" searches every kind at once. For a place’s bounding box and containment chain rather than just its id, use inaturalist_find_places instead.",
      "type": "string",
      "enum": [
        "taxon",
        "place",
        "project",
        "user",
        "any"
      ]
    },
    "rank": {
      "description": "Restrict taxon candidates to one rank. Honoured only on type \"taxon\" — the cross-kind search has no rank filter.",
      "type": "string",
      "enum": [
        "stateofmatter",
        "kingdom",
        "phylum",
        "subphylum",
        "superclass",
        "class",
        "subclass",
        "superorder",
        "order",
        "suborder",
        "infraorder",
        "superfamily",
        "epifamily",
        "family",
        "subfamily",
        "supertribe",
        "tribe",
        "subtribe",
        "genus",
        "genushybrid",
        "species",
        "hybrid",
        "subspecies",
        "variety",
        "form"
      ]
    },
    "limit": {
      "default": 10,
      "description": "Maximum candidates to return.",
      "type": "integer",
      "minimum": 1,
      "maximum": 30
    }
  },
  "required": [
    "q",
    "type",
    "limit"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_find_places

open-world

Resolve a place name to a place id, or list the places containing a map area. Pass q to match a place-name PREFIX, or all four of nelat, nelng, swlat and swlng to list every place covering that box — exactly one of the two, never both. Each result carries the bounding box, place type, and containment chain an area search needs. Place geometry is stripped: a single nearby response carries 247 KB of boundary polygons upstream, none of which reaches the caller.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_find_places",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "q": {
      "description": "Place-name prefix to search. Matches the start of a name, not words inside it. Mutually exclusive with the bounding box.",
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "nelat": {
      "description": "North-east corner latitude of the map area. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "nelng": {
      "description": "North-east corner longitude of the map area. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "swlat": {
      "description": "South-west corner latitude of the map area. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "swlng": {
      "description": "South-west corner longitude of the map area. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "per_page": {
      "default": 10,
      "description": "Maximum places to return. Honoured on the bounding-box arm only — the name-prefix endpoint publishes no page size and returns a fixed page.",
      "type": "integer",
      "minimum": 1,
      "maximum": 30
    }
  },
  "required": [
    "per_page"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_search_observations

open-world

Search georeferenced wildlife sightings by area, date, taxon, quality grade, annotation, and conservation status. Returns a projected record per sighting with coordinates, licence, first photo, and identification counts. An area is given in exactly one form — place_id, the lat/lng/radius triple in kilometres, or a four-corner bounding box — and defaults to research-grade, wild-only records, which are echoed back on every call. Identifications and comments are deliberately not expandable here (one thread is 28 KB); fetch them for specific records with inaturalist_get_observation. Results past 10,000 need the cursor from the previous page rather than a higher page number.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_search_observations",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "place_id": {
      "description": "Numeric iNaturalist place id from inaturalist_find_places. Mutually exclusive with the lat/lng/radius triple and the bounding box. A non-numeric value answers HTTP 500 upstream.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "lat": {
      "description": "Latitude of the search centre, in decimal degrees. Requires lng and radius.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "lng": {
      "description": "Longitude of the search centre, in decimal degrees. Requires lat and radius.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "radius": {
      "description": "Search radius around lat/lng, in KILOMETRES. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes.",
      "type": "number",
      "minimum": 0,
      "maximum": 500
    },
    "nelat": {
      "description": "North-east corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "nelng": {
      "description": "North-east corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "swlat": {
      "description": "South-west corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "swlng": {
      "description": "South-west corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "taxon_id": {
      "description": "Restrict to this taxon and its descendants. Resolve a name to an id with inaturalist_resolve_name.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "d1": {
      "description": "Earliest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "d2": {
      "description": "Latest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "quality_grade": {
      "default": [
        "research"
      ],
      "description": "Identification confidence tiers to include. Defaults to research-grade only; adding \"needs_id\" roughly doubles the corpus and lowers identification confidence.",
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "research",
          "needs_id",
          "casual"
        ]
      }
    },
    "captive": {
      "default": false,
      "description": "Whether to include captive and cultivated records — zoo animals, garden plantings. Defaults to wild organisms only.",
      "type": "boolean"
    },
    "term_id": {
      "description": "Annotation attribute ids, from inaturalist_list_reference topic controlled_terms — e.g. 1 for Life Stage.",
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991
      }
    },
    "term_value_id": {
      "description": "Annotation value ids, from the same attribute listing — e.g. 6 for Larva. Requires term_id; sent alone it is ignored upstream and the unfiltered corpus comes back.",
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991
      }
    },
    "iconic_taxa": {
      "description": "Broad organism groups, by their scientific iconic-taxon name. A common-name value such as \"Birds\" matches nothing upstream, so only the listed values are accepted.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "Actinopterygii",
          "Amphibia",
          "Animalia",
          "Arachnida",
          "Aves",
          "Chromista",
          "Fungi",
          "Insecta",
          "Mammalia",
          "Mollusca",
          "Plantae",
          "Protozoa",
          "Reptilia",
          "unknown"
        ]
      }
    },
    "hrank": {
      "description": "Highest taxonomic rank of the identification to accept.",
      "type": "string",
      "enum": [
        "stateofmatter",
        "kingdom",
        "phylum",
        "subphylum",
        "superclass",
        "class",
        "subclass",
        "superorder",
        "order",
        "suborder",
        "infraorder",
        "superfamily",
        "epifamily",
        "family",
        "subfamily",
        "supertribe",
        "tribe",
        "subtribe",
        "genus",
        "genushybrid",
        "species",
        "hybrid",
        "subspecies",
        "variety",
        "form"
      ]
    },
    "lrank": {
      "description": "Lowest taxonomic rank of the identification to accept.",
      "type": "string",
      "enum": [
        "stateofmatter",
        "kingdom",
        "phylum",
        "subphylum",
        "superclass",
        "class",
        "subclass",
        "superorder",
        "order",
        "suborder",
        "infraorder",
        "superfamily",
        "epifamily",
        "family",
        "subfamily",
        "supertribe",
        "tribe",
        "subtribe",
        "genus",
        "genushybrid",
        "species",
        "hybrid",
        "subspecies",
        "variety",
        "form"
      ]
    },
    "csi": {
      "description": "IUCN-normalised conservation status codes to include, e.g. [\"EN\",\"CR\"]. Decode them with inaturalist_list_reference topic conservation_status_codes.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "LC",
          "NT",
          "VU",
          "EN",
          "CR",
          "EW",
          "EX"
        ]
      }
    },
    "threatened": {
      "description": "Restrict to taxa considered threatened where observed.",
      "type": "boolean"
    },
    "native": {
      "description": "Restrict to taxa native to the observation location.",
      "type": "boolean"
    },
    "introduced": {
      "description": "Restrict to taxa introduced to the observation location.",
      "type": "boolean"
    },
    "endemic": {
      "description": "Restrict to taxa endemic to the observation location.",
      "type": "boolean"
    },
    "licensed": {
      "description": "Restrict to records whose own license_code is not null.",
      "type": "boolean"
    },
    "photo_licensed": {
      "description": "Restrict to records with at least one licensed photo.",
      "type": "boolean"
    },
    "q": {
      "description": "Free text matched across observation properties.",
      "type": "string",
      "minLength": 1
    },
    "search_on": {
      "description": "Narrow what q matches against. Requires q.",
      "type": "string",
      "enum": [
        "names",
        "tags",
        "description",
        "place"
      ]
    },
    "order_by": {
      "default": "observed_on",
      "description": "Sort field. Forced to id when cursor is supplied, since a cursor only continues an id ordering.",
      "type": "string",
      "enum": [
        "created_at",
        "geo_score",
        "id",
        "observed_on",
        "random",
        "species_guess",
        "updated_at",
        "votes"
      ]
    },
    "order": {
      "default": "desc",
      "description": "Sort direction.",
      "type": "string",
      "enum": [
        "asc",
        "desc"
      ]
    },
    "page": {
      "description": "Page number within the first 10,000 results. Defaults to 1. Mutually exclusive with cursor.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "cursor": {
      "description": "next_cursor from a previous page, to continue past the 10,000-result window — a positive integer observation id, sent upstream as id_below. Mutually exclusive with page, and forces an id ordering.",
      "type": "string",
      "pattern": "^[1-9]\\d*$"
    },
    "per_page": {
      "default": 10,
      "description": "Records per page, maximum 25. A projected record costs roughly 1.9 KB across structuredContent and the rendered text together, so 25 is a full page near 49 KB and the default of 10 near 20 KB. Walk further with page or cursor rather than a larger page.",
      "type": "integer",
      "minimum": 1,
      "maximum": 25
    },
    "include": {
      "description": "Embedded arrays to expand per record. Check photo_count and sound_count first — expanding costs context.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "photos",
          "annotations",
          "sounds"
        ]
      }
    }
  },
  "required": [
    "quality_grade",
    "captive",
    "order_by",
    "order",
    "per_page"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_get_observation

open-world

Fetch up to 10 observations by id with their community identification thread — who identified what, whether each identification agrees, and the consensus taxon the community landed on. The whole batch costs one upstream request, so resolving ten ids here is far cheaper than ten separate lookups. A missing id is reported per id in unresolved rather than failing the batch; the call fails only when nothing resolved.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_get_observation",
    "arguments": {
      "observation_id": "<observation_id>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "observation_id": {
      "minItems": 1,
      "maxItems": 10,
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991
      },
      "description": "Observation ids to fetch, 1 to 10. Find current ids for an area with inaturalist_search_observations."
    },
    "include": {
      "default": [
        "identifications"
      ],
      "description": "Embedded arrays to expand per record. identifications is the default and is what carries the thread; the others cost context, so check photo_count and sound_count first.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "identifications",
          "comments",
          "photos",
          "annotations",
          "sounds"
        ]
      }
    }
  },
  "required": [
    "observation_id",
    "include"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_get_species_counts

open-world

Rank the distinct species recorded in an area and period, most-observed first — the "what lives here" answer, without paging through individual sightings. An area is given in exactly one form: place_id, the lat/lng/radius triple in kilometres, or a four-corner bounding box. Narrow to a clade by passing taxon_id, e.g. the birds of a park. Defaults to research-grade, wild-only records and echoes those defaults back. For the most active people rather than the most recorded species, use inaturalist_get_leaderboard.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_get_species_counts",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "place_id": {
      "description": "Numeric iNaturalist place id from inaturalist_find_places. Mutually exclusive with the lat/lng/radius triple and the bounding box. A non-numeric value answers HTTP 500 upstream.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "lat": {
      "description": "Latitude of the search centre, in decimal degrees. Requires lng and radius.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "lng": {
      "description": "Longitude of the search centre, in decimal degrees. Requires lat and radius.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "radius": {
      "description": "Search radius around lat/lng, in KILOMETRES. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes.",
      "type": "number",
      "minimum": 0,
      "maximum": 500
    },
    "nelat": {
      "description": "North-east corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "nelng": {
      "description": "North-east corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "swlat": {
      "description": "South-west corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "swlng": {
      "description": "South-west corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "taxon_id": {
      "description": "Restrict to this taxon and its descendants. Resolve a name to an id with inaturalist_resolve_name.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "d1": {
      "description": "Earliest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "d2": {
      "description": "Latest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "quality_grade": {
      "default": [
        "research"
      ],
      "description": "Identification confidence tiers to include. Defaults to research-grade only; adding \"needs_id\" roughly doubles the corpus and lowers identification confidence.",
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "research",
          "needs_id",
          "casual"
        ]
      }
    },
    "captive": {
      "default": false,
      "description": "Whether to include captive and cultivated records — zoo animals, garden plantings. Defaults to wild organisms only.",
      "type": "boolean"
    },
    "term_id": {
      "description": "Annotation attribute ids, from inaturalist_list_reference topic controlled_terms — e.g. 1 for Life Stage.",
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991
      }
    },
    "term_value_id": {
      "description": "Annotation value ids, from the same attribute listing — e.g. 6 for Larva. Requires term_id; sent alone it is ignored upstream and the unfiltered corpus comes back.",
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991
      }
    },
    "iconic_taxa": {
      "description": "Broad organism groups, by their scientific iconic-taxon name. A common-name value such as \"Birds\" matches nothing upstream, so only the listed values are accepted.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "Actinopterygii",
          "Amphibia",
          "Animalia",
          "Arachnida",
          "Aves",
          "Chromista",
          "Fungi",
          "Insecta",
          "Mammalia",
          "Mollusca",
          "Plantae",
          "Protozoa",
          "Reptilia",
          "unknown"
        ]
      }
    },
    "page": {
      "default": 1,
      "description": "Page number. Defaults to 1.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "per_page": {
      "default": 25,
      "description": "Species per page, maximum 50. A ranked species costs roughly 860 bytes across structuredContent and the rendered text together, so 50 is a full page near 43 KB. Upstream would serve 500 in one page — raise page rather than asking for it.",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    }
  },
  "required": [
    "quality_grade",
    "captive",
    "page",
    "per_page"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_get_histogram

open-world

Build a phenology histogram for a taxon in an area — which months, weeks, or years it is recorded in. The default month_of_year interval answers "when does this bloom or appear here" in twelve buckets; the absolute intervals (year, month, week, day, hour) bucket real dates and upstream applies a default start date to them. An area is given in exactly one form: place_id, the lat/lng/radius triple in kilometres, or a four-corner bounding box. Omit taxon_id to chart every taxon in the area. Defaults to research-grade, wild-only records and echoes those defaults back.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_get_histogram",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "taxon_id": {
      "description": "Restrict to this taxon and its descendants. Omit to chart every taxon in the area. Resolve a name to an id with inaturalist_resolve_name.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "place_id": {
      "description": "Numeric iNaturalist place id from inaturalist_find_places. Mutually exclusive with the lat/lng/radius triple and the bounding box. A non-numeric value answers HTTP 500 upstream.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "lat": {
      "description": "Latitude of the search centre, in decimal degrees. Requires lng and radius.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "lng": {
      "description": "Longitude of the search centre, in decimal degrees. Requires lat and radius.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "radius": {
      "description": "Search radius around lat/lng, in KILOMETRES. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes.",
      "type": "number",
      "minimum": 0,
      "maximum": 500
    },
    "nelat": {
      "description": "North-east corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "nelng": {
      "description": "North-east corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "swlat": {
      "description": "South-west corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "swlng": {
      "description": "South-west corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "interval": {
      "default": "month_of_year",
      "description": "Bucketing. month_of_year and week_of_year fold every year together into a seasonal curve; the rest bucket absolute dates. day and hour over a wide date range can generate thousands of buckets — the response is capped at 800, kept from the start of the range; narrow d1/d2 or use a coarser interval to see the rest.",
      "type": "string",
      "enum": [
        "year",
        "month",
        "week",
        "day",
        "hour",
        "month_of_year",
        "week_of_year"
      ]
    },
    "date_field": {
      "default": "observed",
      "description": "Which date to bucket by: when the organism was observed, or when the record was uploaded.",
      "type": "string",
      "enum": [
        "observed",
        "created"
      ]
    },
    "d1": {
      "description": "Earliest observation date, YYYY-MM-DD. Inclusive. With interval set to day or hour, a wide range can exceed the 800-bucket cap — narrow d1/d2 to reach buckets past it.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "d2": {
      "description": "Latest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "quality_grade": {
      "default": [
        "research"
      ],
      "description": "Identification confidence tiers to include. Defaults to research-grade only; adding \"needs_id\" roughly doubles the corpus and lowers identification confidence.",
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "research",
          "needs_id",
          "casual"
        ]
      }
    },
    "captive": {
      "default": false,
      "description": "Whether to include captive and cultivated records — zoo animals, garden plantings. Defaults to wild organisms only.",
      "type": "boolean"
    }
  },
  "required": [
    "interval",
    "date_field",
    "quality_grade",
    "captive"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_get_leaderboard

open-world

Rank the most active observers or identifiers for an area, period, and taxon — who knows this place or this group. kind selects which: observers are ranked by how many observations they recorded, identifiers by how many identifications they made. An area is given in exactly one form: place_id, the lat/lng/radius triple in kilometres, or a four-corner bounding box. Both endpoints rank only the top 500 entries, so page multiplied by per_page must stay at or below 500 — narrow the area, period, or taxon to bring someone further down into reach. For the most-recorded species rather than the most active people, use inaturalist_get_species_counts.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_get_leaderboard",
    "arguments": {
      "kind": "<kind>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "kind": {
      "type": "string",
      "enum": [
        "observers",
        "identifiers"
      ],
      "description": "Which leaderboard: \"observers\" ranks by observations recorded, \"identifiers\" by identifications made."
    },
    "place_id": {
      "description": "Numeric iNaturalist place id from inaturalist_find_places. Mutually exclusive with the lat/lng/radius triple and the bounding box. A non-numeric value answers HTTP 500 upstream.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "lat": {
      "description": "Latitude of the search centre, in decimal degrees. Requires lng and radius.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "lng": {
      "description": "Longitude of the search centre, in decimal degrees. Requires lat and radius.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "radius": {
      "description": "Search radius around lat/lng, in KILOMETRES. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes.",
      "type": "number",
      "minimum": 0,
      "maximum": 500
    },
    "nelat": {
      "description": "North-east corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "nelng": {
      "description": "North-east corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "swlat": {
      "description": "South-west corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "swlng": {
      "description": "South-west corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "taxon_id": {
      "description": "Restrict to this taxon and its descendants. Resolve a name to an id with inaturalist_resolve_name.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "d1": {
      "description": "Earliest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "d2": {
      "description": "Latest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "quality_grade": {
      "default": [
        "research"
      ],
      "description": "Identification confidence tiers to include. Defaults to research-grade only; adding \"needs_id\" roughly doubles the corpus and lowers identification confidence.",
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "research",
          "needs_id",
          "casual"
        ]
      }
    },
    "page": {
      "default": 1,
      "description": "Page number. Defaults to 1.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "per_page": {
      "default": 25,
      "description": "Entries per page, maximum 250. An entry costs roughly 140 bytes across structuredContent and the rendered text together, so 250 is a full page near 34 KB — and two such pages cover the whole 500-entry window these endpoints rank.",
      "type": "integer",
      "minimum": 1,
      "maximum": 250
    }
  },
  "required": [
    "kind",
    "quality_grade",
    "page",
    "per_page"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_get_similar_species

open-world

List the taxa this one is most often misidentified as, ranked by how many times identifiers made the correction — the field-identification check before committing to a look-alike. Scope it to an area in exactly one form (place_id, the lat/lng/radius triple in kilometres, or a four-corner bounding box) to see the confusion set a specific region actually produces, or leave the area off for the global set. Resolve the organism name to a taxon id with inaturalist_resolve_name first.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_get_similar_species",
    "arguments": {
      "taxon_id": "<taxon_id>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "taxon_id": {
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991,
      "description": "Numeric taxon id to find look-alikes for. Resolve a name to an id with inaturalist_resolve_name."
    },
    "place_id": {
      "description": "Numeric iNaturalist place id from inaturalist_find_places. Mutually exclusive with the lat/lng/radius triple and the bounding box. A non-numeric value answers HTTP 500 upstream.",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "lat": {
      "description": "Latitude of the search centre, in decimal degrees. Requires lng and radius.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "lng": {
      "description": "Longitude of the search centre, in decimal degrees. Requires lat and radius.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "radius": {
      "description": "Search radius around lat/lng, in KILOMETRES. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes.",
      "type": "number",
      "minimum": 0,
      "maximum": 500
    },
    "nelat": {
      "description": "North-east corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "nelng": {
      "description": "North-east corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "swlat": {
      "description": "South-west corner latitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -90,
      "maximum": 90
    },
    "swlng": {
      "description": "South-west corner longitude of the bounding box. All four corners or none.",
      "type": "number",
      "minimum": -180,
      "maximum": 180
    },
    "d1": {
      "description": "Earliest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "d2": {
      "description": "Latest observation date, YYYY-MM-DD. Inclusive.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "quality_grade": {
      "default": [
        "research"
      ],
      "description": "Identification confidence tiers to include. Defaults to research-grade only; adding \"needs_id\" roughly doubles the corpus and lowers identification confidence.",
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "research",
          "needs_id",
          "casual"
        ]
      }
    },
    "captive": {
      "default": false,
      "description": "Whether to include captive and cultivated records — zoo animals, garden plantings. Defaults to wild organisms only.",
      "type": "boolean"
    },
    "limit": {
      "default": 20,
      "description": "Maximum look-alikes to return. Applied in-process — the upstream endpoint publishes no page size and returns its whole confusion set.",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    }
  },
  "required": [
    "taxon_id",
    "quality_grade",
    "captive",
    "limit"
  ],
  "additionalProperties": false
}
view source ↗

inaturalist_get_taxon

open-world

Fetch a taxon profile: the taxonomic path, per-authority conservation listings, the encyclopedia summary, the photo gallery, immediate children, and observation counts. Resolve a name to a taxon id with inaturalist_resolve_name first. The upstream record is 95 KB for a common species, so it is projected before anything else happens; a taxon that still overflows comes back as an outline of its sections with their byte sizes, and naming those sections in a re-call returns only those. The valid section names are summary, taxonomy, children, conservation, photos, and encyclopedia.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inaturalist_get_taxon",
    "arguments": {
      "taxon_id": "<taxon_id>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "taxon_id": {
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991,
      "description": "Numeric taxon id. A non-numeric value answers HTTP 422 with an empty message upstream, so the integer is enforced here. Resolve a name to an id with inaturalist_resolve_name."
    },
    "sections": {
      "description": "Sections to return: summary, taxonomy, children, conservation, photos, encyclopedia. Omit for the whole profile, or an outline of it when it overflows. A selection returns whatever it names, at whatever size, so sum the byte sizes from the outline before asking for several.",
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      }
    }
  },
  "required": [
    "taxon_id"
  ],
  "additionalProperties": false
}
view source ↗

Resources

2

Taxon profile by numeric iNaturalist taxon id — taxonomic path, per-authority conservation listings, encyclopedia summary, photo gallery, immediate children, and observation counts. The whole projected document, whatever its size: a resource read has no way to name sections, so use inaturalist_get_taxon when the outline-and-sections path matters.

uri inaturalist://taxa/{taxon_id} mime application/json

One iNaturalist observation by numeric id, with its community identification thread expanded — who identified what, whether each identification agrees, and the consensus taxon. An obscured coordinate is a locality with an accuracy radius, never a sighting position, and a null license_code means all rights reserved.

uri inaturalist://observations/{observation_id} mime application/json