mcp-libraryofcongress 3.0.1 → 3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.1.0
4
+
5
+ - **The live canary no longer reports a slow night at the Library as a broken
6
+ contract.** A read that meets silence or push-back spends deadlines, waits and
7
+ spacing in series, and it says what the Library did only once it has spent them
8
+ all. The runner gave up first, so the case that was meant to be left out failed
9
+ the run instead. What a read can take is now published by the module that
10
+ spends it, the live suite takes its deadline from there, and tests hold the
11
+ bound to what a read actually takes.
12
+ - **Node.js 24.22.1 or later**, inside the Node.js 24 line. The package, the
13
+ extension manifest and the image all declare it.
14
+ - **zod `^4.6.7`.** Below 4.6.6, zod can exhaust memory while
15
+ validating oversized constrained collections, and the range accepts none of
16
+ those versions.
17
+ - **Dependency floors:** `@modelcontextprotocol/sdk` `^1.32.2`.
18
+
3
19
  ## 3.0.1
4
20
 
5
21
  - **Every tool is documented, with its arguments and what its answer carries.**
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-libraryofcongress)
7
7
  [![Glama](https://glama.ai/mcp/servers/smeet666/mcp-libraryofcongress/badges/score.svg)](https://glama.ai/mcp/servers/smeet666/mcp-libraryofcongress)
8
8
  [![M8ven](https://m8ven.ai/badge/mcp/smeet666-mcp-libraryofcongress-1rr3lc?variant=verified)](https://m8ven.ai/mcp/smeet666-mcp-libraryofcongress-1rr3lc)
9
+ [![LobeHub](https://lobehub.com/badge/mcp/smeet666-mcp-libraryofcongress)](https://lobehub.com/mcp/smeet666-mcp-libraryofcongress)
9
10
  [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=libraryofcongress&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1saWJyYXJ5b2Zjb25ncmVzcyJdfQ%3D%3D)
10
11
  [![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=libraryofcongress&config=%7B%22name%22%3A%22libraryofcongress%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-libraryofcongress%22%5D%7D)
11
12
 
@@ -64,7 +65,7 @@ Node 24 or later is required, and no environment variable has to be set.
64
65
  "mcpServers": {
65
66
  "libraryofcongress": {
66
67
  "command": "docker",
67
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-libraryofcongress:3.0.1"]
68
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-libraryofcongress:3.1.0"]
68
69
  }
69
70
  }
70
71
  }
@@ -77,7 +78,7 @@ port, no credential.
77
78
 
78
79
  ### Bundle, without npm
79
80
 
80
- Download `mcp-libraryofcongress-3.0.1.mcpb` from
81
+ Download `mcp-libraryofcongress-3.1.0.mcpb` from
81
82
  [the latest release](https://github.com/smeet666/mcp-libraryofcongress/releases/latest)
82
83
  and open it. A client that supports MCP bundles installs it on its own, with no
83
84
  npm and no configuration file to edit. The bundle carries its dependencies, so
@@ -363,7 +364,7 @@ renseigner.
363
364
  "mcpServers": {
364
365
  "libraryofcongress": {
365
366
  "command": "docker",
366
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-libraryofcongress:3.0.1"]
367
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-libraryofcongress:3.1.0"]
367
368
  }
368
369
  }
369
370
  }
@@ -376,7 +377,7 @@ aucun volume, aucun port, aucun identifiant.
376
377
 
377
378
  ### Bundle, sans npm
378
379
 
379
- Téléchargez `mcp-libraryofcongress-3.0.1.mcpb` depuis
380
+ Téléchargez `mcp-libraryofcongress-3.1.0.mcpb` depuis
380
381
  [la dernière publication](https://github.com/smeet666/mcp-libraryofcongress/releases/latest)
381
382
  et ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et
382
383
  sans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
11
  import process from "process";
12
12
 
13
13
  // src/version.ts
14
- var PKG_VERSION = "3.0.1";
14
+ var PKG_VERSION = "3.1.0";
15
15
  var REPO_URL = "https://github.com/smeet666/mcp-libraryofcongress";
16
16
 
17
17
  // src/config.ts
@@ -164,6 +164,73 @@ var Cache = class {
164
164
  }
165
165
  };
166
166
 
167
+ // src/loc/rateLimiter.ts
168
+ var WIDEST_SPACING_FACTOR = 8;
169
+ var RateLimiter = class {
170
+ baseIntervalMs;
171
+ maxIntervalMs;
172
+ intervalMs;
173
+ lastStartedAt = 0;
174
+ queue = Promise.resolve();
175
+ calmStreak = 0;
176
+ constructor(options) {
177
+ this.baseIntervalMs = options.intervalMs;
178
+ this.maxIntervalMs = options.maxIntervalMs ?? options.intervalMs * WIDEST_SPACING_FACTOR;
179
+ this.intervalMs = options.intervalMs;
180
+ }
181
+ /** The spacing in force, which callers report rather than guess. */
182
+ get currentIntervalMs() {
183
+ return this.intervalMs;
184
+ }
185
+ /**
186
+ * Run `task` with nothing else in flight.
187
+ *
188
+ * This only serialises. Spacing is claimed per attempt through
189
+ * `beforeRequest`, because a task that retries makes several requests and
190
+ * each of them owes the site the same gap.
191
+ */
192
+ schedule(task) {
193
+ const result = this.queue.then(task, task);
194
+ this.queue = result.then(
195
+ () => void 0,
196
+ () => void 0
197
+ );
198
+ return result;
199
+ }
200
+ /** Wait until enough time has passed since the previous request began. */
201
+ async beforeRequest() {
202
+ const elapsed = Date.now() - this.lastStartedAt;
203
+ const wait = Math.min(this.intervalMs, this.intervalMs - elapsed);
204
+ if (wait > 0) {
205
+ await sleep(wait);
206
+ }
207
+ this.lastStartedAt = Date.now();
208
+ }
209
+ /** Called when the site asks for room: double the gap, up to the ceiling. */
210
+ pushBack() {
211
+ this.calmStreak = 0;
212
+ this.intervalMs = Math.min(this.maxIntervalMs, this.intervalMs * 2);
213
+ }
214
+ /**
215
+ * Called on a clean answer. Recovery takes several in a row, so one lucky
216
+ * response after a rough patch does not undo the caution that earned it.
217
+ */
218
+ succeeded() {
219
+ if (this.intervalMs === this.baseIntervalMs) {
220
+ return;
221
+ }
222
+ this.calmStreak += 1;
223
+ if (this.calmStreak < 3) {
224
+ return;
225
+ }
226
+ this.calmStreak = 0;
227
+ this.intervalMs = Math.max(this.baseIntervalMs, Math.round(this.intervalMs / 2));
228
+ }
229
+ };
230
+ function sleep(ms) {
231
+ return new Promise((resolve) => setTimeout(resolve, ms));
232
+ }
233
+
167
234
  // src/loc/http.ts
168
235
  var ZERO_MAX_AGE = /^(?:max-age|s-maxage)\s*=\s*0$/;
169
236
  var RETRYABLE = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
@@ -1133,72 +1200,6 @@ function toCollections(payload, url, onSkip, settled = true) {
1133
1200
  return { paging: toPaging(payload, url, settled), collections };
1134
1201
  }
1135
1202
 
1136
- // src/loc/rateLimiter.ts
1137
- var RateLimiter = class {
1138
- baseIntervalMs;
1139
- maxIntervalMs;
1140
- intervalMs;
1141
- lastStartedAt = 0;
1142
- queue = Promise.resolve();
1143
- calmStreak = 0;
1144
- constructor(options) {
1145
- this.baseIntervalMs = options.intervalMs;
1146
- this.maxIntervalMs = options.maxIntervalMs ?? options.intervalMs * 8;
1147
- this.intervalMs = options.intervalMs;
1148
- }
1149
- /** The spacing in force, which callers report rather than guess. */
1150
- get currentIntervalMs() {
1151
- return this.intervalMs;
1152
- }
1153
- /**
1154
- * Run `task` with nothing else in flight.
1155
- *
1156
- * This only serialises. Spacing is claimed per attempt through
1157
- * `beforeRequest`, because a task that retries makes several requests and
1158
- * each of them owes the site the same gap.
1159
- */
1160
- schedule(task) {
1161
- const result = this.queue.then(task, task);
1162
- this.queue = result.then(
1163
- () => void 0,
1164
- () => void 0
1165
- );
1166
- return result;
1167
- }
1168
- /** Wait until enough time has passed since the previous request began. */
1169
- async beforeRequest() {
1170
- const elapsed = Date.now() - this.lastStartedAt;
1171
- const wait = Math.min(this.intervalMs, this.intervalMs - elapsed);
1172
- if (wait > 0) {
1173
- await sleep(wait);
1174
- }
1175
- this.lastStartedAt = Date.now();
1176
- }
1177
- /** Called when the site asks for room: double the gap, up to the ceiling. */
1178
- pushBack() {
1179
- this.calmStreak = 0;
1180
- this.intervalMs = Math.min(this.maxIntervalMs, this.intervalMs * 2);
1181
- }
1182
- /**
1183
- * Called on a clean answer. Recovery takes several in a row, so one lucky
1184
- * response after a rough patch does not undo the caution that earned it.
1185
- */
1186
- succeeded() {
1187
- if (this.intervalMs === this.baseIntervalMs) {
1188
- return;
1189
- }
1190
- this.calmStreak += 1;
1191
- if (this.calmStreak < 3) {
1192
- return;
1193
- }
1194
- this.calmStreak = 0;
1195
- this.intervalMs = Math.max(this.baseIntervalMs, Math.round(this.intervalMs / 2));
1196
- }
1197
- };
1198
- function sleep(ms) {
1199
- return new Promise((resolve) => setTimeout(resolve, ms));
1200
- }
1201
-
1202
1203
  // src/loc/client.ts
1203
1204
  function withGuarantees(config) {
1204
1205
  const defaults = loadConfig({});
@@ -1444,7 +1445,7 @@ function indentMarkerLines(body) {
1444
1445
  return body.replace(/^(Note:|Source:)/gm, " $1");
1445
1446
  }
1446
1447
  function ok(structured, body, options = {}) {
1447
- const credit = options.sourceUrl ? `${ATTRIBUTION} \u2014 ${options.sourceUrl}` : ATTRIBUTION;
1448
+ const credit = options.sourceUrl ? `${ATTRIBUTION} (${options.sourceUrl})` : ATTRIBUTION;
1448
1449
  const noteLines = (options.notes ?? []).map((note) => `Note: ${note}`);
1449
1450
  while (noteLines.length > 0 && noteLines.join("\n").length > MAX_TEXT_CHARS / 2) {
1450
1451
  noteLines.pop();
@@ -1685,7 +1686,7 @@ async function runGetItem(client, args) {
1685
1686
  if (resources && resources.length > 0) {
1686
1687
  lines.push("", "Copies served online:");
1687
1688
  for (const resource of resources) {
1688
- lines.push(` ${resource.caption ?? "copy"}${resource.url ? ` \u2014 ${resource.url}` : ""}`);
1689
+ lines.push(` ${resource.caption ?? "copy"}${resource.url ? `: ${resource.url}` : ""}`);
1689
1690
  }
1690
1691
  }
1691
1692
  return ok(structured, lines.join("\n"), { notes, sourceUrl: data.sourceUrl });