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 +16 -0
- package/README.md +5 -4
- package/dist/index.js +70 -69
- package/dist/index.js.map +1 -1
- package/dist/loc/client.d.ts +2 -1
- package/dist/loc/client.js +68 -67
- package/dist/loc/client.js.map +1 -1
- package/package.json +14 -13
- package/server.json +4 -4
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
|
[](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-libraryofcongress)
|
|
7
7
|
[](https://glama.ai/mcp/servers/smeet666/mcp-libraryofcongress)
|
|
8
8
|
[](https://m8ven.ai/mcp/smeet666-mcp-libraryofcongress-1rr3lc)
|
|
9
|
+
[](https://lobehub.com/mcp/smeet666-mcp-libraryofcongress)
|
|
9
10
|
[](https://cursor.com/en/install-mcp?name=libraryofcongress&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1saWJyYXJ5b2Zjb25ncmVzcyJdfQ%3D%3D)
|
|
10
11
|
[](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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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}
|
|
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 ?
|
|
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 });
|