archive-org-mcp 1.0.4 → 1.0.6

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
@@ -24,4 +24,9 @@ node dist/index.js
24
24
 
25
25
  Public free APIs only. See `src/api.ts` for exact endpoints.
26
26
 
27
+ ## Licence — no paywall
27
28
 
29
+ Every tool in this package is free: no licence key, no premium tier, no hosted
30
+ licence gateway. The paywall machinery this repo once carried (per-tool licence
31
+ checks and paid hosted endpoints) was removed repo-wide on 2026-10-17. Nothing
32
+ that this package installs or calls asks you to pay.
package/dist/api.js CHANGED
@@ -289,3 +289,57 @@ export async function searchProfile(args) {
289
289
  lines.push(" every figure above is tallied from the rows the search returned, and the sample size is stated rather than implied.");
290
290
  return lines.join("\n");
291
291
  }
292
+
293
+ const CDX = 'http://web.archive.org/cdx/search/cdx';
294
+ export async function snapshotTimeline(args = {}) {
295
+ const raw = String(args?.url ?? '').trim();
296
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw))
297
+ throw new Error('Give a full URL including the scheme, like https://example.com/page.html');
298
+ const limit = Math.min(Math.max(Number(args?.limit ?? 500) || 500, 1), 3000);
299
+ const qs = new URLSearchParams({
300
+ url: raw,
301
+ output: 'json',
302
+ limit: String(limit),
303
+ fl: 'timestamp,original,mimetype,statuscode,digest,length',
304
+ });
305
+ if (args?.from)
306
+ qs.set('from', `${args.from}0101`);
307
+ if (args?.to)
308
+ qs.set('to', `${args.to}1231`);
309
+ const res = await fetch(`${CDX}?${qs}`, {
310
+ headers: { 'User-Agent': 'mrfentmen-archive-org-mcp/1.0', Accept: 'application/json' },
311
+ signal: AbortSignal.timeout(40000),
312
+ });
313
+ if (!res.ok)
314
+ throw new Error(`Wayback CDX returned ${res.status}`);
315
+ const body = (await res.json());
316
+ if (!Array.isArray(body) || body.length < 2)
317
+ throw new Error(`The Wayback Machine has no captures for ${raw} in that window`);
318
+ const [head, ...rows] = body;
319
+ const idx = (name) => head.indexOf(name);
320
+ const ts = idx('timestamp'), mime = idx('mimetype'), code = idx('statuscode'), digest = idx('digest'), len = idx('length');
321
+ const ok = rows.filter((r) => String(r[code]) === '200');
322
+ const digests = new Set(ok.map((r) => r[digest]).filter(Boolean));
323
+ const mimes = new Map();
324
+ let bytes = 0;
325
+ const perYear = new Map();
326
+ for (const r of rows) {
327
+ mimes.set(r[mime], (mimes.get(r[mime]) ?? 0) + 1);
328
+ bytes += Number(r[len] ?? 0);
329
+ const year = String(r[ts]).slice(0, 4);
330
+ perYear.set(year, (perYear.get(year) ?? 0) + 1);
331
+ }
332
+ const topMimes = [...mimes.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
333
+ return [
334
+ `${raw} — ${rows.length} capture(s) read`,
335
+ `Successful (200): ${ok.length} · distinct content digests: ${digests.size} · repeats of an archived page: ${ok.length - digests.size}`,
336
+ `Window: ${String(rows[0]?.[ts]).slice(0, 4)} to ${String(rows[rows.length - 1]?.[ts]).slice(0, 4)}`,
337
+ `Payload recorded: ${(bytes / 1048576).toFixed(2)} MB across the captures`,
338
+ `Media types: ${topMimes.map(([m, n]) => `${m} (${n})`).join(', ') || 'none reported'}`,
339
+ '',
340
+ 'Captures per year:',
341
+ ...[...perYear.entries()].sort().map(([y, n]) => `${y}: ${n}`),
342
+ '',
343
+ `Oldest: http://web.archive.org/web/${rows[0]?.[ts]}/${raw}`,
344
+ ].join('\n');
345
+ }
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
- import { m0_fmtSize, m0_formatItem, m0_getItemDetails, m0_searchItems, m1_formatSnapshot, m1_getAvailability, m1_getSnapshots, m1_getSnapshotText, searchProfile } from "./api.js";
3
+ import { m0_fmtSize, m0_formatItem, m0_getItemDetails, m0_searchItems, m1_formatSnapshot, m1_getAvailability, m1_getSnapshots, m1_getSnapshotText, searchProfile, snapshotTimeline } from "./api.js";
4
4
  const text = (value) => ({ content: [{ type: 'text', text: value }] });
5
5
  const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
6
6
  const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
@@ -119,9 +119,8 @@ export function createServer() {
119
119
  }),
120
120
  annotations: READ_ONLY,
121
121
  }, async (args) => {
122
- // TODO: implement timeline locally (paywall removed)
123
122
  try {
124
- return textError("Not yet implemented: timeline");
123
+ return text(await snapshotTimeline(args));
125
124
  }
126
125
  catch (e) {
127
126
  return textError(String(e));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archive-org-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Internet Archive search, item metadata, and Wayback snapshots.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/mrfentmen/awesome-mcps",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.0.1",
9
+ "version": "1.0.6",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "archive-org-mcp",
14
- "version": "1.0.1",
14
+ "version": "1.0.6",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }