Skip to main content
Glama

Openalex Analyze Trends

openalex_analyze_trends
Read-onlyIdempotent

Aggregate OpenAlex entities into groups and count them. Use for trend analysis (group works by publication_year), distribution analysis (group by oa_status, type, country), and comparative analysis (group by institution or topic). Combine with filters to scope the analysis. Returns up to 200 groups per page — use cursor pagination for fields with many distinct values.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
orderNoSort order for groups. Omit or pass "count" (default) to return the top-N groups by count descending — no further pages. Pass "key" to enumerate all distinct values in key-ascending order with cursor pagination. Use "key" only when you need a full traversal; most analysis calls want "count".
cursorNoPagination cursor from a previous response. Only relevant when order is "key" — count-descending results have no next page. Pass the next_cursor from the previous response to advance. Omit it on the first call — an empty string is rejected, since a supplied-but-blank cursor is a caller mistake rather than a request for the first page.
filtersNoFilter criteria (same syntax as openalex_search_entities filters). Narrows the population before aggregation. For full-text within filters, use abstract.search, title.search, or default.search — there is no bare 'search' filter key. Example: group works by year filtered to a specific topic.
group_byYesField to group by. Works examples: "publication_year", "type", "oa_status", "primary_topic.field.id", "authorships.institutions.country_code", "is_retracted". Authors: "last_known_institutions.country_code", "has_orcid". Sources: "type", "is_oa", "country_code". Not all fields support group_by — call openalex_describe_fields(entity_type, "group_by") for the groupable set.
per_pageNoMaximum groups per page (1-200). Default 200 (the upstream cap). A real top-N knob when order is count (the default) — reduce to return only the highest-count groups.
entity_typeYesEntity type to aggregate.
include_unknownNoAdd a group for entities with no value for the grouped field. Hidden by default. That group carries `is_unknown: true`; OpenAlex keys it -111 or -111.0 on numeric fields, "unknown" on text fields and under order "key", and an ID ending in /unknown on ID fields — a sentinel, not a measured value. The key is not a filter value: passing -111 as a filter matches a numeric range, not the entities with no value. Boolean fields have no separate group — a missing value counts as false.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
echoNoCompact echo of the input criteria (entity_type, group_by, filters) — surfaces what was actually requested when no groups are returned.
metaNoAggregation metadata.
errorNoPresent when the call failed. Absent on success.
budgetNoWhat this call cost against the OpenAlex daily budget and what is left of it — weigh `remainingUsd` against `costUsd` before enumerating every group with `order: "key"`. Absent when OpenAlex omitted the accounting headers.
groupsNoAggregation groups with counts.
noticeNoGuidance notice. Set when a first call returns no groups (recovery suggestions), when a `cursor` continuation returns none because the traversal is already finished, or when the page is full and more groups likely exist (truncation signal with narrowing advice). Absent otherwise.
totalCountNoTotal entities matching the filters before grouping (across all pages).

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed6 schema fields changed
    • changedInput schema / properties / group_by / description
      Previous value: -"Field to group by. Works examples: \"publication_year\", \"type\", \"oa_status\", \"primary_topic.field.id\", \"authorships.institutions.country_code\", \"is_retracted\". Authors: \"last_known_institutions.country_code\", \"has_orcid\". Sources: \"type\", \"is_oa\", \"country_code\". Not all fields support group_by — check entity docs if unsure."New value: +"Field to group by. Works examples: \"publication_year\", \"type\", \"oa_status\", \"primary_topic.field.id\", \"authorships.institutions.country_code\", \"is_retracted\". Authors: \"last_known_institutions.country_code\", \"has_orcid\". Sources: \"type\", \"is_oa\", \"country_code\". Not all fields support group_by — call openalex_describe_fields(entity_type, \"group_by\") for the groupable set."
    • changedInput schema / properties / include_unknown / description
      Previous value: -"Include a group for entities with no value for the grouped field. Hidden by default."New value: +"Add a group for entities with no value for the grouped field. Hidden by default. That group carries `is_unknown: true`; OpenAlex keys it -111 or -111.0 on numeric fields, \"unknown\" on text fields and under order \"key\", and an ID ending in /unknown on ID fields — a sentinel, not a measured value. The key is not a filter value: passing -111 as a filter matches a numeric range, not the entities with no value. Boolean fields have no separate group — a missing value counts as false."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid group_by or filter field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_ungroupable_group_by`: group_by targets a raw date, float, or *.search field OpenAlex cannot aggregate (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). `upstream_missing_group_by`: OpenAlex answered with its plain list shape, carrying no aggregation for the requested group_by. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid group_by or filter field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_ungroupable_group_by`: group_by targets a field OpenAlex cannot aggregate — a raw date, a decimal score, a *.search operator, a field such as display_name, doi, or referenced_works, or a concept key on authors, which OpenAlex reports as an invalid OpenAlex ID (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). `upstream_missing_group_by`: OpenAlex answered with its plain list shape, carrying no aggregation for the requested group_by. Other values are possible when a failure originates below the handler."
    • addedOutput schema / properties / groups / items / properties / is_unknown
      Added value: +{
      +  "const": true,
      +  "description": "Present only on the group include_unknown adds for entities with no value; its key is an OpenAlex sentinel (-111, -111.0, unknown, or an ID ending in /unknown), not a measured value.",
      +  "type": "boolean"
      +}
    • changedOutput schema / properties / groups / items / properties / key / description
      Previous value: -"Group key (OpenAlex ID or raw value)."New value: +"Group key (OpenAlex ID or raw value), exactly as OpenAlex returns it."
    • changedOutput schema / properties / groups / items / properties / key_display_name / description
      Previous value: -"Human-readable group label."New value: +"Human-readable group label as plain text, with HTML entities decoded and markup removed."
  2. Changed6 schema fields changed
    • changedInput schema / properties / cursor / description
      Previous value: -"Pagination cursor from a previous response. Only relevant when order is \"key\" — count-descending results have no next page. Pass the next_cursor from the previous response to advance."New value: +"Pagination cursor from a previous response. Only relevant when order is \"key\" — count-descending results have no next page. Pass the next_cursor from the previous response to advance. Omit it on the first call — an empty string is rejected, since a supplied-but-blank cursor is a caller mistake rather than a request for the first page."
    • addedInput schema / properties / cursor / minLength
      Added value: +1
    • addedInput schema / properties / group_by / minLength
      Added value: +1
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid group_by or filter field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_ungroupable_group_by`: group_by targets a raw date, float, or *.search field OpenAlex cannot aggregate (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid group_by or filter field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_ungroupable_group_by`: group_by targets a raw date, float, or *.search field OpenAlex cannot aggregate (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). `upstream_missing_group_by`: OpenAlex answered with its plain list shape, carrying no aggregation for the requested group_by. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "rate_limited",
      -  "upstream_budget_exhausted",
      -  "upstream_timeout",
      -  "upstream_unavailable",
      -  "upstream_unauthorized",
      -  "upstream_forbidden",
      -  "comma_in_filter_value",
      -  "upstream_invalid_params",
      -  "upstream_invalid_id_value",
      -  "upstream_ungroupable_group_by",
      -  "upstream_invalid_params_other",
      -  "upstream_validation_failed"
      -]New value: +[
      +  "rate_limited",
      +  "upstream_budget_exhausted",
      +  "upstream_timeout",
      +  "upstream_unavailable",
      +  "upstream_unauthorized",
      +  "upstream_forbidden",
      +  "comma_in_filter_value",
      +  "upstream_invalid_params",
      +  "upstream_invalid_id_value",
      +  "upstream_ungroupable_group_by",
      +  "upstream_invalid_params_other",
      +  "upstream_validation_failed",
      +  "upstream_missing_group_by"
      +]
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance notice. Set when no groups are returned (recovery suggestions) or when the page is full and more groups likely exist (truncation signal with narrowing advice). Absent otherwise."New value: +"Guidance notice. Set when a first call returns no groups (recovery suggestions), when a `cursor` continuation returns none because the traversal is already finished, or when the page is full and more groups likely exist (truncation signal with narrowing advice). Absent otherwise."
  3. Changed4 schema fields changed
    • removedOutput schema / properties / meta / properties / groups_count / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / meta / properties / groups_count / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / meta / properties / next_cursor / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / meta / properties / next_cursor / type
      Added value: +[
      +  "string",
      +  "null"
      +]
  4. Changed6 schema fields changed
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedInput schema / additionalProperties
      Added value: +false
    • changedOutput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "meta",
      +      "groups",
      +      "echo",
      +      "totalCount"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / error
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "Present when the call failed. Absent on success.",
      +  "properties": {
      +    "code": {
      +      "description": "JSON-RPC error code for this failure.",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "data": {
      +      "additionalProperties": {},
      +      "properties": {
      +        "reason": {
      +          "description": "Machine-readable failure mode. Declared by this tool: `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid group_by or filter field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_ungroupable_group_by`: group_by targets a raw date, float, or *.search field OpenAlex cannot aggregate (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler.",
      +          "examples": [
      +            "rate_limited",
      +            "upstream_budget_exhausted",
      +            "upstream_timeout",
      +            "upstream_unavailable",
      +            "upstream_unauthorized",
      +            "upstream_forbidden",
      +            "comma_in_filter_value",
      +            "upstream_invalid_params",
      +            "upstream_invalid_id_value",
      +            "upstream_ungroupable_group_by",
      +            "upstream_invalid_params_other",
      +            "upstream_validation_failed"
      +          ],
      +          "type": "string"
      +        },
      +        "recovery": {
      +          "additionalProperties": {},
      +          "description": "Actionable next step for the caller.",
      +          "properties": {
      +            "hint": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "hint"
      +          ],
      +          "type": "object"
      +        },
      +        "retryable": {
      +          "description": "Whether retrying may succeed.",
      +          "type": "boolean"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "message": {
      +      "description": "Human-readable description of what went wrong.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "code",
      +    "message"
      +  ],
      +  "type": "object"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "meta",
      -  "groups",
      -  "echo",
      -  "totalCount"
      -]
  5. Changed1 schema field changed
    • addedOutput schema / properties / budget / properties / prepaidRemainingUsd
      Added value: +{
      +  "description": "USD left in the prepaid balance — a separate pool OpenAlex draws on only after the daily allowance runs out, and which the daily reset does not refill. Add it to `remainingUsd` for the full spendable amount. Absent when the account holds no prepaid balance.",
      +  "type": "number"
      +}
  6. Changed1 schema field changed
    • addedOutput schema / properties / budget
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "What this call cost against the OpenAlex daily budget and what is left of it — weigh `remainingUsd` against `costUsd` before enumerating every group with `order: \"key\"`. Absent when OpenAlex omitted the accounting headers.",
      +  "properties": {
      +    "costUsd": {
      +      "description": "USD this call spent. Aggregation is priced far below paging the same entities, so a group_by is the cheap way to size a population before searching it.",
      +      "type": "number"
      +    },
      +    "remainingUsd": {
      +      "description": "USD left in today's OpenAlex budget after this call.",
      +      "type": "number"
      +    },
      +    "resetsInSeconds": {
      +      "description": "Seconds until the daily budget refills (midnight UTC).",
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "costUsd",
      +    "remainingUsd",
      +    "resetsInSeconds"
      +  ],
      +  "type": "object"
      +}
  7. Changed3 schema fields changed
    • removedOutput schema / properties / entityTotal
      Removed value: -{
      -  "description": "Total entities matching the filters before grouping (across all pages).",
      -  "type": "number"
      -}
    • addedOutput schema / properties / totalCount
      Added value: +{
      +  "description": "Total entities matching the filters before grouping (across all pages).",
      +  "type": "number"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "meta",
      -  "groups",
      -  "echo",
      -  "entityTotal"
      -]New value: +[
      +  "meta",
      +  "groups",
      +  "echo",
      +  "totalCount"
      +]
  8. Changed4 schema fields changed
    • changedInput schema / properties / cursor / description
      Previous value: -"Pagination cursor from a previous response. Group-by returns max 200 groups per page. Pass cursor to get the next page. The first page is sorted by count descending; subsequent pages (cursor pages) are sorted by key, not by count."New value: +"Pagination cursor from a previous response. Only relevant when order is \"key\" — count-descending results have no next page. Pass the next_cursor from the previous response to advance."
    • addedInput schema / properties / order
      Added value: +{
      +  "description": "Sort order for groups. Omit or pass \"count\" (default) to return the top-N groups by count descending — no further pages. Pass \"key\" to enumerate all distinct values in key-ascending order with cursor pagination. Use \"key\" only when you need a full traversal; most analysis calls want \"count\".",
      +  "enum": [
      +    "count",
      +    "key"
      +  ],
      +  "type": "string"
      +}
    • changedInput schema / properties / per_page / description
      Previous value: -"Maximum groups per page (1-200). Default 200 (the upstream cap). Reduce when only the top-N groups matter — same aggregation, smaller payload."New value: +"Maximum groups per page (1-200). Default 200 (the upstream cap). A real top-N knob when order is count (the default) — reduce to return only the highest-count groups."
    • changedOutput schema / properties / notice / description
      Previous value: -"Recovery guidance when no groups are returned — echoes the criteria and suggests how to adjust. Absent when groups are present."New value: +"Guidance notice. Set when no groups are returned (recovery suggestions) or when the page is full and more groups likely exist (truncation signal with narrowing advice). Absent otherwise."
  9. Changed2 schema fields changed
    • changedInput schema / properties / per_page / description
      Previous value: -"Maximum groups per page (1-200). Default 200 (the upstream cap). Use lower values when only the top-N groups are relevant — reduces token spend without changing the underlying aggregation."New value: +"Maximum groups per page (1-200). Default 200 (the upstream cap). Reduce when only the top-N groups matter — same aggregation, smaller payload."
    • changedOutput schema / properties / echo / description
      Previous value: -"Compact echo of the input criteria (entity_type, group_by, filters) — useful when no groups are returned so callers see what was actually requested."New value: +"Compact echo of the input criteria (entity_type, group_by, filters) — surfaces what was actually requested when no groups are returned."
  10. Changed6 schema fields changed
    • addedOutput schema / properties / echo
      Added value: +{
      +  "description": "Compact echo of the input criteria (entity_type, group_by, filters) — useful when no groups are returned so callers see what was actually requested.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / entityTotal
      Added value: +{
      +  "description": "Total entities matching the filters before grouping (across all pages).",
      +  "type": "number"
      +}
    • removedOutput schema / properties / meta / properties / echo
      Removed value: -{
      -  "description": "Compact echo of the input criteria (entity_type, group_by, filters) — useful when no groups are returned so callers see what was actually requested.",
      -  "type": "string"
      -}
    • changedOutput schema / properties / meta / required
      Previous value: -[
      -  "count",
      -  "groups_count",
      -  "next_cursor",
      -  "echo"
      -]New value: +[
      +  "count",
      +  "groups_count",
      +  "next_cursor"
      +]
    • addedOutput schema / properties / notice
      Added value: +{
      +  "description": "Recovery guidance when no groups are returned — echoes the criteria and suggests how to adjust. Absent when groups are present.",
      +  "type": "string"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "meta",
      -  "groups"
      -]New value: +[
      +  "meta",
      +  "groups",
      +  "echo",
      +  "entityTotal"
      +]
  11. Changed2 schema fields changed
    • changedInput schema / properties / filters / description
      Previous value: -"Filter criteria (same syntax as openalex_search_entities filters). Narrows the population before aggregation. Example: group works by year, filtered to a specific topic."New value: +"Filter criteria (same syntax as openalex_search_entities filters). Narrows the population before aggregation. For full-text within filters, use abstract.search, title.search, or default.search — there is no bare 'search' filter key. Example: group works by year filtered to a specific topic."
    • addedInput schema / properties / per_page
      Added value: +{
      +  "default": 200,
      +  "description": "Maximum groups per page (1-200). Default 200 (the upstream cap). Use lower values when only the top-N groups are relevant — reduces token spend without changing the underlying aggregation.",
      +  "maximum": 200,
      +  "minimum": 1,
      +  "type": "integer"
      +}
  12. Changed3 schema fields changed
    • changedInput schema / properties / cursor / description
      Previous value: -"Pagination cursor from a previous response. Group-by returns max 200 groups per page. Pass cursor to get the next page. Note: paginated groups are sorted by key, not by count."New value: +"Pagination cursor from a previous response. Group-by returns max 200 groups per page. Pass cursor to get the next page. The first page is sorted by count descending; subsequent pages (cursor pages) are sorted by key, not by count."
    • addedOutput schema / properties / meta / properties / echo
      Added value: +{
      +  "description": "Compact echo of the input criteria (entity_type, group_by, filters) — useful when no groups are returned so callers see what was actually requested.",
      +  "type": "string"
      +}
    • changedOutput schema / properties / meta / required
      Previous value: -[
      -  "count",
      -  "groups_count",
      -  "next_cursor"
      -]New value: +[
      +  "count",
      +  "groups_count",
      +  "next_cursor",
      +  "echo"
      +]
  13. Changed1 schema field changed
    • addedOutput schema / properties / groups / items / description
      Added value: +"A single aggregation group with its key, display label, and entity count."
  14. First observed

TDQS

A4.3/5.0
Behavior4/5

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

Annotations already establish readOnly, openWorld, and idempotent behavior. The description adds useful operational transparency beyond that: a hard 200-groups-per-page cap, the cursor-pagination requirement for fields with many distinct values, and the aggregation-then-filter mental model. These are behavioral details not encoded in the annotations.

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?

Three sentences, with the core operation front-loaded and every subsequent sentence adding either a use case or an operational constraint. There is no repetition of schema details and no filler, so the description earns its length.

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?

Between the description, a 100%-documented schema, rich annotations, and an output schema, an agent has all needed context to select and invoke the tool correctly. The description covers purpose, use cases, scoping, and pagination; the schema covers parameters and return shape.

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

Parameters3/5

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

Schema description coverage is 100%, and the individual parameter descriptions already explain group_by examples, filter syntax, cursor semantics, and per_page behavior. The tool description reinforces these concepts but does not add meaning beyond the schema, so the coverage-rich baseline of 3 is appropriate.

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?

The description opens with an unambiguous verb-object pair: "Aggregate OpenAlex entities into groups and count them." It then lists concrete analysis types (trend, distribution, comparative) with example group-by fields, making the tool's role as an aggregation endpoint immediately clear and distinct from search/retrieval siblings such as openalex_search_entities.

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?

The description explicitly states when to use the tool: trend analysis, distribution analysis, and comparative analysis, plus how to combine with filters to scope the analysis. It does not explicitly name sibling alternatives or state when not to use it, but the use-case guidance is strong enough to route an agent correctly.

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.