mcp-databnf 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,305 @@
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
+ maxRetries: number;
16
+ cacheTtlMs: number;
17
+ cacheMaxEntries: number;
18
+ logLevel: LogLevel;
19
+ }
20
+ interface Logger {
21
+ debug(message: string): void;
22
+ info(message: string): void;
23
+ warn(message: string): void;
24
+ error(message: string): void;
25
+ }
26
+
27
+ /** The shapes the access layer produces. Nothing here knows about MCP. */
28
+ /**
29
+ * A link to a digitised document held on Gallica.
30
+ *
31
+ * This server describes the link and never opens it. `url` is for a person to
32
+ * follow, and nothing downstream reports what is behind it.
33
+ */
34
+ interface DigitisedLink {
35
+ /** The Gallica ARK, as it identifies the document. */
36
+ ark: string;
37
+ /** The address to open, exactly as data.bnf.fr publishes it. */
38
+ url: string;
39
+ /**
40
+ * Which statement attached this link to the record.
41
+ *
42
+ * `reproduction` is the digitised copy of the edition itself.
43
+ * `ocr` is the text a machine read off that copy.
44
+ * `depiction` is an image data.bnf.fr uses to illustrate the record, which
45
+ * can be a portrait, a title page, or a page of a newspaper that mentions the
46
+ * subject: it illustrates, and it does not stand for the work.
47
+ */
48
+ role: "reproduction" | "ocr" | "depiction";
49
+ /** Which record carried it, so a caller can say where a link came from. */
50
+ fromId: string;
51
+ fromTitle: string | null;
52
+ }
53
+ /** One row of an author search. */
54
+ interface AuthorSummary {
55
+ /** The identifier get_author takes. */
56
+ id: string;
57
+ /** The name as data.bnf.fr writes it on the person. */
58
+ name: string | null;
59
+ /** The heading as the authority file writes it, usually with the dates. */
60
+ label: string | null;
61
+ birthYear: number | null;
62
+ deathYear: number | null;
63
+ /** What the record says the person did, often a single word. */
64
+ role: string | null;
65
+ sourceUrl: string;
66
+ }
67
+ /** One person, read in full. */
68
+ interface AuthorDetail {
69
+ id: string;
70
+ name: string | null;
71
+ label: string | null;
72
+ givenName: string | null;
73
+ familyName: string | null;
74
+ /** Other headings the record carries, each with the language it is written in. */
75
+ otherNames: Array<{
76
+ label: string;
77
+ language: string | null;
78
+ }>;
79
+ birthDate: string | null;
80
+ deathDate: string | null;
81
+ birthYear: number | null;
82
+ deathYear: number | null;
83
+ birthPlace: string | null;
84
+ deathPlace: string | null;
85
+ /**
86
+ * What the record states about the person, which is a job title far more
87
+ * often than a sentence.
88
+ */
89
+ biographicalInformation: string | null;
90
+ /** The occupation the record names. */
91
+ occupation: string | null;
92
+ /** Languages, as ISO 639-2 codes the record points at. */
93
+ languages: string[];
94
+ /** Countries, as the codes the record points at. */
95
+ countries: string[];
96
+ /** Fields of activity in words, as the record writes them. */
97
+ fields: string[];
98
+ /** Dewey classes the fields of activity point at, as `800` rather than a URL. */
99
+ deweyClasses: string[];
100
+ /** Addresses for the same person in other files, grouped by the file. */
101
+ sameAs: Record<string, string[]>;
102
+ /** The classes the record is typed with, which say what kind of thing it is. */
103
+ types: string[];
104
+ /** True when the record was longer than one query reads, so parts are missing. */
105
+ truncated: boolean;
106
+ /** The record in the BnF general catalogue. */
107
+ catalogueUrl: string | null;
108
+ /** When the authority record was created and last changed, as published. */
109
+ recordCreated: string | null;
110
+ recordModified: string | null;
111
+ /** Images data.bnf.fr attaches to the person, as links. */
112
+ depictions: DigitisedLink[];
113
+ sourceUrl: string;
114
+ }
115
+ /** One row of a work search. */
116
+ interface WorkSummary {
117
+ id: string;
118
+ title: string | null;
119
+ /** The year the record gives the work, as published. */
120
+ date: string | null;
121
+ /** Everyone the record credits with the work. */
122
+ creators: Array<{
123
+ id: string;
124
+ name: string | null;
125
+ }>;
126
+ /**
127
+ * Whether the BnF has established this work as a record of its own, or holds
128
+ * it provisionally while a cataloguer settles it.
129
+ */
130
+ status: "established" | "provisional";
131
+ sourceUrl: string;
132
+ }
133
+ /** One work, read in full. */
134
+ interface WorkDetail {
135
+ id: string;
136
+ title: string | null;
137
+ /** The label the record carries, which can differ from the title in case. */
138
+ label: string | null;
139
+ date: string | null;
140
+ firstYear: number | null;
141
+ creators: Array<{
142
+ id: string;
143
+ name: string | null;
144
+ }>;
145
+ /** Languages of the work, as the codes the record points at. */
146
+ languages: string[];
147
+ /** Forms, as the words the BnF vocabulary uses. */
148
+ forms: string[];
149
+ /** Subjects in words. */
150
+ subjects: string[];
151
+ /** Dewey classes the subjects point at. */
152
+ deweyClasses: string[];
153
+ status: "established" | "provisional";
154
+ /** What the record itself says about how settled it is. */
155
+ statusStatement: string | null;
156
+ /**
157
+ * How many expressions the record links, which is not a count of editions.
158
+ * Null when the record was longer than one query reads, since the number
159
+ * would then be the ceiling rather than the count.
160
+ */
161
+ expressionCount: number | null;
162
+ sameAs: Record<string, string[]>;
163
+ /** The classes the record is typed with, which say what kind of thing it is. */
164
+ types: string[];
165
+ /** True when the record was longer than one query reads, so parts are missing. */
166
+ truncated: boolean;
167
+ catalogueUrl: string | null;
168
+ depictions: DigitisedLink[];
169
+ sourceUrl: string;
170
+ }
171
+ /** One published edition of a work. */
172
+ interface Edition {
173
+ id: string;
174
+ title: string | null;
175
+ /** The date as published, which can be a phrase such as `[s.d.]`. */
176
+ date: string | null;
177
+ year: number | null;
178
+ publisher: string | null;
179
+ place: string | null;
180
+ /** The edition statement, when the record carries one. */
181
+ editionStatement: string | null;
182
+ /** The extent, in the words of the record: pages, volumes, discs. */
183
+ extent: string | null;
184
+ isbn: string | null;
185
+ /** Notes the cataloguer wrote, as published. */
186
+ note: string | null;
187
+ /** The record in the BnF general catalogue. */
188
+ catalogueUrl: string | null;
189
+ /** Digitised copies of this edition, as links this server does not open. */
190
+ digitised: DigitisedLink[];
191
+ sourceUrl: string;
192
+ }
193
+ /** A page of rows, and whether the endpoint held more. */
194
+ interface Page<T> {
195
+ rows: T[];
196
+ /**
197
+ * True when the endpoint answered with at least one row beyond the page.
198
+ *
199
+ * The tools report this rather than a total: counting every match means a
200
+ * second query over the same span, and a count would read as a ranking on a
201
+ * search that does not rank.
202
+ */
203
+ hasMore: boolean;
204
+ /** Rows the endpoint sent that could not be read, present only when some were. */
205
+ skipped?: number;
206
+ }
207
+
208
+ type EntityKind = "ark" | "temp-work";
209
+ interface EntityId {
210
+ kind: EntityKind;
211
+ /** The bare identifier, as a person would quote it. */
212
+ id: string;
213
+ /** The address of the thing itself, which the queries bind to. */
214
+ iri: string;
215
+ /** The page a person can open. */
216
+ pageUrl: string;
217
+ }
218
+
219
+ /**
220
+ * The one place that talks to data.bnf.fr.
221
+ *
222
+ * It holds a single rate limiter and a single cache, so pacing applies to the
223
+ * server as a whole rather than to whichever tool happens to be running. It
224
+ * imports nothing from the MCP layer and is published on its own, so the same
225
+ * code serves a plain script.
226
+ *
227
+ * Every read runs a query, parses it and only then stores: an answer nobody
228
+ * could parse must not be served back for the rest of the cache's lifetime.
229
+ *
230
+ * Every read also carries the moment the metadata came off the endpoint. The
231
+ * licence asks for the date of retrieval alongside the source, so that date is
232
+ * a value the layer produces rather than something a caller is trusted to add.
233
+ */
234
+
235
+ interface ClientOptions {
236
+ config?: Partial<Config>;
237
+ logger?: Logger;
238
+ fetchImpl?: typeof fetch;
239
+ }
240
+ /** Every read reports where the value came from and when it was retrieved. */
241
+ interface Read<T> {
242
+ data: T;
243
+ cached: boolean;
244
+ /**
245
+ * When the metadata came off data.bnf.fr, as an ISO 8601 instant.
246
+ *
247
+ * A cached answer keeps the moment it was first read. Reporting the moment it
248
+ * was served would state a date of retrieval that never happened, which is
249
+ * exactly the claim the licence asks to be accurate.
250
+ */
251
+ retrievedAt: string;
252
+ /** Rows the endpoint sent that could not be read, which paging still counts. */
253
+ skipped?: number;
254
+ }
255
+ declare class BnfClient {
256
+ private readonly config;
257
+ private readonly logger;
258
+ private readonly limiter;
259
+ private readonly cache;
260
+ /** Questions already on the wire, so an identical one waits instead of asking. */
261
+ private readonly inFlight;
262
+ private readonly fetchImpl;
263
+ constructor(options?: ClientOptions);
264
+ /** The pacing in force, which widens when the endpoint pushes back. */
265
+ get intervalMs(): number;
266
+ /** What the BnF sees this client call itself. */
267
+ get userAgent(): string;
268
+ /** The deadline one query is given, which callers report rather than guess. */
269
+ get timeoutMs(): number;
270
+ /** The one address this client sends anything to. */
271
+ get endpoint(): string;
272
+ /**
273
+ * `cacheKey` is stated rather than taken from the query, because two reads of
274
+ * one query can produce different values: a list cut to five rows is not the
275
+ * same value as the same list cut to twenty, and serving one for the other
276
+ * would answer a size the caller never asked for.
277
+ */
278
+ private read;
279
+ private fetchAndParse;
280
+ /** Read an identifier a caller wrote, refusing anything this dataset cannot address. */
281
+ identify(input: string): EntityId;
282
+ /** The words a search will actually look for, which a tool reports back. */
283
+ words(input: string): string[];
284
+ searchAuthors(name: string, limit: number, offset: number): Promise<Read<Page<AuthorSummary>>>;
285
+ getAuthor(id: EntityId): Promise<Read<AuthorDetail>>;
286
+ /**
287
+ * Say what an address is, when it is not the kind of thing that was asked for.
288
+ *
289
+ * A record the catalogue does not describe at all and a record describing
290
+ * something else are two different answers, and only one of them means the
291
+ * caller should go looking elsewhere.
292
+ */
293
+ private refuse;
294
+ searchWorks(title: string, limit: number, offset: number): Promise<Read<Page<WorkSummary>>>;
295
+ getWork(id: EntityId): Promise<Read<WorkDetail>>;
296
+ listEditions(id: EntityId, limit: number, offset: number): Promise<Read<Page<Edition>>>;
297
+ /** Digitised documents attached to a work and to its editions. */
298
+ digitisedForWork(id: EntityId, limit: number): Promise<Read<Page<DigitisedLink>>>;
299
+ /** Digitised documents attached to a person and to the editions of their works. */
300
+ digitisedForPerson(id: EntityId, limit: number): Promise<Read<Page<DigitisedLink>>>;
301
+ /** What an address is typed as, which tells a person from a work. */
302
+ types(id: EntityId): Promise<Read<string[]>>;
303
+ }
304
+
305
+ export { BnfClient, type ClientOptions, type Read };