Skip to main content
Glama

inaturalist-mcp-server

Inaturalist Search Observations

inaturalist_search_observations
Read-only

Search georeferenced wildlife sightings by area, date, taxon, quality grade, annotation, conservation status, observer, project, and licence. 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. page walks the first 10,000 results under any ordering; past 10,000, order by id descending (order_by "id", order "desc") and pass each page’s next_cursor as cursor.

Input Schema

NameRequiredDescriptionDefault
qNoFree text matched across observation properties.
d1NoEarliest observation date, YYYY-MM-DD. Inclusive. Must be on or before d2.
d2NoLatest observation date, YYYY-MM-DD. Inclusive.
csiNoIUCN-normalised conservation status codes to include, e.g. ["EN","CR"]. Decode them with inaturalist_list_reference topic conservation_status_codes.
latNoLatitude of the search centre, in decimal degrees. Requires lng and radius.
lngNoLongitude of the search centre, in decimal degrees. Requires lat and radius.
pageNoPage number within the first 10,000 results, under any ordering. Defaults to 1. Mutually exclusive with cursor.
hrankNoHighest (coarsest) taxonomic rank of the identification to accept. Must be at or above lrank; ranks compare by rank level.
lrankNoLowest (finest) taxonomic rank of the identification to accept. Equal to hrank for an exact-rank match.
nelatNoNorth-east corner latitude of the bounding box, at or north of swlat. All four corners or none.
nelngNoNorth-east corner longitude of the bounding box. All four corners or none. West of swlng is accepted: it describes a box crossing the antimeridian.
orderNoSort direction.desc
swlatNoSouth-west corner latitude of the bounding box. All four corners or none.
swlngNoSouth-west corner longitude of the bounding box. All four corners or none.
cursorNonext_cursor from a previous id-descending 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 order_by "id", order "desc".
nativeNoRestrict to taxa native to the observation location.
radiusNoSearch radius around lat/lng, in KILOMETRES, greater than 0. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes.
soundsNoTrue selects observations with sounds; false selects those without. Omit for either. Use include: ["sounds"] to expand sound metadata; no media is downloaded.
captiveNoTrue returns captive and cultivated records only — zoo animals, garden plantings; false returns wild records only. Defaults to false.
endemicNoRestrict to taxa endemic to the observation location.
includeNoEmbedded arrays to expand per record. Check photo_count and sound_count first — expanding costs context.
licenseNoRestrict to records whose own license_code is one of these, e.g. ["cc-by","cc0"] for reuse with attribution only. Decode the codes with inaturalist_list_reference topic licenses; all-rights-reserved records have no code to pass.
term_idNoAnnotation attribute ids, from inaturalist_list_reference topic controlled_terms — e.g. 1 for Life Stage.
user_idNoRestrict to one observer, by numeric user id from inaturalist_resolve_name type user. Mutually exclusive with user_login.
licensedNoRestrict to records whose own license_code is not null — any licence, NonCommercial and NoDerivatives variants included. For specific licences, use license.
order_byNoSort field. Set "id" with order "desc" to walk past 10,000 results: that is the one ordering next_cursor continues, so it is the only one that issues a next_cursor. A cursor forces it.observed_on
per_pageNoRecords fetched per page. Complete records may be deferred to keep the response within its byte budget; retrieve deferred ids through inaturalist_get_observation with the same include values. Ceiling: 25; oversized values are clamped.
place_idNoNumeric 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.
taxon_idNoRestrict to this taxon and its descendants. Resolve a name to an id with inaturalist_resolve_name.
search_onNoNarrow what q matches against. Requires q.
introducedNoRestrict to taxa introduced to the observation location.
project_idNoRestrict to observations in one project, by numeric project id from inaturalist_resolve_name type project.
threatenedNoRestrict to taxa considered threatened where observed.
user_loginNoRestrict to one observer, by login — the login on an inaturalist_resolve_name user candidate, a leaderboard entry, or an observation’s observer. Mutually exclusive with user_id.
iconic_taxaNoBroad 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.
photo_licenseNoRestrict to records carrying at least one photo under one of these licence codes. Matched independently of the record’s own license_code, so check each photo’s license_code before reusing it.
quality_gradeNoIdentification confidence tiers to include, normalized to unique values in first-appearance order. Defaults to research-grade only; adding "needs_id" roughly doubles the corpus and lowers identification confidence.
term_value_idNoAnnotation 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.
photo_licensedNoRestrict to records with at least one licensed photo, under any licence. For specific licences, use photo_license.

Output Schema

NameRequiredDescriptionDefault
capNoThe per_page that was applied.
errorNoPresent when the call failed. Absent on success.
shownNoHow many records this page carries.
noticeNoComposed guidance for applied limits, ignored filters, empty pages, deferred ids and pagination.
deferredNoOrdered ids of the fetched-page suffix omitted from observations to meet the response budget. Empty when the whole page fits; independent of has_more.
has_moreNoTrue when the fetched page filled the applied per_page and more records follow, independent of deferred ids. On the page path an exactly full final page is false; under a cursor, where the offset is unknown, every full page is true.
truncatedNoTrue when records were deferred or more pages follow.
next_cursorNoPass back as cursor to continue past this page. Present only when has_more is true and the page was ordered by id descending; under any other ordering, raise page instead.
observationsNoThe matching sightings, projected.
total_resultsNoHow many records upstream reports as matching. An estimate over a live index — it drifts between calls seconds apart.
applied_filtersNoThe server-applied defaults and overrides that determine what this answer means.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed40 schema fields changed
    • changedInput schema / properties / captive / description
      Previous value: -"Whether to include captive and cultivated records — zoo animals, garden plantings. Defaults to wild organisms only."New value: +"True returns captive and cultivated records only — zoo animals, garden plantings; false returns wild records only. Defaults to false."
    • changedInput schema / properties / per_page / description
      Previous value: -"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."New value: +"Records fetched per page. Complete records may be deferred to keep the response within its byte budget; retrieve deferred ids through inaturalist_get_observation with the same include values. Ceiling: 25; oversized values are clamped."
    • addedInput schema / properties / per_page / exclusiveMinimum
      Added value: +0
    • changedInput schema / properties / per_page / maximum
      Previous value: -25New value: +9007199254740991
    • removedInput schema / properties / per_page / minimum
      Removed value: -1
    • changedInput schema / properties / quality_grade / description
      Previous value: -"Identification confidence tiers to include. Defaults to research-grade only; adding \"needs_id\" roughly doubles the corpus and lowers identification confidence."New value: +"Identification confidence tiers to include, normalized to unique values in first-appearance order. Defaults to research-grade only; adding \"needs_id\" roughly doubles the corpus and lowers identification confidence."
    • addedInput schema / properties / sounds
      Added value: +{
      +  "description": "True selects observations with sounds; false selects those without. Omit for either. Use include: [\"sounds\"] to expand sound metadata; no media is downloaded.",
      +  "type": "boolean"
      +}
    • changedOutput schema / anyOf
      Previous value: -[
      -  {
      -    "not": {
      -      "required": [
      -        "error"
      -      ]
      -    },
      -    "required": [
      -      "total_results",
      -      "observations",
      -      "has_more",
      -      "applied_filters",
      -      "truncated",
      -      "shown",
      -      "cap"
      -    ]
      -  },
      -  {
      -    "required": [
      -      "error"
      -    ]
      -  }
      -]New value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "total_results",
      +      "observations",
      +      "deferred",
      +      "has_more",
      +      "applied_filters",
      +      "truncated",
      +      "shown",
      +      "cap"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • changedOutput schema / properties / applied_filters / properties / captive / description
      Previous value: -"Whether captive and cultivated records were included."New value: +"Captive-only when true, wild-only when false."
    • changedOutput schema / properties / applied_filters / properties / quality_grade / description
      Previous value: -"Identification tiers actually searched."New value: +"Identification tiers actually searched, normalized to unique values in first-appearance order."
    • addedOutput schema / properties / deferred
      Added value: +{
      +  "description": "Ordered ids of the fetched-page suffix omitted from observations to meet the response budget. Empty when the whole page fits; independent of has_more.",
      +  "items": {
      +    "additionalProperties": false,
      +    "description": "One fetched observation deferred for a by-id call.",
      +    "properties": {
      +      "observation_id": {
      +        "description": "Observation id to retrieve through inaturalist_get_observation with the same include values.",
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "observation_id"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `invalid_geography`: An area was given partially, in two forms at once, with a radius of 0 or less, or with nelat south of swlat. `inverted_date_range`: d1 is after d2. `inverted_rank_range`: hrank is a finer rank than lrank. `result_window_exceeded`: page multiplied by per_page would reach past the upstream 10,000-result window. `conflicting_pagination`: Both page and cursor were supplied. `unpaired_annotation_value`: term_value_id was supplied without term_id. `search_on_without_query`: search_on was supplied without q. `conflicting_observer`: user_id and user_login were both supplied. `unknown_taxon_id`: iNaturalist answered 422 because the taxon_id does not exist. `unknown_user`: iNaturalist answered 422 because the user_id or user_login names no observer. `unknown_project_id`: iNaturalist answered 422 because the project_id does not exist. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `unknown_place_id`: iNaturalist answered 422 because place_id does not exist. `invalid_geography`: An area was given partially, in two forms at once, with a radius of 0 or less, or with nelat south of swlat. `inverted_date_range`: d1 is after d2. `inverted_rank_range`: hrank is a finer rank than lrank. `result_window_exceeded`: page multiplied by per_page would reach past the upstream 10,000-result window. `conflicting_pagination`: Both page and cursor were supplied. `unpaired_annotation_value`: term_value_id was supplied without term_id. `search_on_without_query`: search_on was supplied without q. `conflicting_observer`: user_id and user_login were both supplied. `unknown_taxon_id`: iNaturalist answered 422 because the taxon_id does not exist. `unknown_user`: iNaturalist answered 422 because the user_id or user_login names no observer. `unknown_project_id`: iNaturalist answered 422 because the project_id does not exist. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "invalid_geography",
      -  "inverted_date_range",
      -  "inverted_rank_range",
      -  "result_window_exceeded",
      -  "conflicting_pagination",
      -  "unpaired_annotation_value",
      -  "search_on_without_query",
      -  "conflicting_observer",
      -  "unknown_taxon_id",
      -  "unknown_user",
      -  "unknown_project_id"
      -]New value: +[
      +  "unknown_place_id",
      +  "invalid_geography",
      +  "inverted_date_range",
      +  "inverted_rank_range",
      +  "result_window_exceeded",
      +  "conflicting_pagination",
      +  "unpaired_annotation_value",
      +  "search_on_without_query",
      +  "conflicting_observer",
      +  "unknown_taxon_id",
      +  "unknown_user",
      +  "unknown_project_id"
      +]
    • changedOutput schema / properties / has_more / description
      Previous value: -"True when this page filled per_page and more records follow. On the page path an exactly full final page is false; under a cursor, where the offset is unknown, every full page is true."New value: +"True when the fetched page filled the applied per_page and more records follow, independent of deferred ids. On the page path an exactly full final page is false; under a cursor, where the offset is unknown, every full page is true."
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance when nothing matched, when the page is past the last one holding results, or how to continue past a full page."New value: +"Composed guidance for applied limits, ignored filters, empty pages, deferred ids and pagination."
    • changedOutput schema / properties / observations / items / description
      Previous value: -"One projected observation record."New value: +"One complete projected search record, with requested media expansions."
    • changedOutput schema / properties / observations / items / properties / annotations / items / properties / attribute / description
      Previous value: -"Decoded attribute label, e.g. \"Life Stage\". Null when the id is not in the vocabulary."New value: +"Attribute label, e.g. \"Life Stage\"; null for an unknown id."
    • changedOutput schema / properties / observations / items / properties / annotations / items / properties / value / description
      Previous value: -"Decoded value label, e.g. \"Larva\". Null when the id is not in the vocabulary."New value: +"Value label, e.g. \"Larva\"; null for an unknown id."
    • removedOutput schema / properties / observations / items / properties / comments
      Removed value: -{
      -  "description": "Discussion comments, cut to their first entries in upstream order (not strictly chronological) when they exceed the per-record share of a 40-entry budget. Present when \"comments\" was included.",
      -  "items": {
      -    "additionalProperties": false,
      -    "description": "One discussion comment on the record.",
      -    "properties": {
      -      "body": {
      -        "description": "Free-text comment body.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "by": {
      -        "description": "Login of the commenter.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "created_at": {
      -        "description": "When the comment was posted, ISO 8601.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "id": {
      -        "description": "Comment id.",
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "by",
      -      "body",
      -      "created_at"
      -    ],
      -    "type": "object"
      -  },
      -  "type": "array"
      -}
    • removedOutput schema / properties / observations / items / properties / comments_shown
      Removed value: -{
      -  "description": "How many of them comments carries. Below comments_total when the thread was cut. Present when \"comments\" was included.",
      -  "type": "number"
      -}
    • removedOutput schema / properties / observations / items / properties / comments_total
      Removed value: -{
      -  "description": "How many comments upstream holds on the record. Present when \"comments\" was included.",
      -  "type": "number"
      -}
    • removedOutput schema / properties / observations / items / properties / community_taxon
      Removed value: -{
      -  "anyOf": [
      -    {
      -      "additionalProperties": false,
      -      "properties": {
      -        "common_name": {
      -          "description": "Preferred common name, when one is recorded.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "iconic_taxon_name": {
      -          "description": "Broad organism group, e.g. \"Insecta\". Usable as an iconic_taxa filter value.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "id": {
      -          "description": "Taxon id — the identifier every taxon-scoped filter takes.",
      -          "type": "number"
      -        },
      -        "name": {
      -          "description": "Scientific name.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "rank": {
      -          "description": "Taxonomic rank, e.g. \"species\".",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        }
      -      },
      -      "required": [
      -        "id",
      -        "name",
      -        "rank",
      -        "common_name",
      -        "iconic_taxon_name"
      -      ],
      -      "type": "object"
      -    },
      -    {
      -      "type": "null"
      -    }
      -  ],
      -  "description": "Consensus taxon, resolved. Present on the by-id tool; null when the thread has reached none."
      -}
    • changedOutput schema / properties / observations / items / properties / coordinate / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "accuracy_m": {
      -        "description": "Public positional accuracy radius in metres. On an obscured record this is tens of kilometres and the point is a locality, not a sighting position.",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "lat": {
      -        "description": "Latitude in decimal degrees.",
      -        "type": "number"
      -      },
      -      "lng": {
      -        "description": "Longitude in decimal degrees.",
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "lat",
      -      "lng",
      -      "accuracy_m"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "accuracy_m": {
      +        "description": "Public accuracy radius, metres. An obscured point is a locality, not a sighting position.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "lat": {
      +        "description": "Latitude in decimal degrees.",
      +        "type": "number"
      +      },
      +      "lng": {
      +        "description": "Longitude in decimal degrees.",
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "lat",
      +      "lng",
      +      "accuracy_m"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • removedOutput schema / properties / observations / items / properties / description
      Removed value: -{
      -  "description": "The observer’s own note on the sighting — host plant, behaviour, habitat, count. Third-party free text. Present on the by-id tool; null when the observer wrote none.",
      -  "type": [
      -    "string",
      -    "null"
      -  ]
      -}
    • removedOutput schema / properties / observations / items / properties / identification_disagreements_count
      Removed value: -{
      -  "description": "Count of disagreeing identifications as upstream tallies it. Present on the by-id tool.",
      -  "type": "number"
      -}
    • removedOutput schema / properties / observations / items / properties / identifications
      Removed value: -{
      -  "description": "The identification thread, cut to its first entries in upstream order (roughly but not strictly chronological) when it exceeds the per-record share of a 40-entry budget. Present when \"identifications\" was included.",
      -  "items": {
      -    "additionalProperties": false,
      -    "description": "One identification in the community thread.",
      -    "properties": {
      -      "body": {
      -        "description": "Free-text note written by the identifier.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "by": {
      -        "description": "Login of the identifier.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "category": {
      -        "description": "How this identification moved the thread: improving, supporting, leading, or maverick.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "created_at": {
      -        "description": "When the identification was added, ISO 8601.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "current": {
      -        "description": "True when this is the identifier’s current identification on the record.",
      -        "type": "boolean"
      -      },
      -      "disagreement": {
      -        "description": "True when the identifier explicitly disagreed with the preceding taxon.",
      -        "type": [
      -          "boolean",
      -          "null"
      -        ]
      -      },
      -      "from_vision": {
      -        "description": "True when the identification came from the upstream image classifier.",
      -        "type": "boolean"
      -      },
      -      "id": {
      -        "description": "Identification id.",
      -        "type": "number"
      -      },
      -      "taxon": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "properties": {
      -              "common_name": {
      -                "description": "Preferred common name, when one is recorded.",
      -                "type": [
      -                  "string",
      -                  "null"
      -                ]
      -              },
      -              "iconic_taxon_name": {
      -                "description": "Broad organism group, e.g. \"Insecta\". Usable as an iconic_taxa filter value.",
      -                "type": [
      -                  "string",
      -                  "null"
      -                ]
      -              },
      -              "id": {
      -                "description": "Taxon id — the identifier every taxon-scoped filter takes.",
      -                "type": "number"
      -              },
      -              "name": {
      -                "description": "Scientific name.",
      -                "type": [
      -                  "string",
      -                  "null"
      -                ]
      -              },
      -              "rank": {
      -                "description": "Taxonomic rank, e.g. \"species\".",
      -                "type": [
      -                  "string",
      -                  "null"
      -                ]
      -              }
      -            },
      -            "required": [
      -              "id",
      -              "name",
      -              "rank",
      -              "common_name",
      -              "iconic_taxon_name"
      -            ],
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "Taxon this identification proposes."
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "taxon",
      -      "by",
      -      "current",
      -      "category",
      -      "disagreement",
      -      "from_vision",
      -      "body",
      -      "created_at"
      -    ],
      -    "type": "object"
      -  },
      -  "type": "array"
      -}
    • changedOutput schema / properties / observations / items / properties / identifications_count / description
      Previous value: -"Upstream’s tally of identifications currently agreeing or disagreeing with the community taxon — agreements + disagreements. Not the thread size: it leaves out the observer’s own identification and any that neither agrees nor disagrees, such as a coarser or withdrawn one. The thread size is identifications_total, on inaturalist_get_observation."New value: +"Tally against the community taxon: agreements + disagreements. Excludes the observer and neutral/withdrawn ids; thread size is identifications_total on inaturalist_get_observation."
    • removedOutput schema / properties / observations / items / properties / identifications_shown
      Removed value: -{
      -  "description": "How many of them identifications carries. Below identifications_total when the thread was cut. Present when \"identifications\" was included.",
      -  "type": "number"
      -}
    • removedOutput schema / properties / observations / items / properties / identifications_total
      Removed value: -{
      -  "description": "How many identifications upstream holds on the record — the thread size. Present when \"identifications\" was included.",
      -  "type": "number"
      -}
    • changedOutput schema / properties / observations / items / properties / obscured / description
      Previous value: -"True when the true coordinate is withheld, typically for a threatened taxon. The point is never resolved or approximated."New value: +"True when the true coordinate is withheld; the public point is a locality."
    • removedOutput schema / properties / observations / items / properties / observation_fields
      Removed value: -{
      -  "description": "Observation-field values filled in on the record, usually by a project; fields left blank are dropped. Cut to the first filled fields in upstream order when they exceed the per-record share of a 40-entry budget. Present on the by-id tool.",
      -  "items": {
      -    "additionalProperties": false,
      -    "description": "One filled observation field.",
      -    "properties": {
      -      "name": {
      -        "description": "Field name, as its creator wrote it, e.g. \"Habitat_Description\".",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "value": {
      -        "description": "The value filled in, verbatim.",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "name",
      -      "value"
      -    ],
      -    "type": "object"
      -  },
      -  "type": "array"
      -}
    • removedOutput schema / properties / observations / items / properties / observation_fields_shown
      Removed value: -{
      -  "description": "How many of them observation_fields carries. Below observation_fields_total when the list was cut. Present on the by-id tool.",
      -  "type": "number"
      -}
    • removedOutput schema / properties / observations / items / properties / observation_fields_total
      Removed value: -{
      -  "description": "How many filled observation fields the record carries. Present on the by-id tool.",
      -  "type": "number"
      -}
    • changedOutput schema / properties / observations / items / properties / observer / description
      Previous value: -"Login of the observer. The rest of the upstream profile is not relayed."New value: +"Observer login."
    • changedOutput schema / properties / observations / items / properties / photo / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "A photo with the attribution and licence that must travel with it.",
      -    "properties": {
      -      "attribution": {
      -        "description": "Attribution string, verbatim and never reformatted. Reproduce it with any use of the image.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "license_code": {
      -        "description": "Photo licence code. Null means all rights reserved, independent of the record.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "medium_url": {
      -        "description": "Medium variant. Supplied by upstream where it publishes every size, otherwise derived by substituting the size qualifier in the path. Absent when the path did not match the documented shape.",
      -        "type": "string"
      -      },
      -      "open": {
      -        "description": "True when the photo is served from the host the upstream reserves for open licences. A photo can move hosts when its licence changes, so this reflects fetch time.",
      -        "type": "boolean"
      -      },
      -      "square_url": {
      -        "description": "75px square variant, exactly as upstream published it. Null when absent.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "square_url",
      -      "attribution",
      -      "license_code",
      -      "open"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "A photo with the attribution and licence that must travel with it.",
      +    "properties": {
      +      "attribution": {
      +        "description": "Verbatim attribution; reproduce with any use of the image.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "license_code": {
      +        "description": "Photo licence code. Null means all rights reserved, independent of the record.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "medium_url": {
      +        "description": "Medium image URL; derived from the size path if needed. Absent when no variant is available.",
      +        "type": "string"
      +      },
      +      "open": {
      +        "description": "True when served from the open-licence host at fetch time; licences can change.",
      +        "type": "boolean"
      +      },
      +      "square_url": {
      +        "description": "75px square variant, exactly as upstream published it. Null when absent.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "square_url",
      +      "attribution",
      +      "license_code",
      +      "open"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedOutput schema / properties / observations / items / properties / photo_count / description
      Previous value: -"How many photos the record carries, so include \"photos\" can be spent knowingly."New value: +"Photo count; include \"photos\" for every photo."
    • changedOutput schema / properties / observations / items / properties / photos / items / properties / attribution / description
      Previous value: -"Attribution string, verbatim and never reformatted. Reproduce it with any use of the image."New value: +"Verbatim attribution; reproduce with any use of the image."
    • changedOutput schema / properties / observations / items / properties / photos / items / properties / medium_url / description
      Previous value: -"Medium variant. Supplied by upstream where it publishes every size, otherwise derived by substituting the size qualifier in the path. Absent when the path did not match the documented shape."New value: +"Medium image URL; derived from the size path if needed. Absent when no variant is available."
    • changedOutput schema / properties / observations / items / properties / photos / items / properties / open / description
      Previous value: -"True when the photo is served from the host the upstream reserves for open licences. A photo can move hosts when its licence changes, so this reflects fetch time."New value: +"True when served from the open-licence host at fetch time; licences can change."
    • changedOutput schema / properties / truncated / description
      Previous value: -"True when the page filled per_page and more records follow."New value: +"True when records were deferred or more pages follow."
  2. Changed15 schema fields changed
    • changedInput schema / properties / cursor / description
      Previous value: -"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."New value: +"next_cursor from a previous id-descending 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 order_by \"id\", order \"desc\"."
    • addedInput schema / properties / license
      Added value: +{
      +  "description": "Restrict to records whose own license_code is one of these, e.g. [\"cc-by\",\"cc0\"] for reuse with attribution only. Decode the codes with inaturalist_list_reference topic licenses; all-rights-reserved records have no code to pass.",
      +  "items": {
      +    "enum": [
      +      "cc-by",
      +      "cc-by-nc",
      +      "cc-by-nd",
      +      "cc-by-sa",
      +      "cc-by-nc-nd",
      +      "cc-by-nc-sa",
      +      "cc0"
      +    ],
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • changedInput schema / properties / licensed / description
      Previous value: -"Restrict to records whose own license_code is not null."New value: +"Restrict to records whose own license_code is not null — any licence, NonCommercial and NoDerivatives variants included. For specific licences, use license."
    • changedInput schema / properties / order_by / description
      Previous value: -"Sort field. Forced to id when cursor is supplied, since a cursor only continues an id ordering."New value: +"Sort field. Set \"id\" with order \"desc\" to walk past 10,000 results: that is the one ordering next_cursor continues, so it is the only one that issues a next_cursor. A cursor forces it."
    • changedInput schema / properties / page / description
      Previous value: -"Page number within the first 10,000 results. Defaults to 1. Mutually exclusive with cursor."New value: +"Page number within the first 10,000 results, under any ordering. Defaults to 1. Mutually exclusive with cursor."
    • addedInput schema / properties / photo_license
      Added value: +{
      +  "description": "Restrict to records carrying at least one photo under one of these licence codes. Matched independently of the record’s own license_code, so check each photo’s license_code before reusing it.",
      +  "items": {
      +    "enum": [
      +      "cc-by",
      +      "cc-by-nc",
      +      "cc-by-nd",
      +      "cc-by-sa",
      +      "cc-by-nc-nd",
      +      "cc-by-nc-sa",
      +      "cc0"
      +    ],
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • changedInput schema / properties / photo_licensed / description
      Previous value: -"Restrict to records with at least one licensed photo."New value: +"Restrict to records with at least one licensed photo, under any licence. For specific licences, use photo_license."
    • addedInput schema / properties / project_id
      Added value: +{
      +  "description": "Restrict to observations in one project, by numeric project id from inaturalist_resolve_name type project.",
      +  "maximum": 9007199254740991,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • addedInput schema / properties / user_id
      Added value: +{
      +  "description": "Restrict to one observer, by numeric user id from inaturalist_resolve_name type user. Mutually exclusive with user_login.",
      +  "maximum": 9007199254740991,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • addedInput schema / properties / user_login
      Added value: +{
      +  "description": "Restrict to one observer, by login — the login on an inaturalist_resolve_name user candidate, a leaderboard entry, or an observation’s observer. Mutually exclusive with user_id.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `invalid_geography`: An area was given partially, in two forms at once, with a radius of 0 or less, or with nelat south of swlat. `inverted_date_range`: d1 is after d2. `inverted_rank_range`: hrank is a finer rank than lrank. `result_window_exceeded`: page multiplied by per_page would reach past the upstream 10,000-result window. `conflicting_pagination`: Both page and cursor were supplied. `unpaired_annotation_value`: term_value_id was supplied without term_id. `search_on_without_query`: search_on was supplied without q. `unknown_taxon_id`: iNaturalist answered 422 because the taxon_id does not exist. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_geography`: An area was given partially, in two forms at once, with a radius of 0 or less, or with nelat south of swlat. `inverted_date_range`: d1 is after d2. `inverted_rank_range`: hrank is a finer rank than lrank. `result_window_exceeded`: page multiplied by per_page would reach past the upstream 10,000-result window. `conflicting_pagination`: Both page and cursor were supplied. `unpaired_annotation_value`: term_value_id was supplied without term_id. `search_on_without_query`: search_on was supplied without q. `conflicting_observer`: user_id and user_login were both supplied. `unknown_taxon_id`: iNaturalist answered 422 because the taxon_id does not exist. `unknown_user`: iNaturalist answered 422 because the user_id or user_login names no observer. `unknown_project_id`: iNaturalist answered 422 because the project_id does not exist. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "invalid_geography",
      -  "inverted_date_range",
      -  "inverted_rank_range",
      -  "result_window_exceeded",
      -  "conflicting_pagination",
      -  "unpaired_annotation_value",
      -  "search_on_without_query",
      -  "unknown_taxon_id"
      -]New value: +[
      +  "invalid_geography",
      +  "inverted_date_range",
      +  "inverted_rank_range",
      +  "result_window_exceeded",
      +  "conflicting_pagination",
      +  "unpaired_annotation_value",
      +  "search_on_without_query",
      +  "conflicting_observer",
      +  "unknown_taxon_id",
      +  "unknown_user",
      +  "unknown_project_id"
      +]
    • changedOutput schema / properties / has_more / description
      Previous value: -"True when this page filled per_page, so more records follow."New value: +"True when this page filled per_page and more records follow. On the page path an exactly full final page is false; under a cursor, where the offset is unknown, every full page is true."
    • changedOutput schema / properties / next_cursor / description
      Previous value: -"Pass back as cursor to continue past this page. Absent when has_more is false."New value: +"Pass back as cursor to continue past this page. Present only when has_more is true and the page was ordered by id descending; under any other ordering, raise page instead."
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance when nothing matched, or how to continue past a full page."New value: +"Guidance when nothing matched, when the page is past the last one holding results, or how to continue past a full page."
  3. Changed13 schema fields changed
    • changedOutput schema / properties / observations / items / properties / agreements / description
      Previous value: -"How many identifications agree with the current taxon."New value: +"How many identifications currently agree with the community taxon."
    • changedOutput schema / properties / observations / items / properties / comments / description
      Previous value: -"Discussion comments. Present when \"comments\" was included."New value: +"Discussion comments, cut to their first entries in upstream order (not strictly chronological) when they exceed the per-record share of a 40-entry budget. Present when \"comments\" was included."
    • addedOutput schema / properties / observations / items / properties / comments_shown
      Added value: +{
      +  "description": "How many of them comments carries. Below comments_total when the thread was cut. Present when \"comments\" was included.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / observations / items / properties / comments_total
      Added value: +{
      +  "description": "How many comments upstream holds on the record. Present when \"comments\" was included.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / observations / items / properties / description
      Added value: +{
      +  "description": "The observer’s own note on the sighting — host plant, behaviour, habitat, count. Third-party free text. Present on the by-id tool; null when the observer wrote none.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / observations / items / properties / disagreements / description
      Previous value: -"How many identifications disagree with the current taxon."New value: +"How many identifications currently disagree with the community taxon."
    • changedOutput schema / properties / observations / items / properties / identifications / description
      Previous value: -"The identification thread. Present when \"identifications\" was included."New value: +"The identification thread, cut to its first entries in upstream order (roughly but not strictly chronological) when it exceeds the per-record share of a 40-entry budget. Present when \"identifications\" was included."
    • changedOutput schema / properties / observations / items / properties / identifications_count / description
      Previous value: -"How many identifications the thread holds."New value: +"Upstream’s tally of identifications currently agreeing or disagreeing with the community taxon — agreements + disagreements. Not the thread size: it leaves out the observer’s own identification and any that neither agrees nor disagrees, such as a coarser or withdrawn one. The thread size is identifications_total, on inaturalist_get_observation."
    • addedOutput schema / properties / observations / items / properties / identifications_shown
      Added value: +{
      +  "description": "How many of them identifications carries. Below identifications_total when the thread was cut. Present when \"identifications\" was included.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / observations / items / properties / identifications_total
      Added value: +{
      +  "description": "How many identifications upstream holds on the record — the thread size. Present when \"identifications\" was included.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / observations / items / properties / observation_fields
      Added value: +{
      +  "description": "Observation-field values filled in on the record, usually by a project; fields left blank are dropped. Cut to the first filled fields in upstream order when they exceed the per-record share of a 40-entry budget. Present on the by-id tool.",
      +  "items": {
      +    "additionalProperties": false,
      +    "description": "One filled observation field.",
      +    "properties": {
      +      "name": {
      +        "description": "Field name, as its creator wrote it, e.g. \"Habitat_Description\".",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "value": {
      +        "description": "The value filled in, verbatim.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "name",
      +      "value"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / observations / items / properties / observation_fields_shown
      Added value: +{
      +  "description": "How many of them observation_fields carries. Below observation_fields_total when the list was cut. Present on the by-id tool.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / observations / items / properties / observation_fields_total
      Added value: +{
      +  "description": "How many filled observation fields the record carries. Present on the by-id tool.",
      +  "type": "number"
      +}
  4. Changed9 schema fields changed
    • changedInput schema / properties / d1 / description
      Previous value: -"Earliest observation date, YYYY-MM-DD. Inclusive."New value: +"Earliest observation date, YYYY-MM-DD. Inclusive. Must be on or before d2."
    • changedInput schema / properties / hrank / description
      Previous value: -"Highest taxonomic rank of the identification to accept."New value: +"Highest (coarsest) taxonomic rank of the identification to accept. Must be at or above lrank; ranks compare by rank level."
    • changedInput schema / properties / lrank / description
      Previous value: -"Lowest taxonomic rank of the identification to accept."New value: +"Lowest (finest) taxonomic rank of the identification to accept. Equal to hrank for an exact-rank match."
    • changedInput schema / properties / nelat / description
      Previous value: -"North-east corner latitude of the bounding box. All four corners or none."New value: +"North-east corner latitude of the bounding box, at or north of swlat. All four corners or none."
    • changedInput schema / properties / nelng / description
      Previous value: -"North-east corner longitude of the bounding box. All four corners or none."New value: +"North-east corner longitude of the bounding box. All four corners or none. West of swlng is accepted: it describes a box crossing the antimeridian."
    • changedInput schema / properties / radius / description
      Previous value: -"Search radius around lat/lng, in KILOMETRES. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes."New value: +"Search radius around lat/lng, in KILOMETRES, greater than 0. Requires lat and lng. The upstream publishes no bound; 500 is a verified ceiling this server imposes."
    • removedInput schema / properties / radius / minimum
      Removed value: -0
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `invalid_geography`: An area was given partially or in two forms at once. `result_window_exceeded`: page multiplied by per_page would reach past the upstream 10,000-result window. `conflicting_pagination`: Both page and cursor were supplied. `unpaired_annotation_value`: term_value_id was supplied without term_id. `search_on_without_query`: search_on was supplied without q. `unknown_taxon_id`: iNaturalist answered 422 because the taxon_id does not exist. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_geography`: An area was given partially, in two forms at once, with a radius of 0 or less, or with nelat south of swlat. `inverted_date_range`: d1 is after d2. `inverted_rank_range`: hrank is a finer rank than lrank. `result_window_exceeded`: page multiplied by per_page would reach past the upstream 10,000-result window. `conflicting_pagination`: Both page and cursor were supplied. `unpaired_annotation_value`: term_value_id was supplied without term_id. `search_on_without_query`: search_on was supplied without q. `unknown_taxon_id`: iNaturalist answered 422 because the taxon_id does not exist. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "invalid_geography",
      -  "result_window_exceeded",
      -  "conflicting_pagination",
      -  "unpaired_annotation_value",
      -  "search_on_without_query",
      -  "unknown_taxon_id"
      -]New value: +[
      +  "invalid_geography",
      +  "inverted_date_range",
      +  "inverted_rank_range",
      +  "result_window_exceeded",
      +  "conflicting_pagination",
      +  "unpaired_annotation_value",
      +  "search_on_without_query",
      +  "unknown_taxon_id"
      +]
  5. First observed

TDQS

A4.7/5.0
Behavior5/5

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

Annotations only declare readOnly/openWorld, so the description carries the rest and does so well: it discloses the implicit defaults (research-grade, wild-only) and that they are echoed back, the return projection, the hard 10,000-result page window with the id-descending/cursor workaround, and a payload-size warning explaining why identifications are excluded. This is context an agent could not derive from annotations or schema alone.

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 sentences, front-loaded with purpose, then return shape, then constraints, then the pagination escape hatch. Dense but every clause carries actionable information; nothing restates the name or the schema verbatim.

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?

For a 39-parameter, all-optional search tool with an output schema, the description covers the risks that matter: implicit defaults, mutually exclusive area forms, result-window limits, and payload cost. Nothing an agent needs before calling 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?

Schema coverage is 100%, so baseline is 3, but the description adds real semantics: 'an area is given in exactly one form' with the three mutually exclusive options, the interaction between page/cursor and order_by, and the unit note (kilometres) for the radius triple. It stops short of covering the licence/annotation-code parameters, which the schema already handles well.

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 georeferenced wildlife sightings') and enumerates the filterable dimensions, then states what a record contains. It explicitly carves out the boundary with inaturalist_get_observation ('identifications and comments are deliberately not expandable here'), so an agent can separate this from the detail-fetch sibling without opening schemas.

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?

Gives concrete routing advice: use this for search, fetch identification threads via inaturalist_get_observation, and the exact pagination strategy past 10,000 results. It does not contrast against the aggregation siblings (get_species_counts, get_histogram, get_leaderboard), leaving the agent to infer when a counts/histogram tool is preferable to a record listing.

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

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.