mcp-databnf 2.0.0 → 2.1.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.
package/README.md CHANGED
@@ -9,33 +9,36 @@
9
9
  [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=databnf&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1kYXRhYm5mIl19)
10
10
  [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=databnf&config=%7B%22name%22%3A%22databnf%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-databnf%22%5D%7D)
11
11
 
12
- An MCP server for [data.bnf.fr](https://data.bnf.fr), the open catalogue of the
13
- Bibliothèque nationale de France. Look up an author, find a work, list the
14
- editions the BnF holds of it, list what a person wrote, and gather the links to
15
- what has been digitised.
12
+ [data.bnf.fr](https://data.bnf.fr) is the open data service of the Bibliothèque
13
+ nationale de France. It publishes the authority records the national library
14
+ maintains: the people it catalogues, with their dates, their places, their
15
+ languages and their fields of activity; the works they wrote, with the editions
16
+ each work was published in; and the links to the copies digitised in Gallica. A
17
+ record states whether the library considers it established or still provisional.
16
18
 
17
- No API key. No account. Read-only.
19
+ This server connects a chat client to that service. You can search for an author
20
+ or a work by name, read a record in full, list what an author wrote, list the
21
+ editions of a work, and find the digitised copies attached to either. It needs no
22
+ API key and no account.
23
+
24
+ _[Version française](#mcp-databnf-français)_
18
25
 
19
26
  ---
20
27
 
21
- ## What it is for
28
+ ## Install
22
29
 
23
- The BnF publishes its authority file and its bibliographic records as linked
24
- data, and answers questions about them over SPARQL. That dataset knows things a
25
- web search does not: which of two people bearing one name wrote a given book,
26
- what the BnF recorded as somebody's date and place of death, which editions of a
27
- work exist and who printed them, and which of those have been digitised.
30
+ **One-click install**
28
31
 
29
- This server asks those questions for you, in seven tools, and reports what the
30
- catalogue answers without adding to it.
32
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=databnf&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1kYXRhYm5mIl19)
33
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=databnf&config=%7B%22name%22%3A%22databnf%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-databnf%22%5D%7D)
31
34
 
32
- ## Install
35
+ **Claude Code**
33
36
 
34
37
  ```bash
35
- npx mcp-databnf
38
+ claude mcp add databnf -- npx -y mcp-databnf
36
39
  ```
37
40
 
38
- ### Claude Desktop, Claude Code, and other stdio clients
41
+ **Claude Desktop, Cursor, and any client using the standard config format**
39
42
 
40
43
  ```json
41
44
  {
@@ -48,6 +51,8 @@ npx mcp-databnf
48
51
  }
49
52
  ```
50
53
 
54
+ Node 24 or later is required, and no environment variable has to be set.
55
+
51
56
  ### With Docker
52
57
 
53
58
  ```json
@@ -55,230 +60,309 @@ npx mcp-databnf
55
60
  "mcpServers": {
56
61
  "databnf": {
57
62
  "command": "docker",
58
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-databnf:2.0.0"]
63
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-databnf:2.1.0"]
59
64
  }
60
65
  }
61
66
  }
62
67
  ```
63
68
 
64
- `-i` keeps stdin open, which is where the protocol travels, and no `-t` is
65
- passed: a TTY rewrites the stream and breaks it. The container needs outbound
66
- HTTPS to `data.bnf.fr`, `catalogue.bnf.fr` and `isni.org`, and nothing else: no volume, no port, no environment variable, no credential.
67
-
68
- ## The seven tools
69
-
70
- | Tool | Answers |
71
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
72
- | `search_authors` | Who does the BnF record under this name, and which record is which |
73
- | `get_author` | Dates, places, occupation, language, country, Dewey class, and the same person in VIAF, IdRef, DBpedia, Wikidata and ISNI |
74
- | `search_works` | Which works have these words in their title |
75
- | `get_work` | Title, creators, date, language, form, subject, and whether the record is established or provisional |
76
- | `list_works` | Which works the catalogue names a person the creator of, with the date and the form code it gives each |
77
- | `list_editions` | Publisher, place, year, edition statement, extent, ISBN, catalogue link, and the Gallica link when there is one |
78
- | `find_digitised` | Every digitised document the catalogue attaches to a person or a work, as links |
79
-
80
- A typical exchange asks `search_authors` for a name, reads the rows, and passes
81
- one identifier to `get_author`, to `list_works`, or on to `list_editions`.
82
-
83
- ## What it does not do, and why
84
-
85
- **It never reads Gallica.** The BnF puts its metadata and its digitised contents
86
- under two different regimes. The metadata this server reads may be reused freely
87
- provided the source and the date of retrieval are stated. The contents on
88
- gallica.bnf.fr are governed separately: their terms make use inside an
89
- artificial-intelligence project subject to a paid licence outside academic
90
- research, and the site refuses ClaudeBot and GPTBot at the server, then bans the
91
- calling address after about fifteen requests whatever the pace.
92
-
93
- So a Gallica address is treated here as what the catalogue says it is: a piece of
94
- metadata, rendered as a link for a person to open. The server will tell you that
95
- a 1873 Brussels printing of _Une saison en enfer_ has been digitised and give you
96
- its address. It will not tell you what is on page four, whether the scan is
97
- complete, or whether the document opens at all. A `bnf-onto:OCR` link names a
98
- machine-read text of a document; the server reports that the text exists and
99
- leaves it where it is. A test fails if any address on that host is ever built to
100
- be called.
101
-
102
- **It does not rank.** The BnF's full-text index answers whether a title or a name
103
- carries the words asked for. It returns no measure of how well, so the rows come
104
- back in the order the index holds them. Searching for `saison enfer` returns a
105
- dozen studies of Rimbaud before Rimbaud, and every one of them is a correct
106
- match. This server says so rather than inventing an order the catalogue does not
107
- support, and it reports no total, because a total on a search that does not rank
108
- reads as a measure of relevance.
109
-
110
- **A name is matched letter for letter, and only among the people.** The index
111
- compares the characters given against the characters a cataloguer entered, so
112
- `Dostoïevski`, `Dostoievski` and `Dostoevskij` are three searches reaching three
113
- different sets of records, and a handful of rows under one spelling is no
114
- evidence about the others. `search_authors` says so on every answer, and it reads
115
- the person records alone: an organisation or a conference is outside what it
116
- looked at, so an answer holding no row is never a statement that the authority
117
- file holds no such heading.
118
-
119
- **A list of works is one link, not a bibliography.** `list_works` walks
120
- `dcterms:creator`, the statement that ties a work to the person who made it.
121
- That statement is real and it is partial. The catalogue credits a person on a
122
- record in other ways, and the BnF holds printed editions whose work it has never
123
- established as a record of its own, so a title missing from the list is not a
124
- title the person did not write. Every answer says so, and none of them reports a
125
- total: a count of what one link reaches would be read as a count of what somebody
126
- wrote.
127
-
128
- **A form code says what a work declares, and an empty one says nothing.** A work
129
- record points at a term of the BnF's work-form vocabulary, and this dataset
130
- publishes no label for those terms. So `roman`, `poesi` and `te` arrive as the
131
- codes they are: some read as words and some do not. The catalogue also states no
132
- form at all on a great many works, and that silence is a form unstated rather
133
- than a genre denied. Keeping the rows that carry one code therefore finds the
134
- works declaring it and never all the works of that form, which is why the codes
135
- are reported and no filter is offered on them.
136
-
137
- **It does not write biographies.** The field the BnF calls biographical
138
- information is an occupation on most records: Rimbaud's says _Poète_, and that is
139
- the whole of it. `get_author` returns that word and says what it is.
140
-
141
- **It exposes no raw SPARQL tool.** An arbitrary query is an unbounded load on a
142
- service a public institution pays for, and nothing here would control what the
143
- caller wrote. Every query this server sends is one of ten written in advance.
144
-
145
- ## The licence, and what it asks of you
69
+ `-i` keeps stdin open, which is where the protocol travels, and `-t` is left out
70
+ because a TTY rewrites the stream. The container needs outbound HTTPS to
71
+ `data.bnf.fr`, and nothing else: no volume, no port, no credential.
146
72
 
147
- The BnF states one condition on these metadata:
73
+ ### Bundle, without npm
148
74
 
149
- > L'utilisation de ces métadonnées est libre et gratuite sous réserve du maintien
150
- > de la mention de leur source et de l'indication de leur date de récupération.
75
+ Download `mcp-databnf-2.1.0.mcpb` from
76
+ [the latest release](https://github.com/smeet666/mcp-databnf/releases/latest) and
77
+ open it. A client that supports MCP bundles installs it on its own, with no npm
78
+ and no configuration file to edit. The bundle carries its dependencies, so
79
+ nothing is fetched at install time.
80
+
81
+ ## What you can ask
82
+
83
+ - « Que dit la BnF de Colette ? »
84
+ - "List everything Marguerite Duras wrote."
85
+ - "Which editions of that work does the library hold?"
86
+ - "Are any of them digitised in Gallica?"
87
+ - "When was that record last established?"
88
+
89
+ The ordinary path runs from a search to a record: a row carries an `id`, and
90
+ `get_author` or `get_work` reads it.
91
+
92
+ ## Tools
93
+
94
+ | Tool | What it does |
95
+ | ---------------- | ------------------------------------------------------------- |
96
+ | `search_authors` | Finds people by name in the authority records. |
97
+ | `get_author` | Reads one person's record in full. |
98
+ | `search_works` | Finds works by title. |
99
+ | `get_work` | Reads one work's record in full. |
100
+ | `list_works` | Lists the works one person is credited with. |
101
+ | `list_editions` | Lists the editions of one work. |
102
+ | `find_digitised` | Finds the copies digitised in Gallica for a person or a work. |
103
+
104
+ ### `search_authors`
105
+
106
+ Finds people by name in the authority records.
107
+
108
+ | Argument | Type | Required | What it does |
109
+ | -------- | ------------------------------ | -------- | --------------------- |
110
+ | `name` | string, 1 to 200 characters | yes | The name to look for. |
111
+ | `limit` | integer, 1 to 50, default `10` | no | Rows to serve. |
112
+ | `page` | integer, 1 to 100, default `1` | no | Which page of rows. |
113
+
114
+ **In return:** `authors`, each carrying `id`, which `get_author`, `list_works`
115
+ and `find_digitised` take; `name` as the service writes it; `label`, the
116
+ authority heading, usually with the dates; `birth_year` and `death_year`, `null`
117
+ where the record states none; `role`; and `source_url`. `words_searched` says
118
+ what was actually sent, `has_more` whether further pages exist, and
119
+ `index_window_full` that the index served everything it will serve for this
120
+ search.
121
+
122
+ ### `get_author`
123
+
124
+ Reads one person's record in full.
125
+
126
+ | Argument | Type | Required | What it does |
127
+ | -------------------- | --------------------------- | -------- | --------------------------------------- |
128
+ | `author_id` | string, 1 to 200 characters | yes | The identifier a row carries. |
129
+ | `include_depictions` | boolean, default `false` | no | Add the portraits the record points to. |
130
+
131
+ **In return:** the person with `name`, `label`, `given_name`, `family_name`,
132
+ `other_names`, `birth_date` and `death_date` as published, `birth_year` and
133
+ `death_year` as numbers, `birth_place`, `death_place`,
134
+ `biographical_information`, `occupation`, `languages` as ISO 639-2 codes,
135
+ `countries` and `fields` in the words of the record. A field the record leaves
136
+ empty is `null`.
137
+
138
+ ### `search_works`
139
+
140
+ Finds works by title.
141
+
142
+ | Argument | Type | Required | What it does |
143
+ | -------- | ------------------------------ | -------- | ----------------------------------- |
144
+ | `title` | string, 1 to 200 characters | yes | The words of the title to look for. |
145
+ | `limit` | integer, 1 to 50, default `10` | no | Rows to serve. |
146
+ | `page` | integer, 1 to 100, default `1` | no | Which page of rows. |
147
+
148
+ **In return:** `works`, each carrying `id`, which `get_work`, `list_editions` and
149
+ `find_digitised` take; `title`; `date`, the year the record gives the work, as
150
+ published; `creators`; `status`, reading `established` or `provisional`; and
151
+ `source_url`. The envelope carries the same `words_searched`, `has_more` and
152
+ `index_window_full` a search of people returns.
153
+
154
+ ### `get_work`
155
+
156
+ Reads one work's record in full.
157
+
158
+ | Argument | Type | Required | What it does |
159
+ | -------------------- | --------------------------- | -------- | ------------------------------------------- |
160
+ | `work_id` | string, 1 to 200 characters | yes | The identifier a row carries. |
161
+ | `include_depictions` | boolean, default `false` | no | Add the illustrations the record points to. |
162
+
163
+ **In return:** the work with `title`, `label`, `date` as published, `first_year`,
164
+ `creators` as `{ id, name }`, `languages`, `forms`, `subjects` and
165
+ `dewey_classes` in the words of the record, `expression_count`, `same_as` for the
166
+ registers the BnF aligns it with, and `catalogue_url`. `status` reads
167
+ `established` or `provisional`, and `status_statement` says what the library
168
+ means by it: a provisional record is one the library has not finished checking.
169
+
170
+ ### `list_works`
171
+
172
+ Lists the works one person is credited with.
151
173
 
152
- Use is free of charge, provided the source is named **and the date of retrieval
153
- is stated**. That second half is a design constraint:
154
- every answer this server produces carries `retrieved_at` in its payload and ends
155
- its text block with the source and that date. A cached answer reports the moment
156
- it was originally read, since that is the date it was retrieved. Repeat both
157
- wherever you show what you got.
174
+ | Argument | Type | Required | What it does |
175
+ | ----------- | ------------------------------ | -------- | ------------------------ |
176
+ | `author_id` | string, 1 to 200 characters | yes | The person's identifier. |
177
+ | `limit` | integer, 1 to 50, default `10` | no | Rows to serve. |
178
+ | `page` | integer, 1 to 100, default `1` | no | Which page of rows. |
158
179
 
159
- ## How it treats the service
180
+ **In return:** `works`, each carrying `id`, `title`, `date` as published, `year`
181
+ as a number where the record has one, `forms`, `status` and `source_url`, with
182
+ `has_more` to continue.
160
183
 
161
- data.bnf.fr is a query service a public institution runs at its own cost, and a
162
- SPARQL query is a more expensive request than fetching a page.
184
+ ### `list_editions`
163
185
 
164
- - One request at a time, never in parallel.
165
- - At least three seconds between two of them. Configuration can widen that and
166
- cannot narrow it, including through the published client entry point.
167
- - The `User-Agent` always carries the project identifier and an address where a
168
- person can be reached, whatever a caller sets.
169
- - Answers are cached in memory for fifteen minutes, so a conversation that walks
170
- back over one author does not ask twice.
186
+ Lists the editions of one work.
171
187
 
172
- The BnF publishes no rate for this endpoint. It publishes `Crawl-delay: 5` on its
173
- other host and enforces it there, which is the only figure it has stated about
174
- how fast it wants to be read, and the floor here was set with that in mind.
188
+ | Argument | Type | Required | What it does |
189
+ | --------- | ------------------------------ | -------- | ---------------------- |
190
+ | `work_id` | string, 1 to 200 characters | yes | The work's identifier. |
191
+ | `limit` | integer, 1 to 50, default `10` | no | Rows to serve. |
192
+ | `page` | integer, 1 to 100, default `1` | no | Which page of rows. |
175
193
 
176
- ## Settings
194
+ **In return:** `editions`, each carrying its own `id` in the BnF catalogue, the
195
+ `title` this edition bears, `date` and `year`, `publisher`, `place`,
196
+ `edition_statement`, `extent`, `isbn`, `note` as the cataloguer wrote it,
197
+ `catalogue_url`, `digitised` and `source_url`. A field the record leaves empty is
198
+ `null`.
177
199
 
178
- Every one is optional.
200
+ ### `find_digitised`
179
201
 
180
- | Variable | Default | Meaning |
181
- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------- |
182
- | `BNF_USER_AGENT` | none | Identify your own client. The project identifier is appended, so the BnF can always reach a person. |
183
- | `BNF_MIN_INTERVAL_MS` | `3000` | Milliseconds between requests. The floor is 3000 and cannot be lowered. |
184
- | `BNF_TIMEOUT_MS` | `60000` | Deadline for one query. |
185
- | `BNF_MAX_RETRIES` | `3` | Attempts after a busy answer. |
186
- | `BNF_CACHE_TTL_MS` | `900000` | How long an answer is kept. `0` turns the cache off. |
187
- | `BNF_CACHE_MAX_ENTRIES` | `200` | How many answers are kept. |
188
- | `BNF_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`. Logs go to stderr. |
202
+ Finds the copies digitised in Gallica attached to a person or a work.
189
203
 
190
- A value that cannot be read is refused, named on stderr, and the default stands.
191
- The setting is not clamped: clamping would let you believe a value took effect
192
- when it did not.
204
+ | Argument | Type | Required | What it does |
205
+ | -------- | ------------------------------------------ | -------- | ---------------------------------------- |
206
+ | `id` | string, 1 to 200 characters | yes | The identifier of a person or of a work. |
207
+ | `kind` | `auto`, `person` or `work`, default `auto` | no | What the identifier stands for. |
208
+ | `limit` | integer, 1 to 200, default `40` | no | Links to serve. |
209
+
210
+ **In return:** `kind`, saying what the catalogue types the record as, and `links`,
211
+ each carrying the Gallica `ark`, its `url`, its `rendering` and the `role` the
212
+ person holds on it. `links_returned_by_role` counts them per role. This server
213
+ describes a digitised document and never opens one.
214
+
215
+ ## Established and provisional records
216
+
217
+ A record carries a `status`. `established` means the library has checked it;
218
+ `provisional` means it has not finished, and `status_statement` says so in the
219
+ library's own words. Report the status alongside anything taken from a
220
+ provisional record.
221
+
222
+ ## The licence, and what it asks
223
+
224
+ The BnF states one condition on these metadata:
225
+
226
+ > L'utilisation de ces métadonnées est libre et gratuite sous réserve du maintien
227
+ > de la mention de leur source et de l'indication de leur date de récupération.
228
+
229
+ Use is free of charge, provided the source is named and the date of retrieval is
230
+ stated. Every answer carries `retrieved_at` in its payload and ends its text with
231
+ the source and that date. A cached answer reports the moment it was originally
232
+ read, since that is when it was retrieved. Repeat both wherever what you got is
233
+ shown.
234
+
235
+ ## Configuration
236
+
237
+ Every variable is optional. Set them in the `env` block of your client config.
238
+
239
+ | Variable | Default | What it does |
240
+ | ----------------------- | -------------------- | --------------------------------------------------------------------------------- |
241
+ | `BNF_USER_AGENT` | the project identity | Names your application to the BnF, with an address where a person can be reached. |
242
+ | `BNF_MIN_INTERVAL_MS` | `3000` | Gap between two requests, from 3000 to 120000. |
243
+ | `BNF_TIMEOUT_MS` | `60000` | Deadline for one request, from 1000 to 300000. |
244
+ | `BNF_MAX_RETRIES` | `3` | Attempts after a transient failure, from 0 to 8. |
245
+ | `BNF_CACHE_TTL_MS` | `900000` | How long an answer stays in memory, from 0 to 86400000. |
246
+ | `BNF_CACHE_MAX_ENTRIES` | `200` | Answers held in memory at once, from 1 to 5000. |
247
+ | `BNF_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`, written to stderr. |
248
+
249
+ A value outside its range falls back to the default, and the reason is written to
250
+ stderr.
193
251
 
194
252
  ## Errors
195
253
 
196
- | Code | Means |
197
- | --------------- | ----------------------------------------------------------- |
198
- | `not_found` | The endpoint answered, and the BnF describes no such record |
199
- | `invalid_input` | The request was refused rather than answered |
200
- | `rate_limited` | The endpoint asked this client to slow down |
201
- | `parse_failure` | The answer arrived in a shape this client cannot read |
202
- | `network_error` | The request did not complete |
203
- | `timeout` | The query exceeded its deadline, or the endpoint gave it up |
254
+ Every failure carries one of six codes, a message, and where it helps a hint
255
+ naming the next move.
204
256
 
205
- `rate_limited` never means the record is missing. Neither does `timeout`: the
206
- endpoint answers 200 with an empty body when it abandons a query part way
207
- through, and this server calls that a failure to read rather than an absence,
208
- because the two look identical and mean opposite things.
257
+ | Code | What happened | What to do |
258
+ | --------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
259
+ | `not_found` | The service answered, and holds no such record. | Check the identifier with `search_authors` or `search_works`. |
260
+ | `invalid_input` | The arguments were refused before any request went out. | Read the message, which names the argument. |
261
+ | `rate_limited` | The service asked this client to slow down. | Wait the number of seconds the hint names and call again with the same arguments. The record is still there. |
262
+ | `parse_failure` | The answer arrived in a shape this client cannot read. | Report it at [the issue tracker](https://github.com/smeet666/mcp-databnf/issues). |
263
+ | `network_error` | The request did not complete. | Try again shortly. |
264
+ | `timeout` | The request passed its deadline. | Raise `BNF_TIMEOUT_MS`, or ask for fewer rows. |
209
265
 
210
- ## Using the access layer on its own
266
+ ## As a library
211
267
 
212
- The lower layer imports nothing from the MCP protocol and is published under the
213
- `./client` subpath, with its pacing, its cache and its error taxonomy attached.
268
+ The layer reading the service is published on its own, with its pacing, its cache
269
+ and its errors, and with no protocol attached.
214
270
 
215
271
  ```ts
216
272
  import { BnfClient } from "mcp-databnf/client";
217
273
 
218
274
  const client = new BnfClient();
219
- const { data, retrievedAt } = await client.searchAuthors("Rimbaud", 10, 0);
220
- for (const author of data.rows) console.log(author.id, author.name, author.birthYear);
221
- console.log("retrieved", retrievedAt);
275
+ const { data, cached } = await client.getAuthor("cb11907966z");
276
+ console.log(data.label, cached);
222
277
  ```
223
278
 
279
+ `getAuthor` and `getWork` each answer `{ data, cached }`, and throw an error
280
+ carrying one of the six codes. The three-second floor between two requests holds
281
+ here as well.
282
+
283
+ ## Pacing and attribution
284
+
285
+ Requests go out one at a time with at least three seconds between them, and that
286
+ floor holds however the server is configured. Each question is answered by a
287
+ SPARQL query against a public endpoint the BnF runs at its own expense, which is
288
+ why the interval is wide and the deadline long. The `User-Agent` always ends with
289
+ the project identity and an address where a person can be reached.
290
+
291
+ Every answer carries the source and `retrieved_at`, which the licence asks to be
292
+ stated wherever the metadata are shown.
293
+
294
+ This MCP server is an unofficial project, with no affiliation to the
295
+ Bibliothèque nationale de France.
296
+
297
+ ## Privacy
298
+
299
+ This server collects nothing about you and sends nothing to its author. It runs
300
+ on your machine, contacts `data.bnf.fr` and nothing else, holds its answers in memory
301
+ while it runs, and writes nothing to disk.
302
+ [PRIVACY.md](PRIVACY.md) states what a request carries and which settings change
303
+ any of it.
304
+
224
305
  ## Development
225
306
 
226
307
  ```bash
227
308
  npm install
228
- npm test # unit tests, against generated fixtures, no network
229
- npm run typecheck
230
- npm run build
231
- BNF_LIVE=1 npm run test:live # one real query per route
309
+ npm run build:fixtures
310
+ npm test
311
+ npm run check
232
312
  ```
233
313
 
234
- The unit tests reach no network. Fixtures are generated by
235
- `scripts/build-fixtures.mjs` from invented records, so no BnF content lives in
236
- this repository and every test is reproducible. The live suite runs nightly as a
237
- canary, and it is the only thing that would notice the day the catalogue changes
238
- shape.
314
+ Tests run against generated fixtures and make no network request. The live suite,
315
+ `npm run test:live`, makes one request per route and runs nightly against the
316
+ service itself.
239
317
 
240
318
  ## Contributing
241
319
 
242
- [CONTRIBUTING.md](CONTRIBUTING.md). Reports of a wrong answer are the most useful
243
- kind: say what you asked, what came back, and what the record actually holds.
244
-
245
- ## Licence
320
+ Bugs, questions and ideas belong in
321
+ [the issue tracker](https://github.com/smeet666/mcp-databnf/issues). Pull
322
+ requests are welcome; opening an issue first helps agree on the shape of the
323
+ change. See [CONTRIBUTING.md](CONTRIBUTING.md).
246
324
 
247
- MIT for this code. See [LICENSE](LICENSE).
325
+ ## License
248
326
 
249
- The metadata belong to the Bibliothèque nationale de France and are published
250
- under the condition quoted above: name the source, and state the date of
251
- retrieval.
327
+ MIT, see [LICENSE](LICENSE). The metadata belong to the Bibliothèque nationale de
328
+ France, free to use provided the source and the date of retrieval are stated.
252
329
 
253
330
  ---
254
331
 
332
+ <a name="mcp-databnf-français"></a>
333
+
255
334
  # mcp-databnf (français)
256
335
 
257
- Un serveur MCP pour [data.bnf.fr](https://data.bnf.fr), le catalogue ouvert de la
258
- Bibliothèque nationale de France. Chercher un auteur, trouver une œuvre, lister
259
- les éditions que la BnF en conserve, lister ce qu'une personne a écrit, et
260
- rassembler les liens vers ce qui a été numérisé.
336
+ _[English version](#mcp-databnf)_
261
337
 
262
- Sans clé d'API. Sans compte. En lecture seule.
338
+ [data.bnf.fr](https://data.bnf.fr) est le service de données ouvertes de la
339
+ Bibliothèque nationale de France. Il publie les notices d'autorité que la
340
+ bibliothèque nationale entretient : les personnes qu'elle catalogue, avec leurs
341
+ dates, leurs lieux, leurs langues et leurs domaines d'activité ; les œuvres
342
+ qu'elles ont écrites, avec les éditions dans lesquelles chaque œuvre a paru ; et
343
+ les liens vers les exemplaires numérisés dans Gallica. Une notice indique si la
344
+ bibliothèque la tient pour établie ou encore provisoire.
263
345
 
264
- ## À quoi il sert
346
+ Ce serveur relie un client de conversation à ce service. On peut y chercher un
347
+ auteur ou une œuvre par son nom, lire une notice en entier, lister ce qu'un
348
+ auteur a écrit, lister les éditions d'une œuvre, et trouver les exemplaires
349
+ numérisés attachés à l'un ou l'autre. Aucune clé d'API, aucun compte.
265
350
 
266
- La BnF publie son fichier d'autorité et ses notices bibliographiques en données
267
- liées, et répond aux questions qu'on lui pose en SPARQL. Ce jeu de données sait
268
- des choses qu'une recherche sur le web ignore : lequel de deux homonymes a écrit
269
- tel livre, ce que la BnF a enregistré comme date et lieu de mort de quelqu'un,
270
- quelles éditions d'une œuvre existent et qui les a imprimées, et lesquelles ont
271
- été numérisées.
351
+ ## Installation
272
352
 
273
- Ce serveur pose ces questions pour vous, en sept outils, et rapporte ce que le
274
- catalogue répond sans y ajouter.
353
+ **Installation en un clic**
275
354
 
276
- ## Installation
355
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=databnf&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1kYXRhYm5mIl19)
356
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=databnf&config=%7B%22name%22%3A%22databnf%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-databnf%22%5D%7D)
357
+
358
+ **Claude Code**
277
359
 
278
360
  ```bash
279
- npx mcp-databnf
361
+ claude mcp add databnf -- npx -y mcp-databnf
280
362
  ```
281
363
 
364
+ **Claude Desktop, Cursor, et tout client au format de configuration standard**
365
+
282
366
  ```json
283
367
  {
284
368
  "mcpServers": {
@@ -290,6 +374,9 @@ npx mcp-databnf
290
374
  }
291
375
  ```
292
376
 
377
+ Node 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à
378
+ renseigner.
379
+
293
380
  ### Avec Docker
294
381
 
295
382
  ```json
@@ -297,137 +384,272 @@ npx mcp-databnf
297
384
  "mcpServers": {
298
385
  "databnf": {
299
386
  "command": "docker",
300
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-databnf:2.0.0"]
387
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-databnf:2.1.0"]
301
388
  }
302
389
  }
303
390
  }
304
391
  ```
305
392
 
306
- `-i` garde l'entrée standard ouverte, qui est le canal du protocole, et aucun
307
- `-t` n'est passé : un terminal réécrit le flux et le casse. Le conteneur a besoin
308
- d'un accès HTTPS sortant vers `data.bnf.fr`, `catalogue.bnf.fr` et `isni.org`, et de rien d'autre :
309
- aucun volume, aucun port, aucune variable d'environnement, aucun identifiant.
310
-
311
- ## Les sept outils
312
-
313
- | Outil | Répond à |
314
- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
315
- | `search_authors` | Qui la BnF enregistre sous ce nom, et quelle notice est laquelle |
316
- | `get_author` | Dates, lieux, profession, langue, pays, indice Dewey, et la même personne dans VIAF, IdRef, DBpedia, Wikidata et ISNI |
317
- | `search_works` | Quelles œuvres portent ces mots dans leur titre |
318
- | `get_work` | Titre, auteurs, date, langue, forme, sujet, et si la notice est établie ou provisoire |
319
- | `list_works` | Quelles œuvres le catalogue attribue à une personne comme créatrice, avec la date et le code de forme qu'il leur donne |
320
- | `list_editions` | Éditeur, lieu, année, mention d'édition, pagination, ISBN, lien catalogue, et le lien Gallica quand il existe |
321
- | `find_digitised` | Tous les documents numérisés que le catalogue rattache à une personne ou à une œuvre, sous forme de liens |
322
-
323
- ## Ce qu'il ne fait pas, et pourquoi
324
-
325
- **Il ne lit jamais Gallica.** La BnF place ses métadonnées et ses contenus
326
- numérisés sous deux régimes différents. Les métadonnées lues ici sont
327
- réutilisables librement à condition d'en citer la source et la date de
328
- récupération. Les contenus de gallica.bnf.fr relèvent d'un autre régime : leurs
329
- conditions soumettent l'usage dans un projet d'intelligence artificielle à une
330
- licence payante hors recherche académique, et le site refuse ClaudeBot et GPTBot
331
- au niveau du serveur, puis bannit l'adresse appelante après une quinzaine de
332
- requêtes, quel que soit le rythme.
333
-
334
- Une adresse Gallica est donc traitée ici pour ce que le catalogue en dit : une
335
- métadonnée, rendue comme un lien qu'une personne ouvrira. Le serveur vous dira
336
- qu'un tirage bruxellois de 1873 d'_Une saison en enfer_ a été numérisé et vous en
337
- donnera l'adresse. Il ne vous dira pas ce qu'il y a page quatre, si la
338
- numérisation est complète, ni si le document s'ouvre. Un lien `bnf-onto:OCR`
339
- désigne un texte océrisé : le serveur signale qu'il existe et le laisse où il
340
- est. Un test échoue si une adresse sur cet hôte est un jour construite pour être
341
- appelée.
342
-
343
- **Il ne classe pas.** L'index plein texte de la BnF répond si un titre ou un nom
344
- porte les mots demandés. Il ne rend aucune mesure de pertinence, donc les lignes
345
- arrivent dans l'ordre de l'index. Chercher `saison enfer` rend une douzaine
346
- d'études sur Rimbaud avant Rimbaud, et chacune est une correspondance correcte.
347
- Ce serveur le dit, plutôt que d'inventer un ordre que le catalogue ne porte pas,
348
- et il ne rapporte aucun total : sur une recherche qui ne classe pas, un total se
349
- lit comme une mesure de pertinence.
350
-
351
- **Un nom est apparié lettre à lettre, et seulement parmi les personnes.**
352
- L'index compare les caractères donnés à ceux qu'un catalogueur a saisis :
353
- `Dostoïevski`, `Dostoievski` et `Dostoevskij` sont donc trois recherches qui
354
- atteignent trois ensembles de notices différents, et quelques lignes obtenues
355
- sous une graphie ne disent rien des autres. `search_authors` l'énonce sur chaque
356
- réponse, et il ne lit que les notices de personnes : un organisme ou un congrès
357
- est hors de ce qu'il a regardé, si bien qu'une réponse sans ligne n'affirme
358
- jamais que le fichier d'autorité ne tient pas une telle vedette.
359
-
360
- **Une liste d'œuvres est un lien, pas une bibliographie.** `list_works` suit
361
- `dcterms:creator`, l'énoncé qui rattache une œuvre à qui l'a faite. Cet énoncé
362
- est réel et il est partiel : le catalogue crédite une personne sur une notice par
363
- d'autres voies, et la BnF conserve des éditions imprimées dont l'œuvre n'a jamais
364
- été établie comme notice à part entière. Un titre absent de la liste n'est donc
365
- pas un titre que la personne n'a pas écrit. Chaque réponse le dit, et aucune ne
366
- rapporte de total : un compte de ce qu'un seul lien atteint se lirait comme un
367
- compte de ce que quelqu'un a écrit.
368
-
369
- **Un code de forme dit ce qu'une œuvre déclare ; son absence ne dit rien.** Une
370
- notice d'œuvre pointe vers un terme du vocabulaire des formes d'œuvre de la BnF,
371
- et ce jeu de données n'en publie aucun libellé. `roman`, `poesi` et `te`
372
- arrivent donc tels quels : certains se lisent comme des mots, d'autres non. Le
373
- catalogue n'énonce par ailleurs aucune forme sur quantité d'œuvres, et ce
374
- silence est une forme non déclarée, pas un genre exclu. Garder les lignes qui
375
- portent un code trouve donc les œuvres qui le déclarent, jamais toutes les
376
- œuvres de cette forme : les codes sont rapportés, et aucun filtre n'est offert
377
- dessus.
378
-
379
- **Il n'écrit pas de biographies.** Le champ que la BnF appelle information
380
- biographique contient une profession sur la plupart des notices : celle de
381
- Rimbaud dit _Poète_, et c'est tout. `get_author` rend ce mot et dit ce que c'est.
382
-
383
- **Il n'expose aucun outil SPARQL brut.** Une requête arbitraire est une charge
384
- non bornée sur un service qu'une institution publique paie, et rien ici ne
385
- contrôlerait ce que l'appelant a écrit. Chacune des requêtes envoyées est l'une
386
- des dix écrites à l'avance.
387
-
388
- ## La licence, et ce qu'elle vous demande
389
-
390
- La BnF pose une condition :
393
+ `-i` garde l'entrée standard ouverte, qui est le canal du protocole, et `-t` est
394
+ omis parce qu'un TTY réécrit le flux. Le conteneur a besoin d'un accès HTTPS
395
+ sortant vers `data.bnf.fr`, et de rien d'autre : aucun volume, aucun port, aucun
396
+ identifiant.
397
+
398
+ ### Bundle, sans npm
399
+
400
+ Téléchargez `mcp-databnf-2.1.0.mcpb` depuis
401
+ [la dernière publication](https://github.com/smeet666/mcp-databnf/releases/latest)
402
+ et ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et
403
+ sans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc
404
+ rien n'est téléchargé à l'installation.
405
+
406
+ ## Ce qu'on peut demander
407
+
408
+ - « Que dit la BnF de Colette ? »
409
+ - « Liste tout ce qu'a écrit Marguerite Duras. »
410
+ - « Quelles éditions de cette œuvre la bibliothèque conserve-t-elle ? »
411
+ - « Y en a-t-il de numérisées dans Gallica ? »
412
+ - « Cette notice est-elle établie ou provisoire ? »
413
+
414
+ Le chemin ordinaire va d'une recherche à une notice : une ligne porte un `id`, et
415
+ `get_author` ou `get_work` la lit.
416
+
417
+ ## Les outils
418
+
419
+ | Outil | Ce qu'il fait |
420
+ | ---------------- | ---------------------------------------------------------------------------- |
421
+ | `search_authors` | Trouve des personnes par leur nom dans les notices d'autorité. |
422
+ | `get_author` | Lit la notice d'une personne en entier. |
423
+ | `search_works` | Trouve des œuvres par leur titre. |
424
+ | `get_work` | Lit la notice d'une œuvre en entier. |
425
+ | `list_works` | Liste les œuvres attribuées à une personne. |
426
+ | `list_editions` | Liste les éditions d'une œuvre. |
427
+ | `find_digitised` | Trouve les exemplaires numérisés dans Gallica d'une personne ou d'une œuvre. |
428
+
429
+ ### `search_authors`
430
+
431
+ Trouve des personnes par leur nom dans les notices d'autorité.
432
+
433
+ | Argument | Type | Requis | Ce qu'il fait |
434
+ | -------- | --------------------------- | ------ | ---------------------- |
435
+ | `name` | chaîne, 1 à 200 caractères | oui | Le nom cherché. |
436
+ | `limit` | entier, 1 à 50, défaut `10` | non | Lignes à servir. |
437
+ | `page` | entier, 1 à 100, défaut `1` | non | Quelle page de lignes. |
438
+
439
+ **En retour :** `authors`, chacun portant `id`, que `get_author`, `list_works` et
440
+ `find_digitised` reprennent ; `name` tel que le service l'écrit ; `label`, la
441
+ vedette d'autorité, généralement avec les dates ; `birth_year` et `death_year`,
442
+ `null` là où la notice n'en indique pas ; `role` ; et `source_url`.
443
+ `words_searched` dit ce qui a réellement été envoyé, `has_more` s'il existe
444
+ d'autres pages, et `index_window_full` que l'index a servi tout ce qu'il servira
445
+ pour cette recherche.
446
+
447
+ ### `get_author`
448
+
449
+ Lit la notice d'une personne en entier.
450
+
451
+ | Argument | Type | Requis | Ce qu'il fait |
452
+ | -------------------- | -------------------------- | ------ | ---------------------------------------------------- |
453
+ | `author_id` | chaîne, 1 à 200 caractères | oui | L'identifiant que porte une ligne. |
454
+ | `include_depictions` | booléen, défaut `false` | non | Ajoute les portraits vers lesquels la notice pointe. |
455
+
456
+ **En retour :** la personne avec `name`, `label`, `given_name`, `family_name`,
457
+ `other_names`, `birth_date` et `death_date` tels que publiés, `birth_year` et
458
+ `death_year` en nombres, `birth_place`, `death_place`,
459
+ `biographical_information`, `occupation`, `languages` en codes ISO 639-2,
460
+ `countries` et `fields` dans les mots de la notice. Un champ que la notice laisse
461
+ vide vaut `null`.
462
+
463
+ ### `search_works`
464
+
465
+ Trouve des œuvres par leur titre.
466
+
467
+ | Argument | Type | Requis | Ce qu'il fait |
468
+ | -------- | --------------------------- | ------ | -------------------------- |
469
+ | `title` | chaîne, 1 à 200 caractères | oui | Les mots du titre cherché. |
470
+ | `limit` | entier, 1 à 50, défaut `10` | non | Lignes à servir. |
471
+ | `page` | entier, 1 à 100, défaut `1` | non | Quelle page de lignes. |
472
+
473
+ **En retour :** `works`, chacune portant `id`, que `get_work`, `list_editions` et
474
+ `find_digitised` reprennent ; `title` ; `date`, l'année que la notice donne à
475
+ l'œuvre, telle que publiée ; `creators` ; `status`, valant `established` ou
476
+ `provisional` ; et `source_url`. L'enveloppe porte les mêmes `words_searched`,
477
+ `has_more` et `index_window_full` qu'une recherche de personnes.
478
+
479
+ ### `get_work`
480
+
481
+ Lit la notice d'une œuvre en entier.
482
+
483
+ | Argument | Type | Requis | Ce qu'il fait |
484
+ | -------------------- | -------------------------- | ------ | ---------------------------------------------------------- |
485
+ | `work_id` | chaîne, 1 à 200 caractères | oui | L'identifiant que porte une ligne. |
486
+ | `include_depictions` | booléen, défaut `false` | non | Ajoute les illustrations vers lesquelles la notice pointe. |
487
+
488
+ **En retour :** l'œuvre avec `title`, `label`, `date` telle que publiée,
489
+ `first_year`, `creators` en `{ id, name }`, `languages`, `forms`, `subjects` et
490
+ `dewey_classes` dans les mots de la notice, `expression_count`, `same_as` pour
491
+ les registres auxquels la BnF l'aligne, et `catalogue_url`. `status` vaut
492
+ `established` ou `provisional`, et `status_statement` dit ce que la bibliothèque
493
+ entend par là : une notice provisoire est une notice qu'elle n'a pas fini de
494
+ vérifier.
495
+
496
+ ### `list_works`
497
+
498
+ Liste les œuvres attribuées à une personne.
499
+
500
+ | Argument | Type | Requis | Ce qu'il fait |
501
+ | ----------- | --------------------------- | ------ | ----------------------------- |
502
+ | `author_id` | chaîne, 1 à 200 caractères | oui | L'identifiant de la personne. |
503
+ | `limit` | entier, 1 à 50, défaut `10` | non | Lignes à servir. |
504
+ | `page` | entier, 1 à 100, défaut `1` | non | Quelle page de lignes. |
505
+
506
+ **En retour :** `works`, chacune portant `id`, `title`, `date` telle que publiée,
507
+ `year` en nombre quand la notice en a un, `forms`, `status` et `source_url`, avec
508
+ `has_more` pour poursuivre.
509
+
510
+ ### `list_editions`
511
+
512
+ Liste les éditions d'une œuvre.
513
+
514
+ | Argument | Type | Requis | Ce qu'il fait |
515
+ | --------- | --------------------------- | ------ | ------------------------- |
516
+ | `work_id` | chaîne, 1 à 200 caractères | oui | L'identifiant de l'œuvre. |
517
+ | `limit` | entier, 1 à 50, défaut `10` | non | Lignes à servir. |
518
+ | `page` | entier, 1 à 100, défaut `1` | non | Quelle page de lignes. |
519
+
520
+ **En retour :** `editions`, chacune portant son propre `id` au catalogue de la
521
+ BnF, le `title` que cette édition porte, `date` et `year`, `publisher`, `place`,
522
+ `edition_statement`, `extent`, `isbn`, `note` telle que le catalogueur l'a
523
+ écrite, `catalogue_url`, `digitised` et `source_url`. Un champ que la notice
524
+ laisse vide vaut `null`.
525
+
526
+ ### `find_digitised`
527
+
528
+ Trouve les exemplaires numérisés dans Gallica attachés à une personne ou à une
529
+ œuvre.
530
+
531
+ | Argument | Type | Requis | Ce qu'il fait |
532
+ | -------- | ----------------------------------------- | ------ | -------------------------------------------- |
533
+ | `id` | chaîne, 1 à 200 caractères | oui | L'identifiant d'une personne ou d'une œuvre. |
534
+ | `kind` | `auto`, `person` ou `work`, défaut `auto` | non | Ce que l'identifiant désigne. |
535
+ | `limit` | entier, 1 à 200, défaut `40` | non | Liens à servir. |
536
+
537
+ **En retour :** `kind`, qui dit de quel type le catalogue tient la notice, et
538
+ `links`, chacun portant l'`ark` Gallica, son `url`, son `rendering` et le `role`
539
+ que la personne y tient. `links_returned_by_role` les compte par rôle. Ce serveur
540
+ décrit un document numérisé et n'en ouvre jamais aucun.
541
+
542
+ ## Notices établies et provisoires
543
+
544
+ Une notice porte un `status`. `established` signifie que la bibliothèque l'a
545
+ vérifiée ; `provisional` qu'elle ne l'a pas terminée, et `status_statement` le
546
+ dit dans ses propres mots. Rapportez ce statut à côté de tout ce qui vient d'une
547
+ notice provisoire.
548
+
549
+ ## La licence, et ce qu'elle demande
550
+
551
+ La BnF pose une condition sur ces métadonnées :
391
552
 
392
553
  > L'utilisation de ces métadonnées est libre et gratuite sous réserve du maintien
393
554
  > de la mention de leur source et de l'indication de leur date de récupération.
394
555
 
395
- La date de récupération est une contrainte de conception : chaque réponse porte `retrieved_at` dans sa charge structurée et termine
396
- son bloc de texte par la source et cette date. Une réponse servie depuis le cache
397
- rapporte le moment où elle a été lue la première fois, puisque c'est là qu'elle a
398
- été récupérée. Reprenez les deux partout où vous montrez ce que vous avez obtenu.
556
+ Chaque réponse porte `retrieved_at` dans sa charge utile et termine son texte par
557
+ la source et cette date. Une réponse servie depuis le cache rapporte le moment où
558
+ elle a été lue à l'origine, puisque c'est sa date de récupération. Redonnez les
559
+ deux partout où ce que vous avez obtenu est montré.
399
560
 
400
- ## Le rythme
561
+ ## Configuration
401
562
 
402
- Une requête à la fois, jamais en parallèle. Au moins trois secondes entre deux
403
- requêtes : la configuration peut élargir cet intervalle et ne peut pas le
404
- réduire, y compris par le point d'entrée `client` publié. Le `User-Agent` porte
405
- toujours l'identifiant du projet et une adresse où joindre une personne. Les
406
- réponses sont gardées quinze minutes en mémoire.
563
+ Chaque variable est facultative. Elles se posent dans le bloc `env` de la
564
+ configuration du client.
407
565
 
408
- La BnF ne publie aucune limite pour ce point d'accès. Elle publie `Crawl-delay: 5`
409
- sur son autre hôte et l'y fait respecter, ce qui est le seul chiffre qu'elle ait
410
- énoncé sur la vitesse à laquelle elle veut être lue.
566
+ | Variable | Défaut | Ce qu'elle fait |
567
+ | ----------------------- | -------------------- | ----------------------------------------------------------------------------------- |
568
+ | `BNF_USER_AGENT` | l'identité du projet | Nomme votre application auprès de la BnF, avec une adresse où joindre une personne. |
569
+ | `BNF_MIN_INTERVAL_MS` | `3000` | Écart entre deux requêtes, de 3000 à 120000. |
570
+ | `BNF_TIMEOUT_MS` | `60000` | Délai d'une requête, de 1000 à 300000. |
571
+ | `BNF_MAX_RETRIES` | `3` | Tentatives après un échec passager, de 0 à 8. |
572
+ | `BNF_CACHE_TTL_MS` | `900000` | Durée pendant laquelle une réponse reste en mémoire, de 0 à 86400000. |
573
+ | `BNF_CACHE_MAX_ENTRIES` | `200` | Réponses gardées en mémoire à la fois, de 1 à 5000. |
574
+ | `BNF_LOG_LEVEL` | `error` | `silent`, `error`, `info` ou `debug`, écrit sur la sortie d'erreur. |
411
575
 
412
- ## Réglages
413
-
414
- Tous facultatifs : `BNF_USER_AGENT`, `BNF_MIN_INTERVAL_MS` (3000, plancher
415
- infranchissable), `BNF_TIMEOUT_MS` (60000), `BNF_MAX_RETRIES` (3),
416
- `BNF_CACHE_TTL_MS` (900000), `BNF_CACHE_MAX_ENTRIES` (200), `BNF_LOG_LEVEL`
417
- (`error`). Une valeur illisible est refusée, signalée sur stderr, et la valeur
418
- par défaut s'applique.
576
+ Une valeur hors de sa plage retombe sur le défaut, et la raison est écrite sur la
577
+ sortie d'erreur.
419
578
 
420
579
  ## Erreurs
421
580
 
422
- `not_found`, `invalid_input`, `rate_limited`, `parse_failure`, `network_error`,
423
- `timeout`. `rate_limited` ne veut jamais dire que la notice est absente.
424
- `timeout` non plus : le point d'accès répond 200 avec un corps vide quand il
425
- abandonne une requête en cours de route, et ce serveur appelle cela un échec de
426
- lecture plutôt qu'une absence, parce que les deux se ressemblent et veulent dire
427
- le contraire.
581
+ Chaque échec porte un des six codes, un message, et quand cela aide une
582
+ indication du geste suivant.
583
+
584
+ | Code | Ce qui s'est passé | Que faire |
585
+ | --------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
586
+ | `not_found` | Le service a répondu, et n'a pas cette notice. | Vérifiez l'identifiant avec `search_authors` ou `search_works`. |
587
+ | `invalid_input` | Les arguments ont été refusés avant toute requête. | Lisez le message, qui nomme l'argument. |
588
+ | `rate_limited` | Le service demande à ce client de ralentir. | Attendez les secondes indiquées et rappelez avec les mêmes arguments. La notice est toujours là. |
589
+ | `parse_failure` | La réponse est arrivée dans une forme illisible ici. | Signalez-le sur [le suivi d'incidents](https://github.com/smeet666/mcp-databnf/issues). |
590
+ | `network_error` | La requête n'a pas abouti. | Réessayez sous peu. |
591
+ | `timeout` | La requête a dépassé son délai. | Augmentez `BNF_TIMEOUT_MS`, ou demandez moins de lignes. |
592
+
593
+ ## Comme bibliothèque
594
+
595
+ La couche qui lit le service est publiée seule, avec son rythme, son cache et ses
596
+ erreurs, sans protocole attaché.
597
+
598
+ ```ts
599
+ import { BnfClient } from "mcp-databnf/client";
600
+
601
+ const client = new BnfClient();
602
+ const { data, cached } = await client.getAuthor("cb11907966z");
603
+ console.log(data.label, cached);
604
+ ```
605
+
606
+ `getAuthor` et `getWork` répondent chacun `{ data, cached }`, et lèvent une
607
+ erreur portant un des six codes. Le plancher de trois secondes entre deux
608
+ requêtes tient également ici.
609
+
610
+ ## Rythme et attribution
611
+
612
+ Les requêtes partent une à une avec au moins trois secondes entre elles, et ce
613
+ plancher tient quelle que soit la configuration. Chaque question se résout par
614
+ une requête SPARQL contre un point d'accès public que la BnF fait tourner à ses
615
+ frais, d'où un intervalle large et un délai long. Le `User-Agent` se termine
616
+ toujours par l'identité du projet et une adresse où joindre une personne.
617
+
618
+ Chaque réponse porte la source et `retrieved_at`, que la licence demande
619
+ d'indiquer partout où les métadonnées sont montrées.
620
+
621
+ Ce MCP est un projet non officiel, sans affiliation à la Bibliothèque nationale
622
+ de France.
623
+
624
+ ## Confidentialité
625
+
626
+ Ce serveur ne collecte rien sur vous et n'envoie rien à son auteur. Il tourne sur
627
+ votre machine, ne joint que `data.bnf.fr`, garde ses réponses en mémoire le temps qu'il
628
+ tourne, et n'écrit rien sur le disque. [PRIVACY.md](PRIVACY.md) dit ce qu'une
629
+ requête emporte et quels réglages changent cela.
630
+
631
+ ## Développement
632
+
633
+ ```bash
634
+ npm install
635
+ npm run build:fixtures
636
+ npm test
637
+ npm run check
638
+ ```
639
+
640
+ Les tests s'exécutent sur des fixtures engendrées et n'émettent aucune requête.
641
+ La suite en direct, `npm run test:live`, émet une requête par route et tourne
642
+ chaque nuit contre le service lui-même.
643
+
644
+ ## Contribuer
645
+
646
+ Les anomalies, les questions et les idées ont leur place dans
647
+ [le suivi d'incidents](https://github.com/smeet666/mcp-databnf/issues). Les
648
+ propositions de modification sont bienvenues ; ouvrir un ticket d'abord aide à
649
+ s'accorder sur la forme du changement. Voir [CONTRIBUTING.md](CONTRIBUTING.md).
428
650
 
429
651
  ## Licence
430
652
 
431
- MIT pour ce code. Les métadonnées appartiennent à la Bibliothèque nationale de
432
- France et sont publiées sous la condition citée plus haut : citer la source, et
433
- indiquer la date de récupération.
653
+ MIT, voir [LICENSE](LICENSE). Les métadonnées appartiennent à la Bibliothèque
654
+ nationale de France, d'usage libre sous réserve d'indiquer la source et la date
655
+ de récupération.