@cyanheads/zenodo-mcp-server 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/AGENTS.md +22 -15
  2. package/CLAUDE.md +22 -15
  3. package/Dockerfile +3 -3
  4. package/README.md +6 -4
  5. package/changelog/0.1.x/0.1.3.md +35 -0
  6. package/dist/mcp-server/tools/definitions/get-record.tool.d.ts +106 -2
  7. package/dist/mcp-server/tools/definitions/get-record.tool.d.ts.map +1 -1
  8. package/dist/mcp-server/tools/definitions/get-record.tool.js +452 -106
  9. package/dist/mcp-server/tools/definitions/get-record.tool.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/index.d.ts +283 -180
  11. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/list-files.tool.d.ts.map +1 -1
  13. package/dist/mcp-server/tools/definitions/list-files.tool.js +21 -15
  14. package/dist/mcp-server/tools/definitions/list-files.tool.js.map +1 -1
  15. package/dist/mcp-server/tools/definitions/list-versions.tool.d.ts +1 -1
  16. package/dist/mcp-server/tools/definitions/list-versions.tool.d.ts.map +1 -1
  17. package/dist/mcp-server/tools/definitions/list-versions.tool.js +29 -20
  18. package/dist/mcp-server/tools/definitions/list-versions.tool.js.map +1 -1
  19. package/dist/mcp-server/tools/definitions/lookup-vocabulary.tool.d.ts +2 -1
  20. package/dist/mcp-server/tools/definitions/lookup-vocabulary.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/lookup-vocabulary.tool.js +39 -22
  22. package/dist/mcp-server/tools/definitions/lookup-vocabulary.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/read-file.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/read-file.tool.js +16 -14
  25. package/dist/mcp-server/tools/definitions/read-file.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/search-records.tool.d.ts +3 -3
  27. package/dist/mcp-server/tools/definitions/search-records.tool.d.ts.map +1 -1
  28. package/dist/mcp-server/tools/definitions/search-records.tool.js +138 -29
  29. package/dist/mcp-server/tools/definitions/search-records.tool.js.map +1 -1
  30. package/dist/mcp-server/tools/paging.d.ts +42 -0
  31. package/dist/mcp-server/tools/paging.d.ts.map +1 -0
  32. package/dist/mcp-server/tools/paging.js +44 -0
  33. package/dist/mcp-server/tools/paging.js.map +1 -0
  34. package/dist/mcp-server/tools/record-miss.d.ts +5 -1
  35. package/dist/mcp-server/tools/record-miss.d.ts.map +1 -1
  36. package/dist/mcp-server/tools/record-miss.js +6 -2
  37. package/dist/mcp-server/tools/record-miss.js.map +1 -1
  38. package/dist/services/zenodo/html-to-text.d.ts +19 -8
  39. package/dist/services/zenodo/html-to-text.d.ts.map +1 -1
  40. package/dist/services/zenodo/html-to-text.js +175 -48
  41. package/dist/services/zenodo/html-to-text.js.map +1 -1
  42. package/dist/services/zenodo/http.js +1 -1
  43. package/dist/services/zenodo/http.js.map +1 -1
  44. package/dist/services/zenodo/normalize.d.ts.map +1 -1
  45. package/dist/services/zenodo/normalize.js +5 -2
  46. package/dist/services/zenodo/normalize.js.map +1 -1
  47. package/dist/services/zenodo/query-builder.d.ts +84 -8
  48. package/dist/services/zenodo/query-builder.d.ts.map +1 -1
  49. package/dist/services/zenodo/query-builder.js +484 -21
  50. package/dist/services/zenodo/query-builder.js.map +1 -1
  51. package/dist/services/zenodo/text-preview.d.ts +1 -1
  52. package/dist/services/zenodo/text-preview.d.ts.map +1 -1
  53. package/dist/services/zenodo/text-preview.js +38 -38
  54. package/dist/services/zenodo/text-preview.js.map +1 -1
  55. package/package.json +7 -7
  56. package/server.json +3 -3
@@ -11,6 +11,283 @@ import { searchRecords } from './search-records.tool.js';
11
11
  export { getRecord, listFiles, listVersions, lookupVocabulary, readFile, searchRecords };
12
12
  /** All tool definitions, in registration order. */
13
13
  export declare const allToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
14
+ id: import("zod").ZodString;
15
+ citation_style: import("zod").ZodPreprocess<import("zod").ZodOptional<import("zod").ZodEnum<{
16
+ apa: "apa";
17
+ bibtex: "bibtex";
18
+ "chicago-author-date": "chicago-author-date";
19
+ "csl-json": "csl-json";
20
+ "harvard-cite-them-right": "harvard-cite-them-right";
21
+ ieee: "ieee";
22
+ "modern-language-association": "modern-language-association";
23
+ nature: "nature";
24
+ }>>, unknown>;
25
+ cursor: import("zod").ZodPreprocess<import("zod").ZodOptional<import("zod").ZodString>, unknown>;
26
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
27
+ found: import("zod").ZodBoolean;
28
+ input_kind: import("zod").ZodEnum<{
29
+ external_doi: "external_doi";
30
+ record_id: "record_id";
31
+ url: "url";
32
+ zenodo_doi: "zenodo_doi";
33
+ }>;
34
+ resolved_from: import("zod").ZodOptional<import("zod").ZodEnum<{
35
+ concept_to_latest: "concept_to_latest";
36
+ external_doi: "external_doi";
37
+ version: "version";
38
+ }>>;
39
+ miss_kind: import("zod").ZodOptional<import("zod").ZodEnum<{
40
+ deleted: "deleted";
41
+ not_found: "not_found";
42
+ not_on_zenodo: "not_on_zenodo";
43
+ restricted: "restricted";
44
+ }>>;
45
+ guidance: import("zod").ZodOptional<import("zod").ZodString>;
46
+ tombstone: import("zod").ZodOptional<import("zod").ZodObject<{
47
+ removal_date: import("zod").ZodOptional<import("zod").ZodString>;
48
+ removal_reason: import("zod").ZodOptional<import("zod").ZodString>;
49
+ note: import("zod").ZodOptional<import("zod").ZodString>;
50
+ citation_text: import("zod").ZodOptional<import("zod").ZodString>;
51
+ }, import("zod/v4/core").$strip>>;
52
+ record: import("zod").ZodOptional<import("zod").ZodObject<{
53
+ recid: import("zod").ZodString;
54
+ concept_recid: import("zod").ZodOptional<import("zod").ZodString>;
55
+ doi: import("zod").ZodOptional<import("zod").ZodString>;
56
+ doi_provider: import("zod").ZodOptional<import("zod").ZodString>;
57
+ concept_doi: import("zod").ZodOptional<import("zod").ZodString>;
58
+ oai_id: import("zod").ZodOptional<import("zod").ZodString>;
59
+ title: import("zod").ZodString;
60
+ publication_date: import("zod").ZodOptional<import("zod").ZodString>;
61
+ version: import("zod").ZodOptional<import("zod").ZodString>;
62
+ publisher: import("zod").ZodOptional<import("zod").ZodString>;
63
+ resource_type: import("zod").ZodOptional<import("zod").ZodObject<{
64
+ id: import("zod").ZodString;
65
+ title: import("zod").ZodOptional<import("zod").ZodString>;
66
+ }, import("zod/v4/core").$strip>>;
67
+ description: import("zod").ZodOptional<import("zod").ZodString>;
68
+ description_truncated: import("zod").ZodOptional<import("zod").ZodBoolean>;
69
+ description_length: import("zod").ZodOptional<import("zod").ZodNumber>;
70
+ additional_descriptions: import("zod").ZodArray<import("zod").ZodObject<{
71
+ type: import("zod").ZodOptional<import("zod").ZodString>;
72
+ text: import("zod").ZodString;
73
+ length: import("zod").ZodNumber;
74
+ truncated: import("zod").ZodBoolean;
75
+ }, import("zod/v4/core").$strip>>;
76
+ additional_description_count: import("zod").ZodNumber;
77
+ creators: import("zod").ZodArray<import("zod").ZodObject<{
78
+ name: import("zod").ZodString;
79
+ type: import("zod").ZodOptional<import("zod").ZodString>;
80
+ orcid: import("zod").ZodOptional<import("zod").ZodString>;
81
+ role: import("zod").ZodOptional<import("zod").ZodString>;
82
+ affiliations: import("zod").ZodArray<import("zod").ZodObject<{
83
+ name: import("zod").ZodOptional<import("zod").ZodString>;
84
+ ror: import("zod").ZodOptional<import("zod").ZodString>;
85
+ }, import("zod/v4/core").$strip>>;
86
+ }, import("zod/v4/core").$strip>>;
87
+ creator_count: import("zod").ZodNumber;
88
+ contributors: import("zod").ZodArray<import("zod").ZodObject<{
89
+ name: import("zod").ZodString;
90
+ type: import("zod").ZodOptional<import("zod").ZodString>;
91
+ orcid: import("zod").ZodOptional<import("zod").ZodString>;
92
+ role: import("zod").ZodOptional<import("zod").ZodString>;
93
+ affiliations: import("zod").ZodArray<import("zod").ZodObject<{
94
+ name: import("zod").ZodOptional<import("zod").ZodString>;
95
+ ror: import("zod").ZodOptional<import("zod").ZodString>;
96
+ }, import("zod/v4/core").$strip>>;
97
+ }, import("zod/v4/core").$strip>>;
98
+ contributor_count: import("zod").ZodNumber;
99
+ keywords: import("zod").ZodArray<import("zod").ZodString>;
100
+ keyword_count: import("zod").ZodNumber;
101
+ rights: import("zod").ZodArray<import("zod").ZodObject<{
102
+ id: import("zod").ZodOptional<import("zod").ZodString>;
103
+ title: import("zod").ZodString;
104
+ url: import("zod").ZodOptional<import("zod").ZodString>;
105
+ }, import("zod/v4/core").$strip>>;
106
+ access: import("zod").ZodObject<{
107
+ status: import("zod").ZodString;
108
+ record: import("zod").ZodOptional<import("zod").ZodString>;
109
+ files: import("zod").ZodOptional<import("zod").ZodString>;
110
+ embargo_active: import("zod").ZodBoolean;
111
+ embargo_until: import("zod").ZodOptional<import("zod").ZodString>;
112
+ embargo_reason: import("zod").ZodOptional<import("zod").ZodString>;
113
+ }, import("zod/v4/core").$strip>;
114
+ funding: import("zod").ZodArray<import("zod").ZodObject<{
115
+ funder_id: import("zod").ZodOptional<import("zod").ZodString>;
116
+ funder_name: import("zod").ZodOptional<import("zod").ZodString>;
117
+ award_id: import("zod").ZodOptional<import("zod").ZodString>;
118
+ award_number: import("zod").ZodOptional<import("zod").ZodString>;
119
+ award_acronym: import("zod").ZodOptional<import("zod").ZodString>;
120
+ award_title: import("zod").ZodOptional<import("zod").ZodString>;
121
+ award_program: import("zod").ZodOptional<import("zod").ZodString>;
122
+ award_doi: import("zod").ZodOptional<import("zod").ZodString>;
123
+ award_url: import("zod").ZodOptional<import("zod").ZodString>;
124
+ }, import("zod/v4/core").$strip>>;
125
+ funding_count: import("zod").ZodNumber;
126
+ related_identifiers: import("zod").ZodArray<import("zod").ZodObject<{
127
+ identifier: import("zod").ZodString;
128
+ scheme: import("zod").ZodOptional<import("zod").ZodString>;
129
+ relation: import("zod").ZodOptional<import("zod").ZodString>;
130
+ resource_type: import("zod").ZodOptional<import("zod").ZodString>;
131
+ }, import("zod/v4/core").$strip>>;
132
+ related_identifier_count: import("zod").ZodNumber;
133
+ code_repository: import("zod").ZodOptional<import("zod").ZodString>;
134
+ communities: import("zod").ZodArray<import("zod").ZodObject<{
135
+ slug: import("zod").ZodString;
136
+ title: import("zod").ZodOptional<import("zod").ZodString>;
137
+ }, import("zod/v4/core").$strip>>;
138
+ community_count: import("zod").ZodNumber;
139
+ versions: import("zod").ZodObject<{
140
+ index: import("zod").ZodOptional<import("zod").ZodNumber>;
141
+ is_latest: import("zod").ZodOptional<import("zod").ZodBoolean>;
142
+ latest_recid: import("zod").ZodOptional<import("zod").ZodString>;
143
+ }, import("zod/v4/core").$strip>;
144
+ stats: import("zod").ZodOptional<import("zod").ZodObject<{
145
+ this_version: import("zod").ZodObject<{
146
+ views: import("zod").ZodOptional<import("zod").ZodNumber>;
147
+ downloads: import("zod").ZodOptional<import("zod").ZodNumber>;
148
+ }, import("zod/v4/core").$strip>;
149
+ all_versions: import("zod").ZodObject<{
150
+ views: import("zod").ZodOptional<import("zod").ZodNumber>;
151
+ downloads: import("zod").ZodOptional<import("zod").ZodNumber>;
152
+ }, import("zod/v4/core").$strip>;
153
+ }, import("zod/v4/core").$strip>>;
154
+ files: import("zod").ZodObject<{
155
+ enabled: import("zod").ZodBoolean;
156
+ count: import("zod").ZodOptional<import("zod").ZodNumber>;
157
+ total_bytes: import("zod").ZodOptional<import("zod").ZodNumber>;
158
+ shown: import("zod").ZodNumber;
159
+ entries: import("zod").ZodArray<import("zod").ZodObject<{
160
+ key: import("zod").ZodString;
161
+ size: import("zod").ZodOptional<import("zod").ZodNumber>;
162
+ mimetype: import("zod").ZodOptional<import("zod").ZodString>;
163
+ md5: import("zod").ZodOptional<import("zod").ZodString>;
164
+ download_url: import("zod").ZodString;
165
+ }, import("zod/v4/core").$strip>>;
166
+ }, import("zod/v4/core").$strip>;
167
+ revision: import("zod").ZodOptional<import("zod").ZodNumber>;
168
+ zenodo_url: import("zod").ZodString;
169
+ }, import("zod/v4/core").$strip>>;
170
+ window: import("zod").ZodOptional<import("zod").ZodObject<{
171
+ field: import("zod").ZodEnum<{
172
+ additional_descriptions: "additional_descriptions";
173
+ communities: "communities";
174
+ contributors: "contributors";
175
+ creators: "creators";
176
+ description: "description";
177
+ funding: "funding";
178
+ keywords: "keywords";
179
+ related_identifiers: "related_identifiers";
180
+ }>;
181
+ index: import("zod").ZodOptional<import("zod").ZodNumber>;
182
+ unit: import("zod").ZodEnum<{
183
+ characters: "characters";
184
+ entries: "entries";
185
+ }>;
186
+ offset: import("zod").ZodNumber;
187
+ shown: import("zod").ZodNumber;
188
+ total: import("zod").ZodNumber;
189
+ text: import("zod").ZodOptional<import("zod").ZodString>;
190
+ additional_descriptions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
191
+ type: import("zod").ZodOptional<import("zod").ZodString>;
192
+ text: import("zod").ZodString;
193
+ length: import("zod").ZodNumber;
194
+ truncated: import("zod").ZodBoolean;
195
+ }, import("zod/v4/core").$strip>>>;
196
+ creators: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
197
+ name: import("zod").ZodString;
198
+ type: import("zod").ZodOptional<import("zod").ZodString>;
199
+ orcid: import("zod").ZodOptional<import("zod").ZodString>;
200
+ role: import("zod").ZodOptional<import("zod").ZodString>;
201
+ affiliations: import("zod").ZodArray<import("zod").ZodObject<{
202
+ name: import("zod").ZodOptional<import("zod").ZodString>;
203
+ ror: import("zod").ZodOptional<import("zod").ZodString>;
204
+ }, import("zod/v4/core").$strip>>;
205
+ }, import("zod/v4/core").$strip>>>;
206
+ contributors: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
207
+ name: import("zod").ZodString;
208
+ type: import("zod").ZodOptional<import("zod").ZodString>;
209
+ orcid: import("zod").ZodOptional<import("zod").ZodString>;
210
+ role: import("zod").ZodOptional<import("zod").ZodString>;
211
+ affiliations: import("zod").ZodArray<import("zod").ZodObject<{
212
+ name: import("zod").ZodOptional<import("zod").ZodString>;
213
+ ror: import("zod").ZodOptional<import("zod").ZodString>;
214
+ }, import("zod/v4/core").$strip>>;
215
+ }, import("zod/v4/core").$strip>>>;
216
+ keywords: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
217
+ funding: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
218
+ funder_id: import("zod").ZodOptional<import("zod").ZodString>;
219
+ funder_name: import("zod").ZodOptional<import("zod").ZodString>;
220
+ award_id: import("zod").ZodOptional<import("zod").ZodString>;
221
+ award_number: import("zod").ZodOptional<import("zod").ZodString>;
222
+ award_acronym: import("zod").ZodOptional<import("zod").ZodString>;
223
+ award_title: import("zod").ZodOptional<import("zod").ZodString>;
224
+ award_program: import("zod").ZodOptional<import("zod").ZodString>;
225
+ award_doi: import("zod").ZodOptional<import("zod").ZodString>;
226
+ award_url: import("zod").ZodOptional<import("zod").ZodString>;
227
+ }, import("zod/v4/core").$strip>>>;
228
+ related_identifiers: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
229
+ identifier: import("zod").ZodString;
230
+ scheme: import("zod").ZodOptional<import("zod").ZodString>;
231
+ relation: import("zod").ZodOptional<import("zod").ZodString>;
232
+ resource_type: import("zod").ZodOptional<import("zod").ZodString>;
233
+ }, import("zod/v4/core").$strip>>>;
234
+ communities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
235
+ slug: import("zod").ZodString;
236
+ title: import("zod").ZodOptional<import("zod").ZodString>;
237
+ }, import("zod/v4/core").$strip>>>;
238
+ }, import("zod/v4/core").$strip>>;
239
+ continuations: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
240
+ field: import("zod").ZodEnum<{
241
+ additional_descriptions: "additional_descriptions";
242
+ communities: "communities";
243
+ contributors: "contributors";
244
+ creators: "creators";
245
+ description: "description";
246
+ funding: "funding";
247
+ keywords: "keywords";
248
+ related_identifiers: "related_identifiers";
249
+ }>;
250
+ index: import("zod").ZodOptional<import("zod").ZodNumber>;
251
+ unit: import("zod").ZodEnum<{
252
+ characters: "characters";
253
+ entries: "entries";
254
+ }>;
255
+ shown: import("zod").ZodNumber;
256
+ total: import("zod").ZodNumber;
257
+ id: import("zod").ZodString;
258
+ cursor: import("zod").ZodString;
259
+ }, import("zod/v4/core").$strip>>>;
260
+ citation: import("zod").ZodOptional<import("zod").ZodString>;
261
+ citation_style: import("zod").ZodOptional<import("zod").ZodString>;
262
+ }, import("zod/v4/core").$strip>, readonly [{
263
+ readonly reason: "invalid_identifier";
264
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
265
+ readonly when: "id matches no accepted form, or is a GitHub-badge latestdoi id or a non-zenodo.org host";
266
+ readonly recovery: "Pass a Zenodo record id (22705923), a DOI (10.5281/zenodo.22705923), or a zenodo.org/records URL as id; for a title or keyword, use zenodo_search_records.";
267
+ }, {
268
+ readonly reason: "invalid_cursor";
269
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
270
+ readonly when: "cursor is not one this tool returned in continuations, belongs to another record, or the record was edited after the cursor was issued";
271
+ readonly recovery: "Pass the cursor with the record id the message names as id to continue that record; otherwise call zenodo_get_record with this id and no cursor for this record’s own continuations, and pass one of those cursors unchanged with the id it lists.";
272
+ }, {
273
+ readonly reason: "record_unavailable";
274
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
275
+ readonly when: "Zenodo could not serve this record: HTTP 500 after one retry, HTTP 500 while resolving a non-Zenodo DOI, or no complete response within the time budget (its gateway cuts off near 30 s, which deposits with about 10,000 or more files hit)";
276
+ readonly thrownBy: "service";
277
+ readonly recovery: "Look the record up with zenodo_search_records using query doi:\"<its DOI>\" (all_versions true) or its title; withdrawn legacy records and deposits with more than about 10,000 files fail on the record endpoint, and if a keyword search also fails, Zenodo is degraded.";
278
+ }, {
279
+ readonly reason: "rate_limited";
280
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
281
+ readonly when: "Zenodo answered HTTP 429, or this server's shared Zenodo request budget is spent for the current window";
282
+ readonly retryable: true;
283
+ readonly thrownBy: "service";
284
+ readonly recovery: "Wait for the retryAfter seconds in the error data, then call zenodo_get_record again with the same arguments.";
285
+ }], {
286
+ readonly truncated: import("zod").ZodBoolean;
287
+ readonly shown: import("zod").ZodNumber;
288
+ readonly cap: import("zod").ZodNumber;
289
+ readonly notice: import("zod").ZodOptional<import("zod").ZodString>;
290
+ }> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
14
291
  id: import("zod").ZodString;
15
292
  page: import("zod").ZodDefault<import("zod").ZodNumber>;
16
293
  size: import("zod").ZodDefault<import("zod").ZodNumber>;
@@ -64,7 +341,7 @@ export declare const allToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolD
64
341
  }, {
65
342
  readonly reason: "result_window_exceeded";
66
343
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
67
- readonly when: "page × size exceeds 10,000";
344
+ readonly when: "The page starts past the first 10,000 versions: (page − 1) × size is 10,000 or more, counting a size above 25 as 25";
68
345
  readonly recovery: "Zenodo pages only the first 10,000 versions of a series, and no series comes near that; call zenodo_list_versions with page 1 (total_versions and has_more show how far the series goes).";
69
346
  }, {
70
347
  readonly reason: "record_unavailable";
@@ -202,8 +479,8 @@ export declare const allToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolD
202
479
  }, {
203
480
  readonly reason: "query_syntax";
204
481
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
205
- readonly when: "The query has an unpaired unescaped / outside quotes";
206
- readonly recovery: "Escape the slash as \\/ or wrap that term in double quotes, then call zenodo_search_records again.";
482
+ readonly when: "The query holds something Zenodo cannot run: a regular expression with no closing / (an unescaped / outside double quotes) or with a \" inside /…/ left open; a \" inside a word whose phrase never closes (with a filter other than community and all_versions set, only when the query still fails once that phrase is closed at its end) or closes past a bracket the query needs; a range bound ending in a backslash, as one holding an escaped space, ] or } does; an unescaped ! with nothing after it; or a boost, fuzziness, or proximity value it cannot convert. Or a filter other than community and all_versions is set and the query is not valid search syntax, which Zenodo would match as plain text with every filter dropped";
483
+ readonly recovery: "Fix the syntax the message names, then call zenodo_search_records again: double-quote or backslash-escape any / [ ] { } ~ ^ : or ! meant as plain text (\"[CMIP6]\", \\[CMIP6\\], 10.5281\\/zenodo.1, climate\\!) and any \" inside a regular expression or a word (12\\\" vinyl), write phrases in double quotes rather than single, drop a doubled or dangling operator (AND AND, or one with nothing after it inside parentheses, as in (climate AND)), give a field: prefix a value, close a range as [a TO b] and double-quote a bound that holds a space, ] or } ([a TO \"b]\"]), and give a boost or fuzziness one decimal number and a phrase proximity a whole number (^2, ~0.5, \"x y\"~2).";
207
484
  }, {
208
485
  readonly reason: "query_failed";
209
486
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
@@ -236,7 +513,7 @@ export declare const allToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolD
236
513
  }, {
237
514
  readonly reason: "result_window_exceeded";
238
515
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
239
- readonly when: "page × size exceeds 10,000";
516
+ readonly when: "The page starts past the first 10,000 matches: (page − 1) × size is 10,000 or more, counting a size above 25 as 25";
240
517
  readonly recovery: "Zenodo serves only the first 10,000 matches; add filters or a published_from/published_to range to zenodo_search_records instead of paging deeper.";
241
518
  }, {
242
519
  readonly reason: "rate_limited";
@@ -262,181 +539,6 @@ export declare const allToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolD
262
539
  }>;
263
540
  readonly allVersions: import("zod").ZodBoolean;
264
541
  readonly notice: import("zod").ZodOptional<import("zod").ZodString>;
265
- }> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
266
- id: import("zod").ZodString;
267
- citation_style: import("zod").ZodPreprocess<import("zod").ZodOptional<import("zod").ZodEnum<{
268
- apa: "apa";
269
- bibtex: "bibtex";
270
- "chicago-author-date": "chicago-author-date";
271
- "csl-json": "csl-json";
272
- "harvard-cite-them-right": "harvard-cite-them-right";
273
- ieee: "ieee";
274
- "modern-language-association": "modern-language-association";
275
- nature: "nature";
276
- }>>, unknown>;
277
- }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
278
- found: import("zod").ZodBoolean;
279
- input_kind: import("zod").ZodEnum<{
280
- external_doi: "external_doi";
281
- record_id: "record_id";
282
- url: "url";
283
- zenodo_doi: "zenodo_doi";
284
- }>;
285
- resolved_from: import("zod").ZodOptional<import("zod").ZodEnum<{
286
- concept_to_latest: "concept_to_latest";
287
- external_doi: "external_doi";
288
- version: "version";
289
- }>>;
290
- miss_kind: import("zod").ZodOptional<import("zod").ZodEnum<{
291
- deleted: "deleted";
292
- not_found: "not_found";
293
- not_on_zenodo: "not_on_zenodo";
294
- restricted: "restricted";
295
- }>>;
296
- guidance: import("zod").ZodOptional<import("zod").ZodString>;
297
- tombstone: import("zod").ZodOptional<import("zod").ZodObject<{
298
- removal_date: import("zod").ZodOptional<import("zod").ZodString>;
299
- removal_reason: import("zod").ZodOptional<import("zod").ZodString>;
300
- note: import("zod").ZodOptional<import("zod").ZodString>;
301
- citation_text: import("zod").ZodOptional<import("zod").ZodString>;
302
- }, import("zod/v4/core").$strip>>;
303
- record: import("zod").ZodOptional<import("zod").ZodObject<{
304
- recid: import("zod").ZodString;
305
- concept_recid: import("zod").ZodOptional<import("zod").ZodString>;
306
- doi: import("zod").ZodOptional<import("zod").ZodString>;
307
- doi_provider: import("zod").ZodOptional<import("zod").ZodString>;
308
- concept_doi: import("zod").ZodOptional<import("zod").ZodString>;
309
- oai_id: import("zod").ZodOptional<import("zod").ZodString>;
310
- title: import("zod").ZodString;
311
- publication_date: import("zod").ZodOptional<import("zod").ZodString>;
312
- version: import("zod").ZodOptional<import("zod").ZodString>;
313
- publisher: import("zod").ZodOptional<import("zod").ZodString>;
314
- resource_type: import("zod").ZodOptional<import("zod").ZodObject<{
315
- id: import("zod").ZodString;
316
- title: import("zod").ZodOptional<import("zod").ZodString>;
317
- }, import("zod/v4/core").$strip>>;
318
- description: import("zod").ZodOptional<import("zod").ZodString>;
319
- description_truncated: import("zod").ZodOptional<import("zod").ZodBoolean>;
320
- description_length: import("zod").ZodOptional<import("zod").ZodNumber>;
321
- additional_descriptions: import("zod").ZodArray<import("zod").ZodObject<{
322
- type: import("zod").ZodOptional<import("zod").ZodString>;
323
- text: import("zod").ZodString;
324
- }, import("zod/v4/core").$strip>>;
325
- creators: import("zod").ZodArray<import("zod").ZodObject<{
326
- name: import("zod").ZodString;
327
- type: import("zod").ZodOptional<import("zod").ZodString>;
328
- orcid: import("zod").ZodOptional<import("zod").ZodString>;
329
- role: import("zod").ZodOptional<import("zod").ZodString>;
330
- affiliations: import("zod").ZodArray<import("zod").ZodObject<{
331
- name: import("zod").ZodOptional<import("zod").ZodString>;
332
- ror: import("zod").ZodOptional<import("zod").ZodString>;
333
- }, import("zod/v4/core").$strip>>;
334
- }, import("zod/v4/core").$strip>>;
335
- creator_count: import("zod").ZodNumber;
336
- contributors: import("zod").ZodArray<import("zod").ZodObject<{
337
- name: import("zod").ZodString;
338
- type: import("zod").ZodOptional<import("zod").ZodString>;
339
- orcid: import("zod").ZodOptional<import("zod").ZodString>;
340
- role: import("zod").ZodOptional<import("zod").ZodString>;
341
- affiliations: import("zod").ZodArray<import("zod").ZodObject<{
342
- name: import("zod").ZodOptional<import("zod").ZodString>;
343
- ror: import("zod").ZodOptional<import("zod").ZodString>;
344
- }, import("zod/v4/core").$strip>>;
345
- }, import("zod/v4/core").$strip>>;
346
- contributor_count: import("zod").ZodNumber;
347
- keywords: import("zod").ZodArray<import("zod").ZodString>;
348
- rights: import("zod").ZodArray<import("zod").ZodObject<{
349
- id: import("zod").ZodOptional<import("zod").ZodString>;
350
- title: import("zod").ZodString;
351
- url: import("zod").ZodOptional<import("zod").ZodString>;
352
- }, import("zod/v4/core").$strip>>;
353
- access: import("zod").ZodObject<{
354
- status: import("zod").ZodString;
355
- record: import("zod").ZodOptional<import("zod").ZodString>;
356
- files: import("zod").ZodOptional<import("zod").ZodString>;
357
- embargo_active: import("zod").ZodBoolean;
358
- embargo_until: import("zod").ZodOptional<import("zod").ZodString>;
359
- embargo_reason: import("zod").ZodOptional<import("zod").ZodString>;
360
- }, import("zod/v4/core").$strip>;
361
- funding: import("zod").ZodArray<import("zod").ZodObject<{
362
- funder_id: import("zod").ZodOptional<import("zod").ZodString>;
363
- funder_name: import("zod").ZodOptional<import("zod").ZodString>;
364
- award_id: import("zod").ZodOptional<import("zod").ZodString>;
365
- award_number: import("zod").ZodOptional<import("zod").ZodString>;
366
- award_acronym: import("zod").ZodOptional<import("zod").ZodString>;
367
- award_title: import("zod").ZodOptional<import("zod").ZodString>;
368
- award_program: import("zod").ZodOptional<import("zod").ZodString>;
369
- award_doi: import("zod").ZodOptional<import("zod").ZodString>;
370
- award_url: import("zod").ZodOptional<import("zod").ZodString>;
371
- }, import("zod/v4/core").$strip>>;
372
- related_identifiers: import("zod").ZodArray<import("zod").ZodObject<{
373
- identifier: import("zod").ZodString;
374
- scheme: import("zod").ZodOptional<import("zod").ZodString>;
375
- relation: import("zod").ZodOptional<import("zod").ZodString>;
376
- resource_type: import("zod").ZodOptional<import("zod").ZodString>;
377
- }, import("zod/v4/core").$strip>>;
378
- related_identifier_count: import("zod").ZodNumber;
379
- code_repository: import("zod").ZodOptional<import("zod").ZodString>;
380
- communities: import("zod").ZodArray<import("zod").ZodObject<{
381
- slug: import("zod").ZodString;
382
- title: import("zod").ZodOptional<import("zod").ZodString>;
383
- }, import("zod/v4/core").$strip>>;
384
- versions: import("zod").ZodObject<{
385
- index: import("zod").ZodOptional<import("zod").ZodNumber>;
386
- is_latest: import("zod").ZodOptional<import("zod").ZodBoolean>;
387
- latest_recid: import("zod").ZodOptional<import("zod").ZodString>;
388
- }, import("zod/v4/core").$strip>;
389
- stats: import("zod").ZodOptional<import("zod").ZodObject<{
390
- this_version: import("zod").ZodObject<{
391
- views: import("zod").ZodOptional<import("zod").ZodNumber>;
392
- downloads: import("zod").ZodOptional<import("zod").ZodNumber>;
393
- }, import("zod/v4/core").$strip>;
394
- all_versions: import("zod").ZodObject<{
395
- views: import("zod").ZodOptional<import("zod").ZodNumber>;
396
- downloads: import("zod").ZodOptional<import("zod").ZodNumber>;
397
- }, import("zod/v4/core").$strip>;
398
- }, import("zod/v4/core").$strip>>;
399
- files: import("zod").ZodObject<{
400
- enabled: import("zod").ZodBoolean;
401
- count: import("zod").ZodOptional<import("zod").ZodNumber>;
402
- total_bytes: import("zod").ZodOptional<import("zod").ZodNumber>;
403
- shown: import("zod").ZodNumber;
404
- entries: import("zod").ZodArray<import("zod").ZodObject<{
405
- key: import("zod").ZodString;
406
- size: import("zod").ZodOptional<import("zod").ZodNumber>;
407
- mimetype: import("zod").ZodOptional<import("zod").ZodString>;
408
- md5: import("zod").ZodOptional<import("zod").ZodString>;
409
- download_url: import("zod").ZodString;
410
- }, import("zod/v4/core").$strip>>;
411
- }, import("zod/v4/core").$strip>;
412
- revision: import("zod").ZodOptional<import("zod").ZodNumber>;
413
- zenodo_url: import("zod").ZodString;
414
- }, import("zod/v4/core").$strip>>;
415
- citation: import("zod").ZodOptional<import("zod").ZodString>;
416
- citation_style: import("zod").ZodOptional<import("zod").ZodString>;
417
- }, import("zod/v4/core").$strip>, readonly [{
418
- readonly reason: "invalid_identifier";
419
- readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
420
- readonly when: "id matches no accepted form, or is a GitHub-badge latestdoi id or a non-zenodo.org host";
421
- readonly recovery: "Pass a Zenodo record id (22705923), a DOI (10.5281/zenodo.22705923), or a zenodo.org/records URL as id; for a title or keyword, use zenodo_search_records.";
422
- }, {
423
- readonly reason: "record_unavailable";
424
- readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ServiceUnavailable;
425
- readonly when: "Zenodo could not serve this record: HTTP 500 after one retry, HTTP 500 while resolving a non-Zenodo DOI, or no complete response within the time budget (its gateway cuts off near 30 s, which deposits with about 10,000 or more files hit)";
426
- readonly thrownBy: "service";
427
- readonly recovery: "Look the record up with zenodo_search_records using query doi:\"<its DOI>\" (all_versions true) or its title; withdrawn legacy records and deposits with more than about 10,000 files fail on the record endpoint, and if a keyword search also fails, Zenodo is degraded.";
428
- }, {
429
- readonly reason: "rate_limited";
430
- readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.RateLimited;
431
- readonly when: "Zenodo answered HTTP 429, or this server's shared Zenodo request budget is spent for the current window";
432
- readonly retryable: true;
433
- readonly thrownBy: "service";
434
- readonly recovery: "Wait for the retryAfter seconds in the error data, then call zenodo_get_record again with the same arguments.";
435
- }], {
436
- readonly truncated: import("zod").ZodBoolean;
437
- readonly shown: import("zod").ZodNumber;
438
- readonly cap: import("zod").ZodNumber;
439
- readonly notice: import("zod").ZodOptional<import("zod").ZodString>;
440
542
  }> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
441
543
  id: import("zod").ZodString;
442
544
  archive_key: import("zod").ZodPreprocess<import("zod").ZodOptional<import("zod").ZodString>, unknown>;
@@ -650,6 +752,7 @@ export declare const allToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolD
650
752
  }>;
651
753
  query: import("zod").ZodOptional<import("zod").ZodString>;
652
754
  total: import("zod").ZodNumber;
755
+ reachable: import("zod").ZodNumber;
653
756
  page: import("zod").ZodNumber;
654
757
  size: import("zod").ZodNumber;
655
758
  has_more: import("zod").ZodBoolean;
@@ -702,7 +805,7 @@ export declare const allToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolD
702
805
  }, {
703
806
  readonly reason: "result_window_exceeded";
704
807
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
705
- readonly when: "page × size exceeds 10,000";
808
+ readonly when: "The page starts past the first 10,000 entries: (page − 1) × size is 10,000 or more, counting a size above 25 as 25";
706
809
  readonly recovery: "Narrow the query and call zenodo_lookup_vocabulary again instead of paging past 10,000 entries.";
707
810
  }, {
708
811
  readonly reason: "rate_limited";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;AAEzF,mDAAmD;AACnD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAO9B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;AAEzF,mDAAmD;AACnD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAO9B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"list-files.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/list-files.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAsBjE,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6YpB,CAAC"}
1
+ {"version":3,"file":"list-files.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/list-files.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AA0BjE,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkZpB,CAAC"}
@@ -9,8 +9,11 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
9
9
  import { parseRecordRef } from '../../../services/zenodo/identifiers.js';
10
10
  import { isZip, previewMode } from '../../../services/zenodo/text-preview.js';
11
11
  import { getZenodoService } from '../../../services/zenodo/zenodo-service.js';
12
+ import { applyCap } from '../paging.js';
12
13
  import { inline } from '../render.js';
13
14
  import { blankToUndefined } from '../schema-helpers.js';
15
+ /** Entries per page: a larger `limit` is served at this many. */
16
+ const LIMIT_CAP = 200;
14
17
  /** True when zenodo_read_file reads the file rather than refusing it unread. */
15
18
  const readsAsText = (key, mimetype) => previewMode(key, mimetype) !== 'binary';
16
19
  const PREVIEWABLE_DESCRIPTION = 'True when zenodo_read_file reads this as text: a text MIME type or extension, or an extensionless application/octet-stream file (LICENSE, Makefile) whose content is checked on read and returned only if it is UTF-8 text.';
@@ -38,7 +41,13 @@ export const listFiles = tool('zenodo_list_files', {
38
41
  .preprocess(blankToUndefined, z.string().max(200).optional())
39
42
  .describe('Case-insensitive substring filter on file keys (manifest) or member paths (archive), applied before paging.'),
40
43
  offset: z.number().int().min(0).default(0).describe('Entries to skip before this page.'),
41
- limit: z.number().int().min(1).max(200).default(50).describe('Entries per page (1–200).'),
44
+ limit: z
45
+ .number()
46
+ .int()
47
+ .min(1)
48
+ .max(10_000)
49
+ .default(50)
50
+ .describe('Entries per page, at most 200 per call; larger values return 200.'),
42
51
  }),
43
52
  output: z.object({
44
53
  recid: z
@@ -58,7 +67,7 @@ export const listFiles = tool('zenodo_list_files', {
58
67
  files_enabled: z.boolean().describe('False for a metadata-only deposit.'),
59
68
  key_contains: z.string().optional().describe('The key filter applied, when set.'),
60
69
  offset: z.number().describe('Entries skipped before this page.'),
61
- limit: z.number().describe('Page size applied.'),
70
+ limit: z.number().describe('Page size applied: limit, or 200 when limit asked for more.'),
62
71
  matched: z.number().describe('Entries matching the filter, before paging.'),
63
72
  has_more: z.boolean().describe('True when more matching entries follow this page.'),
64
73
  next_offset: z.number().optional().describe('Offset of the next page, when has_more.'),
@@ -122,12 +131,12 @@ export const listFiles = tool('zenodo_list_files', {
122
131
  enrichment: {
123
132
  truncated: z.boolean().describe('True when more matching entries follow this page.'),
124
133
  shown: z.number().describe('Entries returned on this page.'),
125
- cap: z.number().describe('Page size applied.'),
134
+ cap: z.number().describe('Page size applied (at most 200).'),
126
135
  totalCount: z.number().describe('Entries matching the filter, before paging.'),
127
136
  notice: z
128
137
  .string()
129
138
  .optional()
130
- .describe('File access notes, empty-filter guidance, listing caps, or paging guidance.'),
139
+ .describe('File access notes, empty-filter guidance, listing caps, the 200-per-call limit cap, or paging guidance.'),
131
140
  },
132
141
  errors: [
133
142
  {
@@ -184,7 +193,8 @@ export const listFiles = tool('zenodo_list_files', {
184
193
  },
185
194
  ],
186
195
  async handler(input, ctx) {
187
- ctx.enrich({ truncated: false, shown: 0, cap: input.limit, totalCount: 0 });
196
+ const { applied: limit, capNote } = applyCap('limit', input.limit, LIMIT_CAP);
197
+ ctx.enrich({ truncated: false, shown: 0, cap: limit, totalCount: 0 });
188
198
  const ref = parseRecordRef(input.id);
189
199
  if (ref.kind === 'invalid') {
190
200
  throw ctx.fail('invalid_identifier', ref.message);
@@ -221,7 +231,7 @@ export const listFiles = tool('zenodo_list_files', {
221
231
  files_enabled: files.enabled,
222
232
  ...(input.key_contains ? { key_contains: input.key_contains } : {}),
223
233
  offset: input.offset,
224
- limit: input.limit,
234
+ limit,
225
235
  };
226
236
  const needle = input.key_contains?.toLowerCase();
227
237
  if (files.enabled && access.files === 'restricted') {
@@ -245,15 +255,11 @@ export const listFiles = tool('zenodo_list_files', {
245
255
  : `No ${noun} contain "${filter}"; call zenodo_list_files again without key_contains.`);
246
256
  }
247
257
  else if (page.matched > 0 && input.offset >= page.matched) {
248
- notice.push(`Offset ${input.offset} is past the last of ${page.matched} ${noun}; call again with a lower offset.`);
258
+ notice.push(`Offset ${input.offset} is past the last of ${page.matched.toLocaleString('en-US')} ${noun}; call again with a lower offset.`);
249
259
  }
250
260
  if (page.hasMore) {
251
- notice.push(`Showing ${page.slice.length} of ${page.matched}; call again with offset ${page.nextOffset}.`);
252
- ctx.enrich.truncated({
253
- shown: page.slice.length,
254
- cap: input.limit,
255
- guidance: notice.join(' '),
256
- });
261
+ notice.push(`Showing ${page.slice.length} of ${page.matched.toLocaleString('en-US')}${capNote ? ` (${capNote})` : ''}; call again with offset ${page.nextOffset}.`);
262
+ ctx.enrich.truncated({ shown: page.slice.length, cap: limit, guidance: notice.join(' ') });
257
263
  }
258
264
  else if (notice.length) {
259
265
  ctx.enrich.notice(notice.join(' '));
@@ -270,7 +276,7 @@ export const listFiles = tool('zenodo_list_files', {
270
276
  const matching = needle
271
277
  ? files.entries.filter((e) => e.key.toLowerCase().includes(needle))
272
278
  : files.entries;
273
- const page = pageOf(matching, input.offset, input.limit);
279
+ const page = pageOf(matching, input.offset, limit);
274
280
  const paging = finish(page, 'files');
275
281
  return {
276
282
  ...common,
@@ -304,7 +310,7 @@ export const listFiles = tool('zenodo_list_files', {
304
310
  const matching = needle
305
311
  ? listing.members.filter((m) => m.path.toLowerCase().includes(needle))
306
312
  : listing.members;
307
- const page = pageOf(matching, input.offset, input.limit);
313
+ const page = pageOf(matching, input.offset, limit);
308
314
  const paging = finish(page, 'members', listing.upstream_truncated);
309
315
  return {
310
316
  ...common,