Artifacts
Artifacts
Spoken by ArtifactsProvider. Same resources, same shapes, its own quirks kept inside.
Access
CreatecreateProvider("artifacts")
Importimport { ArtifactsProvider } from"@agntn/forges/artifacts"
EnvCLOUDFLARE_API_TOKEN, AGNTN_CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_ACCOUNT_ID
CLIcf auth login session
- Header
- Authorization: Bearer
- Anonymous
- none, every read needs a token
- Threads
- none, no pull requests to hang them on
- Code search
- unsupported, explicit error
- Templates
- none
Address an Artifacts namespace
import { createProvider } from "@agntn/forges";
const artifacts = await createProvider("artifacts", {
token: process.env.CLOUDFLARE_API_TOKEN,
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
});
const repo = await artifacts.repos.get("default", "starter-repo");
const readme = await artifacts.repos.readContents("default", "starter-repo", "README.md");
Artifacts is Git without the forge around it. An account holds namespaces, a namespace holds repositories, and that's the whole model. So owner is the namespace. Everything else lines up with the shapes you already know.
Where does the token come from?
Leave both fields out and @agntn/credentials looks for you. The token comes from CLOUDFLARE_API_TOKEN, then the session cf auth login left behind. The account comes from AGNTN_CLOUDFLARE_ACCOUNT_ID or CLOUDFLARE_ACCOUNT_ID, or from that session when it reaches exactly one account. A token from the environment wants its account from there too.
That session token expires. So the provider asks the chain on every request, and a 401 on a session token gets one refresh and one retry. Your MCP server keeps reading after lunch. Nice.
There's no guest mode. { token: "" } means go look, same as leaving it out.
What it reads
Paths below hang off /accounts/:account/artifacts.
| Resource | Endpoint |
|---|---|
| repos | GET /namespaces/:namespace/repos, …/repos/:name |
| repository contents | GET …/repos/:name/file?ref=&path=, …/tree/:hash, after the ref resolves |
| commits | GET …/repos/:name/log?ref=&offset=&limit=, …/commit/:hash |
Repositories page by cursor, and a cursor can't jump. Page 3 reads pages 1 and 2 on the way, 200 rows a request at most, plus one row to tell if there's more. The log pages by offset like a sane API.
A full SHA goes straight to …/commit/:hash. A branch or tag resolves through the log first. A file is one read at that commit. A directory walks the trees, one request per level.
What comes back
privateis alwaystrue. Every read needs a token.urlis the REST address of the repository.cloneUrlis the Git remote.- Only a fork made inside Artifacts counts as one. A
sourceofartifacts:<namespace>/<repo>setsisForkandparent. - Commit dates arrive as Unix seconds and leave as ISO 8601.
- Directory entries have
size: null. The tree doesn't send one. commits.getreturnsfiles: []withfilesComplete: null. There's no diff in the API, so the honest answer is "don't know", not "nothing changed".
Gotchas
- The log reads
ref=HEADas a ref that doesn't exist and answers with an empty list. forges leavesHEADout, so the default branch answers. - An unknown ref isn't a 404 there either. Just an empty log. forges turns it into
NotFoundError, so a typo doesn't pass for an empty repository. commits.listtakesrefand nothing else.path,sinceanduntilare a 501 before any request goes out.- The file route answers 404 for a directory too. That's why a path that isn't a file gets the tree walk.
What isn't there
Issues, pull requests, comments, review threads, users, releases, CI, code search and templates. None of it. Just Git, no drama.
Why put storage without a forge into a forge library then? Because reading is the same job. An agent that reads a file on GitHub reads one here with the same call. Everything else is a ForgesError with status 501 and a sentence, so the agent stops instead of retrying.
Where it lives
src/providers/artifacts.ts, with the credentials from @agntn/credentials/cloudflare.