Configuration
Config files
Under Node, the library loads configuration through c12: archives.config.ts, .archives, or an archives key in package.json.
export default {
storage: {
cache: true,
ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
prefix: "archives",
},
performance: {
concurrency: 3,
batchSize: 20,
timeout: 10_000,
retries: 1,
},
};
Overrides per environment live under $development, $production and $test. getConfig() returns the resolved object, resetConfig() drops the cache, resolveConfig({ cwd }) loads from somewhere else without caching. concurrency and batchSize must be positive integers; invalid values fail before processing starts.
Timeouts and cancellation
timeout applies to each HTTP attempt, including the response body, even when a caller supplies an AbortSignal. Retries start with a fresh deadline; cancelling the caller's signal stops retrying. This isn't a deadline for the whole archive operation: index lookup, playback and retries can each take time.
The library accepts finite timeouts from 0 to 2_147_483_647 milliseconds, rounding positive fractions up. 0 disables the deadline without disabling caller cancellation. Tool calls keep their narrower schema limits. HTTP retries don't cover failures while reading a response body.
Wayback, Common Crawl and Archive.today lift the configured value to 60 seconds. The Wayback and Common Crawl indexes regularly need more than ten, and Archive.today keeps the minute its timemap request always asked for. A timeout on the provider, on createArchive or on the call still wins, shorter or not.
Cache
Responses are cached through unstorage, in memory unless you say otherwise. Any driver works:
import { configureStorage } from "@agntn/archives";
import fsDriver from "unstorage/drivers/fs";
await configureStorage({
driver: fsDriver({ base: "./cache" }),
ttl: 24 * 60 * 60 * 1000, // 1 day
});
Keys are {prefix}:{provider key hash}:{query hash}, where the provider key includes the options that change a result set, such as a Wayback collapse or an Archive-It collection. Two calls that differ only in cosmetic options share an entry. clearProviderStorage() empties the cache.
Per call:
await archive.snapshots("example.com", { cache: false }); // skip the cache
await archive.snapshots("example.com", { ttl: 60_000 }); // keep this one for a minute
Listings and bodies are cached separately. A body read is cached against the capture it resolved to, so asking for timestamp: "2015" twice reads the archive once.
Where the config is read from
The library looks for config in process.cwd(). The MCP server doesn't. A client starts archives mcp in whatever directory it has open. And an archives.config.ts from a repo you're only browsing is code you never chose to run. So archives mcp pins discovery to the home directory of the account running it.
Embedding createMcpServer() in your own host? Pin the root yourself, before the first lookup. setConfigCwd() comes from the package root, next to setConfig(), not from /mcp:
import { homedir } from "node:os";
import { setConfigCwd } from "@agntn/archives";
import { createMcpServer } from "@agntn/archives/mcp";
setConfigCwd(homedir()); // what `archives mcp` does
const server = createMcpServer();
Both entries share one config, so the server reads from there. Skip the call and the first lookup runs whatever archives.config.ts sits in the current directory. Great news for whoever wrote it.
Running on Workers or in a browser?
What would config discovery read there? There's no disk. So it doesn't look. The package picks discovery by export condition, and under browser or workerd it gets a build with no c12 in it. Vite, Wrangler and Nitro's Cloudflare preset all pick that one. You get the defaults. In a Vite build, c12, jiti and a tar extractor stay out of your bundle.
Want your own values there? Hand them over yourself:
import { setConfig } from "@agntn/archives";
setConfig({ performance: { timeout: 20_000 } });
Call it once at startup. Anything you leave out comes from the defaults, and $env layers don't apply, since you're passing final values. Under Node, c12 doesn't even load until something asks for discovery. resetConfig() hands control back to discovery. This site does exactly that from a Nitro plugin.
Perma.cc key
Perma.cc is the one provider that needs a credential. In code, pass apiKey to the factory. Over MCP and in the agent extensions, the key comes from PERMA_CC_API_KEY or PERMACC_API_KEY and is never accepted as a tool argument; it is redacted before the options reach a transcript.