mcp-libraryofcongress 1.0.0

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.
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Settings, read from the environment.
3
+ *
4
+ * A value that cannot be read warns and falls back rather than stopping the
5
+ * server: a typo in one variable should not take away every tool. Warnings go
6
+ * to stderr, because stdout carries the protocol and anything written there
7
+ * corrupts the session.
8
+ */
9
+ declare const LOG_LEVELS: readonly ["silent", "error", "info", "debug"];
10
+ type LogLevel = (typeof LOG_LEVELS)[number];
11
+ interface Config {
12
+ userAgent: string;
13
+ minIntervalMs: number;
14
+ timeoutMs: number;
15
+ /** Deadline for the full-text newspaper route, which is the slow one. */
16
+ newspaperTimeoutMs: number;
17
+ maxRetries: number;
18
+ cacheTtlMs: number;
19
+ cacheMaxEntries: number;
20
+ logLevel: LogLevel;
21
+ }
22
+ interface Logger {
23
+ debug(message: string): void;
24
+ info(message: string): void;
25
+ warn(message: string): void;
26
+ error(message: string): void;
27
+ }
28
+
29
+ /** The shapes the API layer produces. Nothing here knows about MCP. */
30
+ /** A catalogue row, trimmed to what picks one record out of a list. */
31
+ interface RecordSummary {
32
+ /** Opaque, and the string get_item takes. It can carry slashes. */
33
+ identifier: string | null;
34
+ title: string | null;
35
+ /** Whoever the Library credits: author, photographer, publisher, performer. */
36
+ creator: string | null;
37
+ /** Read off the date the record carries, when that date names a year. */
38
+ year: number | null;
39
+ /** The date exactly as published, which is often a range or a phrase. */
40
+ date: string | null;
41
+ /** What the Library calls the physical thing: book, photo, map, newspaper. */
42
+ format: string | null;
43
+ /** Places the record is catalogued under. */
44
+ location: string[];
45
+ subjects: string[];
46
+ /** Whether a digitised copy can be read online. */
47
+ online: boolean;
48
+ sourceUrl: string;
49
+ }
50
+ /**
51
+ * How many results a query has, and how the site divides them into pages.
52
+ *
53
+ * The two numbers are kept apart on purpose. The site reports the count of
54
+ * results and the count of pages under names that read alike, and reporting the
55
+ * page count as a result count multiplies or divides an answer by the page size.
56
+ */
57
+ interface Paging {
58
+ /** Results matching the query across every page. */
59
+ resultCount: number;
60
+ /** Pages those results are divided into, at the page size in force. */
61
+ pageCount: number | null;
62
+ /** The page this answer came from, as the site numbers them. */
63
+ currentPage: number | null;
64
+ /** Results a page holds, as the site applied it. */
65
+ perPage: number | null;
66
+ }
67
+ interface SearchResults {
68
+ paging: Paging;
69
+ records: RecordSummary[];
70
+ }
71
+ /**
72
+ * One newspaper page whose machine-read text matched.
73
+ *
74
+ * The text a search returns with a page is the opening of what optical
75
+ * recognition read off it, not the whole page, so the words searched for are
76
+ * often further down than the response reaches. `wordsLocated` says which of
77
+ * the two happened, and the excerpts are cut accordingly.
78
+ */
79
+ interface NewspaperHit {
80
+ identifier: string | null;
81
+ title: string | null;
82
+ creator: string | null;
83
+ year: number | null;
84
+ /** The page within the issue, as the Library numbers it. */
85
+ pageNumber: number | null;
86
+ /** Date of the issue, as published: usually YYYY-MM-DD. */
87
+ publishedOn: string | null;
88
+ /** The newspaper itself, with the years it ran. */
89
+ publication: string | null;
90
+ /** The state the paper was published in, as the Library records it. */
91
+ state: string | null;
92
+ /** True when the searched words were found in the text this row carries. */
93
+ wordsLocated: boolean;
94
+ /** Passages of machine-read text, cut to the budget the caller set. */
95
+ excerpts: string[];
96
+ /** Opens the page itself, already at the right leaf with the query applied. */
97
+ sourceUrl: string;
98
+ }
99
+ interface NewspaperResults {
100
+ paging: Paging;
101
+ hits: NewspaperHit[];
102
+ }
103
+ interface ItemDetail {
104
+ identifier: string;
105
+ title: string | null;
106
+ creator: string | null;
107
+ year: number | null;
108
+ date: string | null;
109
+ format: string | null;
110
+ /** Every paragraph the Library publishes as the description, joined. */
111
+ description: string | null;
112
+ notes: string[];
113
+ subjects: string[];
114
+ location: string[];
115
+ language: string[];
116
+ /** Collections and divisions the record sits in. */
117
+ partOf: string[];
118
+ repository: string | null;
119
+ callNumber: string | null;
120
+ /**
121
+ * What the Library says about reuse. Absent on most records, and silence
122
+ * here is not permission.
123
+ */
124
+ rights: string | null;
125
+ /** Ready-made citations, in the styles the Library publishes. */
126
+ citations: Record<string, string>;
127
+ /** Files that can be downloaded or viewed, one row per served copy. */
128
+ resources: ItemResource[];
129
+ sourceUrl: string;
130
+ /** Every field the Library published for the record, for the caller who wants it. */
131
+ raw: Record<string, unknown> | null;
132
+ }
133
+ interface ItemResource {
134
+ caption: string | null;
135
+ /** Number of files behind this copy, when the Library counts them. */
136
+ fileCount: number | null;
137
+ url: string | null;
138
+ imageUrl: string | null;
139
+ }
140
+ interface CollectionSummary {
141
+ /** The slug the collection is addressed by, and the string get_item takes. */
142
+ identifier: string | null;
143
+ title: string;
144
+ description: string | null;
145
+ /** Records gathered in the collection, when the Library counts them. */
146
+ itemCount: number | null;
147
+ subjects: string[];
148
+ formats: string[];
149
+ sourceUrl: string;
150
+ /** Lists what the collection holds, one page at a time. */
151
+ itemsUrl: string | null;
152
+ }
153
+ interface CollectionResults {
154
+ paging: Paging;
155
+ collections: CollectionSummary[];
156
+ }
157
+
158
+ /**
159
+ * Turning the Library's answers into the shapes this server publishes.
160
+ *
161
+ * Two rules run through every function here. A response that cannot be read is
162
+ * a `parse_failure`, never an empty result, because a caller cannot tell an
163
+ * empty result from an absence and will report one as the other. And a single
164
+ * unreadable row among many is skipped rather than failing the page it sat in;
165
+ * where a parser is given a counter, the rows it dropped are reported.
166
+ */
167
+
168
+ interface ExcerptBudget {
169
+ /** Characters one passage may run to. */
170
+ maxChars: number;
171
+ /** Passages kept for one page. */
172
+ maxCount: number;
173
+ }
174
+
175
+ /**
176
+ * One route per kind of thing, which is how the catalogue is divided.
177
+ *
178
+ * The vocabulary is the Library's own: a route is a path segment, and the
179
+ * values a tool accepts are exactly these keys.
180
+ */
181
+ declare const FORMAT_ROUTE: {
182
+ readonly books: "/books/";
183
+ readonly photos: "/photos/";
184
+ readonly maps: "/maps/";
185
+ readonly audio: "/audio/";
186
+ readonly "film-and-videos": "/film-and-videos/";
187
+ readonly manuscripts: "/manuscripts/";
188
+ readonly "notated-music": "/notated-music/";
189
+ readonly newspapers: "/newspapers/";
190
+ };
191
+ type FormatRoute = keyof typeof FORMAT_ROUTE;
192
+ /**
193
+ * Facet fields a filter may name, and nothing else.
194
+ *
195
+ * A facet the site does not know is neither refused nor applied: it answers
196
+ * with the unfiltered result and no sign that the narrowing was dropped. Only
197
+ * fields listed here are ever sent, so a filter that reaches the site is one
198
+ * the site acts on.
199
+ */
200
+ declare const FACET_FIELD: {
201
+ readonly subject: "subject";
202
+ readonly location: "location";
203
+ readonly language: "language";
204
+ readonly contributor: "contributor";
205
+ readonly partof: "partof";
206
+ /** The state a newspaper was published in, as the newspaper corpus files it. */
207
+ readonly state: "location_state";
208
+ /** A newspaper, named with the town and the years it ran. */
209
+ readonly publication: "partof_title";
210
+ };
211
+ type FacetField = keyof typeof FACET_FIELD;
212
+ /**
213
+ * Sort orders, mapped from the words the tools use.
214
+ *
215
+ * Relevance sends nothing, which is the site's own default.
216
+ */
217
+ declare const SORT: {
218
+ readonly relevance: "";
219
+ readonly newest: "date_desc";
220
+ readonly oldest: "date";
221
+ readonly title: "title_s";
222
+ };
223
+ type SortKey = keyof typeof SORT;
224
+
225
+ /**
226
+ * Builders for every address the client fetches.
227
+ *
228
+ * Query strings are assembled here so no caller can smuggle a parameter past
229
+ * the checks: which blocks come back, which facet fields may be named and
230
+ * which sort orders exist are decided by this file, not by whoever calls a
231
+ * tool. A facet field or a sort order the site does not know is accepted
232
+ * silently and applied to nothing, so sending only known ones is what keeps a
233
+ * filter from being quietly dropped.
234
+ */
235
+
236
+ type Facets = Partial<Record<FacetField, string>>;
237
+ interface CatalogueQuery {
238
+ query: string;
239
+ format: FormatRoute;
240
+ facets?: Facets;
241
+ yearFrom?: number;
242
+ yearTo?: number;
243
+ sort?: SortKey;
244
+ /**
245
+ * When false, the search widens past what the Library has digitised and
246
+ * takes in catalogue records with no copy to read online.
247
+ */
248
+ onlineOnly: boolean;
249
+ limit: number;
250
+ page: number;
251
+ }
252
+ /**
253
+ * The narrowing a full-text newspaper search may carry.
254
+ *
255
+ * The corpus files a page under the state it was published in and under the
256
+ * paper it belongs to, and both are facets the route acts on.
257
+ */
258
+ interface NewspaperFilters {
259
+ facets?: Facets;
260
+ yearFrom?: number;
261
+ yearTo?: number;
262
+ }
263
+
264
+ /**
265
+ * The one place that talks to the Library of Congress.
266
+ *
267
+ * It holds a single rate limiter and a single cache, so pacing applies to the
268
+ * server as a whole rather than to whichever tool happens to be running. It
269
+ * imports nothing from the MCP layer and is published on its own, so the same
270
+ * code serves a plain script.
271
+ *
272
+ * Every read fetches, parses and only then stores: a response nobody could
273
+ * parse must not be served back for the rest of the cache's lifetime.
274
+ */
275
+
276
+ interface ClientOptions {
277
+ config?: Partial<Config>;
278
+ logger?: Logger;
279
+ fetchImpl?: typeof fetch;
280
+ }
281
+ /** Every read reports whether it went out, so a caller can say what it knows. */
282
+ interface Read<T> {
283
+ data: T;
284
+ cached: boolean;
285
+ /** Rows the site sent that could not be read, which paging still counts. */
286
+ skipped?: number;
287
+ }
288
+ declare class LocClient {
289
+ private readonly config;
290
+ private readonly logger;
291
+ private readonly limiter;
292
+ private readonly cache;
293
+ private readonly fetchImpl;
294
+ constructor(options?: ClientOptions);
295
+ /** The pacing in force, which widens when the Library pushes back. */
296
+ get intervalMs(): number;
297
+ /** What the Library sees this client call itself. */
298
+ get userAgent(): string;
299
+ /**
300
+ * `cacheKey` is stated rather than taken from the address, because two reads
301
+ * of one address can produce different values: a passage cut to three hundred
302
+ * characters is not the same value as the same passage cut to a thousand, and
303
+ * serving one for the other would answer a budget the caller never set.
304
+ */
305
+ private read;
306
+ searchItems(query: CatalogueQuery): Promise<Read<SearchResults>>;
307
+ searchNewspapers(query: string, limit: number, page: number, budget: ExcerptBudget, filters?: NewspaperFilters): Promise<Read<NewspaperResults>>;
308
+ getItem(identifier: string): Promise<Read<ItemDetail>>;
309
+ listCollections(limit: number, page: number): Promise<Read<CollectionResults>>;
310
+ }
311
+
312
+ export { type ClientOptions, LocClient, type Read };