Why
The generic-pagination PR shipped the extension, the runtime, the /v1/search flag and the dashboard pill, but annotated exactly one shipped template (services/gmail.yaml). That was deliberate — twenty template judgements in one diff are impossible to argue with individually — and it leaves the actual damage unfixed. The Metabase case that started this is still live, and eventbrite.list_event_attendees is the same shape waiting.
Read docs/design/generic-pagination.md and the decision entry first; the vocabulary is settled and this issue is about applying it.
Part 1 — annotate what already pages
These declare paging parameters the gateway now knows how to read. Each is mechanical: add a pagination: block naming the parameters that already exist.
| service |
operations |
page size |
continuation |
style |
| github |
list_installations, list_repos, list_issues |
per_page (default 30) |
RFC 8288 Link (not currently modelled — no page param either, so one must be added) |
link |
| stripe |
list_charges |
limit (default 10) |
none declared — needs starting_after |
cursor |
| notion |
search, query_database |
page_size (body, no default) |
start_cursor (body) |
cursor |
| google_drive |
list_files, list_permissions |
pageSize (no default) |
pageToken |
cursor |
| google_keep |
list_notes |
pageSize (no default) |
pageToken |
cursor |
| outlook |
list_messages, list_folders |
$top (no default) |
$skip |
offset |
| metabase |
search |
limit (default 50) |
offset |
offset |
| google_calendar |
list_events |
maxResults (no default) |
none declared — needs pageToken |
cursor |
| google_tasks |
list_tasklists, list_tasks |
maxResults (no default) |
none declared — needs pageToken |
cursor |
| email |
search |
limit (body, no default) |
none — response carries {results, total, truncated} |
n/a, page-size half only |
| slack (MCP) |
list_channels, read_channel_history |
limit (defaults 100 / 50) |
cursor |
cursor |
| hubspot (MCP) |
search_crm_objects, search_owners, get_campaign_contacts_by_type, get_content_analytics_report |
limit |
offset |
offset |
| whatsapp (MCP) |
seven list tools |
limit |
page (default 0) |
page |
Two things to check as you go, because they are where the value is:
- A page-size parameter with no
default: bounds nothing. page_size.default seeds it. That is most of the table.
- Several rows need a continuation parameter added to the template, not just annotated — the upstream supports it and we never declared it. Those are the ones that make an action genuinely pageable rather than merely bounded.
Note slack.search_messages spells page size count, which is a seventh spelling and needs no new vocabulary — page_size.param names whatever the parameter is called.
Part 2 — bound what declares nothing
Seventeen HTTP-runtime list operations declare no page size at all. Where the upstream supports one, add the parameter and the pagination: block. Where it does not (Metabase's list_cards is the canonical case — no pagination, no sorting, no field selection), say so in the action's description and point at the paged sibling, which is what metabase.yaml already does in prose.
metabase.list_databases, list_cards, popular_items, recents
eventbrite.list_my_orders, list_organizations, list_ticket_classes, list_event_attendees — the severe one: an unbounded collection of rich objects, the same shape as the Metabase failure
outlook.list_folders
stripe.list_customers (its sibling list_charges declares limit)
resend.list_domains
google_calendar.list_calendars
gmail.list_labels
- MCP side:
hubspot.get_crm_objects, query_crm_data, get_properties, search_properties, get_campaign_analytics, get_campaign_asset_metrics; deepwiki.read_wiki_contents
Part 3 — the gate that keeps it swept
Close with a corpus test shaped like registry::tests::shipped_mutating_actions_declare_disclose: every shipped action whose key looks like a list (or that returns a collection) either declares x-overslash-pagination or appears in an ALLOW_MISSING list with a comment saying why the upstream cannot page. Keep the allow-list honest — the existing one is empty and carries the instruction "add entries only with a comment explaining why".
Notes
NextStyle::Link is refused on MCP tools at compile: a tool result has no response headers.
- The gateway does not clamp
page_size.max; it bounds default at validation time and writes the dashboard tooltip.
- Where a parameter already declares its own
default:, that wins and page_size.default is inert — validation warns (pagination_default_shadowed). Prefer removing the duplicate over leaving both.
Why
The generic-pagination PR shipped the extension, the runtime, the
/v1/searchflag and the dashboard pill, but annotated exactly one shipped template (services/gmail.yaml). That was deliberate — twenty template judgements in one diff are impossible to argue with individually — and it leaves the actual damage unfixed. The Metabase case that started this is still live, andeventbrite.list_event_attendeesis the same shape waiting.Read
docs/design/generic-pagination.mdand the decision entry first; the vocabulary is settled and this issue is about applying it.Part 1 — annotate what already pages
These declare paging parameters the gateway now knows how to read. Each is mechanical: add a
pagination:block naming the parameters that already exist.list_installations,list_repos,list_issuesper_page(default 30)Link(not currently modelled — nopageparam either, so one must be added)linklist_chargeslimit(default 10)starting_aftercursorsearch,query_databasepage_size(body, no default)start_cursor(body)cursorlist_files,list_permissionspageSize(no default)pageTokencursorlist_notespageSize(no default)pageTokencursorlist_messages,list_folders$top(no default)$skipoffsetsearchlimit(default 50)offsetoffsetlist_eventsmaxResults(no default)pageTokencursorlist_tasklists,list_tasksmaxResults(no default)pageTokencursorsearchlimit(body, no default){results, total, truncated}list_channels,read_channel_historylimit(defaults 100 / 50)cursorcursorsearch_crm_objects,search_owners,get_campaign_contacts_by_type,get_content_analytics_reportlimitoffsetoffsetlimitpage(default 0)pageTwo things to check as you go, because they are where the value is:
default:bounds nothing.page_size.defaultseeds it. That is most of the table.Note
slack.search_messagesspells page sizecount, which is a seventh spelling and needs no new vocabulary —page_size.paramnames whatever the parameter is called.Part 2 — bound what declares nothing
Seventeen HTTP-runtime list operations declare no page size at all. Where the upstream supports one, add the parameter and the
pagination:block. Where it does not (Metabase'slist_cardsis the canonical case — no pagination, no sorting, no field selection), say so in the action'sdescriptionand point at the paged sibling, which is whatmetabase.yamlalready does in prose.metabase.list_databases,list_cards,popular_items,recentseventbrite.list_my_orders,list_organizations,list_ticket_classes,list_event_attendees— the severe one: an unbounded collection of rich objects, the same shape as the Metabase failureoutlook.list_foldersstripe.list_customers(its siblinglist_chargesdeclareslimit)resend.list_domainsgoogle_calendar.list_calendarsgmail.list_labelshubspot.get_crm_objects,query_crm_data,get_properties,search_properties,get_campaign_analytics,get_campaign_asset_metrics;deepwiki.read_wiki_contentsPart 3 — the gate that keeps it swept
Close with a corpus test shaped like
registry::tests::shipped_mutating_actions_declare_disclose: every shipped action whose key looks like a list (or that returns a collection) either declaresx-overslash-paginationor appears in anALLOW_MISSINGlist with a comment saying why the upstream cannot page. Keep the allow-list honest — the existing one is empty and carries the instruction "add entries only with a comment explaining why".Notes
NextStyle::Linkis refused on MCP tools at compile: a tool result has no response headers.page_size.max; it boundsdefaultat validation time and writes the dashboard tooltip.default:, that wins andpage_size.defaultis inert — validation warns (pagination_default_shadowed). Prefer removing the duplicate over leaving both.