ponymail-mcp: add limit/offset pagination to search_list#12
Conversation
The search_list tool previously rendered the first 30 email summaries unconditionally, then printed "... and N more emails" with no way to reach those N entries without re-querying the backend (and getting the same first 30 back). For LLM clients doing list-wide analysis, the remainder was effectively invisible. This change adds two optional parameters: - limit (int, 1..200, default 30) — caps the rendered window - offset (int, >= 0, default 0) — skips the first N entries The backend call is unchanged; both parameters only affect which slice of the already-fetched result set is formatted into the response. The default behaviour (no params) is identical to before. The trailing summary is upgraded from a static "... and N more" to an explicit "Showing X-Y of Z" plus the exact next-page offset when more results remain, so the caller can page deterministically. Signed-off-by: André Ahlert <andre@aex.partners>
|
@rbowen @potiuk context on why I opened this I'm using ponymail-mcp from Claude inside the airflow-steward / Magpie skills (mailing list triage, governance analysis, finding historical threads). The skills want to enumerate everything on a list, not just browse the top of it ran a test asking Claude (Opus 4.7) to list every thread on dev-magpie@airflow.apache.org. 93 emails, 46 threads. it got back 30 and then tried to work around the cap with narrow from/subject filters. missed 15+ threads that didn't match the guesses, and worse, had no way to know the result was incomplete the 30 default is fine for a human skimming. for an agent walking a list it's a wall with no escape hatch. patch keeps the default at 30 so nothing changes for existing callers, just lets you ask for more when you actually need it happy to follow up with the same treatment for participants / get_mbox if this lands, kept the scope tight on purpose |
|
Yes, the explanation is clear and I expect I wasn't even aware that I was missing results. I'll test and merge asap. Thanks! |
|
Nice catch indeed! |
Problem
search_listcurrently formats the first 30 emails of the backend response and prints... and N more emailsfor the rest. Because there is nolimitoroffsetparameter, the remainingNentries are unreachable: re-querying the backend just returns the same first 30. For LLM clients doing list-wide analysis (e.g. enumerating every thread on a list, walking a month of archive), this hides most of the data behind a hard wall.Concrete example I hit while writing this PR:
dev-magpie@airflow.apache.orghas 93 emails / 46 threads. Callingsearch_listreturns 30; the other 63 emails are invisible. Working around it requires chaining many narrowfrom:/subject:queries hoping to cover the full set, with no guarantee of completeness.Solution
Two optional parameters on
search_list:limit(int, 1..200, default 30) — caps the rendered windowoffset(int, >= 0, default 0) — skips the first N entriesDefaults preserve current behaviour. The backend call is unchanged; both parameters only affect which slice of the already-fetched result set is formatted into the response. No new API hits, no new auth surface, no new env vars.
The trailing summary is upgraded:
... and N more emails(no way to reach them)Showing X-Y of Z.plus... K more emails. Re-query with offset=N to continue.(deterministic pagination)Why now
This is the first thing a long-running LLM session needs that the MCP doesn't have. The original 30-cap is the right default for browse-style use, but offers no escape hatch for analysis-style use. The 200 ceiling on
limitkeeps the worst-case response size sane (~40KB of summaries) while removing the "you can only ever see 30" wall.Backwards compatibility
limit/offsetproduces the same first 30 as before; only the trailing line text changes (Showing 1-30 of 93.vs... and 63 more emails).get_email,get_thread,get_mbox, or any auth/lifecycle tool.Test plan
npm test(43 existing tests pass)node --check index.js(syntax clean)lists.apache.org:search_list(list=dev-magpie, domain=airflow.apache.org)— same first 30 as before, new footer linesearch_list(... limit=100)— renders up to 93 (the full hit set)search_list(... limit=30, offset=30)— renders emails 31-60, footer hintsoffset=60search_list(... offset=999)— renders 0 emails, "offset is past the end" footerNotes for reviewers
/api/stats.luareturns the full set in one response, so an opaque cursor would just be a re-encoded numeric offset. Plainoffsetis simpler and equivalent.parts.slice(0, 15)(participants) andtruncate(text, 8000/4000/10000)(bodies / mbox). Not touched here — scope of this PR is justsearch_list. Happy to follow up with a second PR if the design lands well.