mcp-databnf 1.1.1 → 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
@@ -5,36 +5,40 @@
5
5
  [![license](https://img.shields.io/npm/l/mcp-databnf.svg)](./LICENSE)
6
6
  [![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-databnf)
7
7
  [![Glama](https://glama.ai/mcp/servers/smeet666/mcp-databnf/badges/score.svg)](https://glama.ai/mcp/servers/smeet666/mcp-databnf)
8
+ [![M8ven](https://m8ven.ai/badge/mcp/smeet666-mcp-databnf-abxqfo?variant=verified)](https://m8ven.ai/mcp/smeet666-mcp-databnf-abxqfo)
8
9
  [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=databnf&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1kYXRhYm5mIl19)
9
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)
10
11
 
11
- An MCP server for [data.bnf.fr](https://data.bnf.fr), the open catalogue of the
12
- Bibliothèque nationale de France. Look up an author, find a work, list the
13
- editions the BnF holds of it, list what a person wrote, and gather the links to
14
- 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.
15
18
 
16
- 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)_
17
25
 
18
26
  ---
19
27
 
20
- ## What it is for
28
+ ## Install
21
29
 
22
- The BnF publishes its authority file and its bibliographic records as linked
23
- data, and answers questions about them over SPARQL. That dataset knows things a
24
- web search does not: which of two people bearing one name wrote a given book,
25
- what the BnF recorded as somebody's date and place of death, which editions of a
26
- work exist and who printed them, and which of those have been digitised.
30
+ **One-click install**
27
31
 
28
- This server asks those questions for you, in seven tools, and reports what the
29
- 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)
30
34
 
31
- ## Install
35
+ **Claude Code**
32
36
 
33
37
  ```bash
34
- npx mcp-databnf
38
+ claude mcp add databnf -- npx -y mcp-databnf
35
39
  ```
36
40
 
37
- ### Claude Desktop, Claude Code, and other stdio clients
41
+ **Claude Desktop, Cursor, and any client using the standard config format**
38
42
 
39
43
  ```json
40
44
  {
@@ -47,220 +51,318 @@ npx mcp-databnf
47
51
  }
48
52
  ```
49
53
 
50
- ## The seven tools
51
-
52
- | Tool | Answers |
53
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
54
- | `search_authors` | Who does the BnF record under this name, and which record is which |
55
- | `get_author` | Dates, places, occupation, language, country, Dewey class, and the same person in VIAF, IdRef, DBpedia, Wikidata and ISNI |
56
- | `search_works` | Which works have these words in their title |
57
- | `get_work` | Title, creators, date, language, form, subject, and whether the record is established or provisional |
58
- | `list_works` | Which works the catalogue names a person the creator of, with the date and the form code it gives each |
59
- | `list_editions` | Publisher, place, year, edition statement, extent, ISBN, catalogue link, and the Gallica link when there is one |
60
- | `find_digitised` | Every digitised document the catalogue attaches to a person or a work, as links |
61
-
62
- A typical exchange asks `search_authors` for a name, reads the rows, and passes
63
- one identifier to `get_author`, to `list_works`, or on to `list_editions`.
64
-
65
- ## What it does not do, and why
66
-
67
- **It never reads Gallica.** The BnF puts its metadata and its digitised contents
68
- under two different regimes. The metadata this server reads may be reused freely
69
- provided the source and the date of retrieval are stated. The contents on
70
- gallica.bnf.fr are governed separately: their terms make use inside an
71
- artificial-intelligence project subject to a paid licence outside academic
72
- research, and the site refuses ClaudeBot and GPTBot at the server, then bans the
73
- calling address after about fifteen requests whatever the pace.
74
-
75
- So a Gallica address is treated here as what the catalogue says it is: a piece of
76
- metadata, rendered as a link for a person to open. The server will tell you that
77
- a 1873 Brussels printing of _Une saison en enfer_ has been digitised and give you
78
- its address. It will not tell you what is on page four, whether the scan is
79
- complete, or whether the document opens at all. A `bnf-onto:OCR` link names a
80
- machine-read text of a document; the server reports that the text exists and
81
- leaves it where it is. A test fails if any address on that host is ever built to
82
- be called.
83
-
84
- **It does not rank.** The BnF's full-text index answers whether a title or a name
85
- carries the words asked for. It returns no measure of how well, so the rows come
86
- back in the order the index holds them. Searching for `saison enfer` returns a
87
- dozen studies of Rimbaud before Rimbaud, and every one of them is a correct
88
- match. This server says so rather than inventing an order the catalogue does not
89
- support, and it reports no total, because a total on a search that does not rank
90
- reads as a measure of relevance.
91
-
92
- **A name is matched letter for letter, and only among the people.** The index
93
- compares the characters given against the characters a cataloguer entered, so
94
- `Dostoïevski`, `Dostoievski` and `Dostoevskij` are three searches reaching three
95
- different sets of records, and a handful of rows under one spelling is no
96
- evidence about the others. `search_authors` says so on every answer, and it reads
97
- the person records alone: an organisation or a conference is outside what it
98
- looked at, so an answer holding no row is never a statement that the authority
99
- file holds no such heading.
100
-
101
- **A list of works is one link, not a bibliography.** `list_works` walks
102
- `dcterms:creator`, the statement that ties a work to the person who made it.
103
- That statement is real and it is partial. The catalogue credits a person on a
104
- record in other ways, and the BnF holds printed editions whose work it has never
105
- established as a record of its own, so a title missing from the list is not a
106
- title the person did not write. Every answer says so, and none of them reports a
107
- total: a count of what one link reaches would be read as a count of what somebody
108
- wrote.
109
-
110
- **A form code says what a work declares, and an empty one says nothing.** A work
111
- record points at a term of the BnF's work-form vocabulary, and this dataset
112
- publishes no label for those terms. So `roman`, `poesi` and `te` arrive as the
113
- codes they are: some read as words and some do not. The catalogue also states no
114
- form at all on a great many works, and that silence is a form unstated rather
115
- than a genre denied. Keeping the rows that carry one code therefore finds the
116
- works declaring it and never all the works of that form, which is why the codes
117
- are reported and no filter is offered on them.
118
-
119
- **It does not write biographies.** The field the BnF calls biographical
120
- information is an occupation on most records: Rimbaud's says _Poète_, and that is
121
- the whole of it. `get_author` returns that word and says what it is.
122
-
123
- **It exposes no raw SPARQL tool.** An arbitrary query is an unbounded load on a
124
- service a public institution pays for, and nothing here would control what the
125
- caller wrote. Every query this server sends is one of ten written in advance.
126
-
127
- ## The licence, and what it asks of you
54
+ Node 24 or later is required, and no environment variable has to be set.
128
55
 
129
- The BnF states one condition on these metadata:
56
+ ### With Docker
130
57
 
131
- > L'utilisation de ces métadonnées est libre et gratuite sous réserve du maintien
132
- > de la mention de leur source et de l'indication de leur date de récupération.
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "databnf": {
62
+ "command": "docker",
63
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-databnf:2.1.0"]
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
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.
72
+
73
+ ### Bundle, without npm
74
+
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`
133
155
 
134
- Use is free of charge, provided the source is named **and the date of retrieval
135
- is stated**. That second half is a design constraint:
136
- every answer this server produces carries `retrieved_at` in its payload and ends
137
- its text block with the source and that date. A cached answer reports the moment
138
- it was originally read, since that is the date it was retrieved. Repeat both
139
- wherever you show what you got.
156
+ Reads one work's record in full.
140
157
 
141
- ## How it treats the service
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. |
142
162
 
143
- data.bnf.fr is a query service a public institution runs at its own cost, and a
144
- SPARQL query is a more expensive request than fetching a page.
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.
145
169
 
146
- - One request at a time, never in parallel.
147
- - At least three seconds between two of them. Configuration can widen that and
148
- cannot narrow it, including through the published client entry point.
149
- - The `User-Agent` always carries the project identifier and an address where a
150
- person can be reached, whatever a caller sets.
151
- - Answers are cached in memory for fifteen minutes, so a conversation that walks
152
- back over one author does not ask twice.
170
+ ### `list_works`
153
171
 
154
- The BnF publishes no rate for this endpoint. It publishes `Crawl-delay: 5` on its
155
- other host and enforces it there, which is the only figure it has stated about
156
- how fast it wants to be read, and the floor here was set with that in mind.
172
+ Lists the works one person is credited with.
157
173
 
158
- ## Settings
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. |
159
179
 
160
- Every one is optional.
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.
161
183
 
162
- | Variable | Default | Meaning |
163
- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------- |
164
- | `BNF_USER_AGENT` | none | Identify your own client. The project identifier is appended, so the BnF can always reach a person. |
165
- | `BNF_MIN_INTERVAL_MS` | `3000` | Milliseconds between requests. The floor is 3000 and cannot be lowered. |
166
- | `BNF_TIMEOUT_MS` | `60000` | Deadline for one query. |
167
- | `BNF_MAX_RETRIES` | `3` | Attempts after a busy answer. |
168
- | `BNF_CACHE_TTL_MS` | `900000` | How long an answer is kept. `0` turns the cache off. |
169
- | `BNF_CACHE_MAX_ENTRIES` | `200` | How many answers are kept. |
170
- | `BNF_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`. Logs go to stderr. |
184
+ ### `list_editions`
171
185
 
172
- A value that cannot be read is refused, named on stderr, and the default stands.
173
- The setting is not clamped: clamping would let you believe a value took effect
174
- when it did not.
186
+ Lists the editions of one work.
187
+
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. |
193
+
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`.
199
+
200
+ ### `find_digitised`
201
+
202
+ Finds the copies digitised in Gallica attached to a person or a work.
203
+
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.
175
251
 
176
252
  ## Errors
177
253
 
178
- | Code | Means |
179
- | --------------- | ----------------------------------------------------------- |
180
- | `not_found` | The endpoint answered, and the BnF describes no such record |
181
- | `invalid_input` | The request was refused rather than answered |
182
- | `rate_limited` | The endpoint asked this client to slow down |
183
- | `parse_failure` | The answer arrived in a shape this client cannot read |
184
- | `network_error` | The request did not complete |
185
- | `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.
186
256
 
187
- `rate_limited` never means the record is missing. Neither does `timeout`: the
188
- endpoint answers 200 with an empty body when it abandons a query part way
189
- through, and this server calls that a failure to read rather than an absence,
190
- 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. |
191
265
 
192
- ## Using the access layer on its own
266
+ ## As a library
193
267
 
194
- The lower layer imports nothing from the MCP protocol and is published under the
195
- `./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.
196
270
 
197
271
  ```ts
198
272
  import { BnfClient } from "mcp-databnf/client";
199
273
 
200
274
  const client = new BnfClient();
201
- const { data, retrievedAt } = await client.searchAuthors("Rimbaud", 10, 0);
202
- for (const author of data.rows) console.log(author.id, author.name, author.birthYear);
203
- console.log("retrieved", retrievedAt);
275
+ const { data, cached } = await client.getAuthor("cb11907966z");
276
+ console.log(data.label, cached);
204
277
  ```
205
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
+
206
305
  ## Development
207
306
 
208
307
  ```bash
209
308
  npm install
210
- npm test # unit tests, against generated fixtures, no network
211
- npm run typecheck
212
- npm run build
213
- BNF_LIVE=1 npm run test:live # one real query per route
309
+ npm run build:fixtures
310
+ npm test
311
+ npm run check
214
312
  ```
215
313
 
216
- The unit tests reach no network. Fixtures are generated by
217
- `scripts/build-fixtures.mjs` from invented records, so no BnF content lives in
218
- this repository and every test is reproducible. The live suite runs nightly as a
219
- canary, and it is the only thing that would notice the day the catalogue changes
220
- 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.
221
317
 
222
318
  ## Contributing
223
319
 
224
- [CONTRIBUTING.md](CONTRIBUTING.md). Reports of a wrong answer are the most useful
225
- kind: say what you asked, what came back, and what the record actually holds.
226
-
227
- ## 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).
228
324
 
229
- MIT for this code. See [LICENSE](LICENSE).
325
+ ## License
230
326
 
231
- The metadata belong to the Bibliothèque nationale de France and are published
232
- under the condition quoted above: name the source, and state the date of
233
- 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.
234
329
 
235
330
  ---
236
331
 
332
+ <a name="mcp-databnf-français"></a>
333
+
237
334
  # mcp-databnf (français)
238
335
 
239
- Un serveur MCP pour [data.bnf.fr](https://data.bnf.fr), le catalogue ouvert de la
240
- Bibliothèque nationale de France. Chercher un auteur, trouver une œuvre, lister
241
- les éditions que la BnF en conserve, lister ce qu'une personne a écrit, et
242
- rassembler les liens vers ce qui a été numérisé.
336
+ _[English version](#mcp-databnf)_
243
337
 
244
- 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.
245
345
 
246
- ## À 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.
247
350
 
248
- La BnF publie son fichier d'autorité et ses notices bibliographiques en données
249
- liées, et répond aux questions qu'on lui pose en SPARQL. Ce jeu de données sait
250
- des choses qu'une recherche sur le web ignore : lequel de deux homonymes a écrit
251
- tel livre, ce que la BnF a enregistré comme date et lieu de mort de quelqu'un,
252
- quelles éditions d'une œuvre existent et qui les a imprimées, et lesquelles ont
253
- été numérisées.
351
+ ## Installation
254
352
 
255
- Ce serveur pose ces questions pour vous, en sept outils, et rapporte ce que le
256
- catalogue répond sans y ajouter.
353
+ **Installation en un clic**
257
354
 
258
- ## 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**
259
359
 
260
360
  ```bash
261
- npx mcp-databnf
361
+ claude mcp add databnf -- npx -y mcp-databnf
262
362
  ```
263
363
 
364
+ **Claude Desktop, Cursor, et tout client au format de configuration standard**
365
+
264
366
  ```json
265
367
  {
266
368
  "mcpServers": {
@@ -272,126 +374,282 @@ npx mcp-databnf
272
374
  }
273
375
  ```
274
376
 
275
- ## Les sept outils
276
-
277
- | Outil | Répond à |
278
- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
279
- | `search_authors` | Qui la BnF enregistre sous ce nom, et quelle notice est laquelle |
280
- | `get_author` | Dates, lieux, profession, langue, pays, indice Dewey, et la même personne dans VIAF, IdRef, DBpedia, Wikidata et ISNI |
281
- | `search_works` | Quelles œuvres portent ces mots dans leur titre |
282
- | `get_work` | Titre, auteurs, date, langue, forme, sujet, et si la notice est établie ou provisoire |
283
- | `list_works` | Quelles œuvres le catalogue attribue à une personne comme créatrice, avec la date et le code de forme qu'il leur donne |
284
- | `list_editions` | Éditeur, lieu, année, mention d'édition, pagination, ISBN, lien catalogue, et le lien Gallica quand il existe |
285
- | `find_digitised` | Tous les documents numérisés que le catalogue rattache à une personne ou à une œuvre, sous forme de liens |
286
-
287
- ## Ce qu'il ne fait pas, et pourquoi
288
-
289
- **Il ne lit jamais Gallica.** La BnF place ses métadonnées et ses contenus
290
- numérisés sous deux régimes différents. Les métadonnées lues ici sont
291
- réutilisables librement à condition d'en citer la source et la date de
292
- récupération. Les contenus de gallica.bnf.fr relèvent d'un autre régime : leurs
293
- conditions soumettent l'usage dans un projet d'intelligence artificielle à une
294
- licence payante hors recherche académique, et le site refuse ClaudeBot et GPTBot
295
- au niveau du serveur, puis bannit l'adresse appelante après une quinzaine de
296
- requêtes, quel que soit le rythme.
297
-
298
- Une adresse Gallica est donc traitée ici pour ce que le catalogue en dit : une
299
- métadonnée, rendue comme un lien qu'une personne ouvrira. Le serveur vous dira
300
- qu'un tirage bruxellois de 1873 d'_Une saison en enfer_ a été numérisé et vous en
301
- donnera l'adresse. Il ne vous dira pas ce qu'il y a page quatre, si la
302
- numérisation est complète, ni si le document s'ouvre. Un lien `bnf-onto:OCR`
303
- désigne un texte océrisé : le serveur signale qu'il existe et le laisse où il
304
- est. Un test échoue si une adresse sur cet hôte est un jour construite pour être
305
- appelée.
306
-
307
- **Il ne classe pas.** L'index plein texte de la BnF répond si un titre ou un nom
308
- porte les mots demandés. Il ne rend aucune mesure de pertinence, donc les lignes
309
- arrivent dans l'ordre de l'index. Chercher `saison enfer` rend une douzaine
310
- d'études sur Rimbaud avant Rimbaud, et chacune est une correspondance correcte.
311
- Ce serveur le dit, plutôt que d'inventer un ordre que le catalogue ne porte pas,
312
- et il ne rapporte aucun total : sur une recherche qui ne classe pas, un total se
313
- lit comme une mesure de pertinence.
314
-
315
- **Un nom est apparié lettre à lettre, et seulement parmi les personnes.**
316
- L'index compare les caractères donnés à ceux qu'un catalogueur a saisis :
317
- `Dostoïevski`, `Dostoievski` et `Dostoevskij` sont donc trois recherches qui
318
- atteignent trois ensembles de notices différents, et quelques lignes obtenues
319
- sous une graphie ne disent rien des autres. `search_authors` l'énonce sur chaque
320
- réponse, et il ne lit que les notices de personnes : un organisme ou un congrès
321
- est hors de ce qu'il a regardé, si bien qu'une réponse sans ligne n'affirme
322
- jamais que le fichier d'autorité ne tient pas une telle vedette.
323
-
324
- **Une liste d'œuvres est un lien, pas une bibliographie.** `list_works` suit
325
- `dcterms:creator`, l'énoncé qui rattache une œuvre à qui l'a faite. Cet énoncé
326
- est réel et il est partiel : le catalogue crédite une personne sur une notice par
327
- d'autres voies, et la BnF conserve des éditions imprimées dont l'œuvre n'a jamais
328
- été établie comme notice à part entière. Un titre absent de la liste n'est donc
329
- pas un titre que la personne n'a pas écrit. Chaque réponse le dit, et aucune ne
330
- rapporte de total : un compte de ce qu'un seul lien atteint se lirait comme un
331
- compte de ce que quelqu'un a écrit.
332
-
333
- **Un code de forme dit ce qu'une œuvre déclare ; son absence ne dit rien.** Une
334
- notice d'œuvre pointe vers un terme du vocabulaire des formes d'œuvre de la BnF,
335
- et ce jeu de données n'en publie aucun libellé. `roman`, `poesi` et `te`
336
- arrivent donc tels quels : certains se lisent comme des mots, d'autres non. Le
337
- catalogue n'énonce par ailleurs aucune forme sur quantité d'œuvres, et ce
338
- silence est une forme non déclarée, pas un genre exclu. Garder les lignes qui
339
- portent un code trouve donc les œuvres qui le déclarent, jamais toutes les
340
- œuvres de cette forme : les codes sont rapportés, et aucun filtre n'est offert
341
- dessus.
342
-
343
- **Il n'écrit pas de biographies.** Le champ que la BnF appelle information
344
- biographique contient une profession sur la plupart des notices : celle de
345
- Rimbaud dit _Poète_, et c'est tout. `get_author` rend ce mot et dit ce que c'est.
346
-
347
- **Il n'expose aucun outil SPARQL brut.** Une requête arbitraire est une charge
348
- non bornée sur un service qu'une institution publique paie, et rien ici ne
349
- contrôlerait ce que l'appelant a écrit. Chacune des requêtes envoyées est l'une
350
- des dix écrites à l'avance.
351
-
352
- ## La licence, et ce qu'elle vous demande
353
-
354
- La BnF pose une condition :
377
+ Node 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à
378
+ renseigner.
379
+
380
+ ### Avec Docker
381
+
382
+ ```json
383
+ {
384
+ "mcpServers": {
385
+ "databnf": {
386
+ "command": "docker",
387
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-databnf:2.1.0"]
388
+ }
389
+ }
390
+ }
391
+ ```
392
+
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 :
355
552
 
356
553
  > L'utilisation de ces métadonnées est libre et gratuite sous réserve du maintien
357
554
  > de la mention de leur source et de l'indication de leur date de récupération.
358
555
 
359
- La date de récupération est une contrainte de conception : chaque réponse porte `retrieved_at` dans sa charge structurée et termine
360
- son bloc de texte par la source et cette date. Une réponse servie depuis le cache
361
- rapporte le moment où elle a été lue la première fois, puisque c'est là qu'elle a
362
- été récupérée. Reprenez les deux partout où vous montrez ce que vous avez obtenu.
363
-
364
- ## Le rythme
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é.
365
560
 
366
- Une requête à la fois, jamais en parallèle. Au moins trois secondes entre deux
367
- requêtes : la configuration peut élargir cet intervalle et ne peut pas le
368
- réduire, y compris par le point d'entrée `client` publié. Le `User-Agent` porte
369
- toujours l'identifiant du projet et une adresse où joindre une personne. Les
370
- réponses sont gardées quinze minutes en mémoire.
561
+ ## Configuration
371
562
 
372
- La BnF ne publie aucune limite pour ce point d'accès. Elle publie `Crawl-delay: 5`
373
- sur son autre hôte et l'y fait respecter, ce qui est le seul chiffre qu'elle ait
374
- énoncé sur la vitesse à laquelle elle veut être lue.
563
+ Chaque variable est facultative. Elles se posent dans le bloc `env` de la
564
+ configuration du client.
375
565
 
376
- ## Réglages
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. |
377
575
 
378
- Tous facultatifs : `BNF_USER_AGENT`, `BNF_MIN_INTERVAL_MS` (3000, plancher
379
- infranchissable), `BNF_TIMEOUT_MS` (60000), `BNF_MAX_RETRIES` (3),
380
- `BNF_CACHE_TTL_MS` (900000), `BNF_CACHE_MAX_ENTRIES` (200), `BNF_LOG_LEVEL`
381
- (`error`). Une valeur illisible est refusée, signalée sur stderr, et la valeur
382
- 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.
383
578
 
384
579
  ## Erreurs
385
580
 
386
- `not_found`, `invalid_input`, `rate_limited`, `parse_failure`, `network_error`,
387
- `timeout`. `rate_limited` ne veut jamais dire que la notice est absente.
388
- `timeout` non plus : le point d'accès répond 200 avec un corps vide quand il
389
- abandonne une requête en cours de route, et ce serveur appelle cela un échec de
390
- lecture plutôt qu'une absence, parce que les deux se ressemblent et veulent dire
391
- 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).
392
650
 
393
651
  ## Licence
394
652
 
395
- MIT pour ce code. Les métadonnées appartiennent à la Bibliothèque nationale de
396
- France et sont publiées sous la condition citée plus haut : citer la source, et
397
- 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.