๐ค AI & Agents ยท 9 min read
agent-tools.json: How an AI Agent Finds Out What Your Site Can Do
agent-tools.json is a file at a site root that lists the actions the site offers, the page each one lives on, whether sign-in is needed, and what the caller has to supply. An agent reads it instead of guessing a site's abilities from page text.
It is not an official standard. We wrote the one on this site, and we check it against the live pages rather than trusting it. The check we ran while writing this found four entries that resolve to a real tool and one that an anonymous agent cannot reach at all. Both parts are below, because the second is the more useful half.
Why a reading list is not enough
Three files now sit at the root of a site that wants to be legible to machines, and they answer different questions.
- robots.txt says what may be fetched. It is a request about access, and RFC 9309 is explicit that it is not a form of authorization. We covered the AI crawler tokens in the robots.txt guide.
- llms.txt says what is worth opening. The convention is documented at llmstxt.org, and ours is 11,004 bytes of grouped links with a line of description each. What it does not say is what the site can do.
- WebMCP is the browser API that lets a page hand tools to an agent. Chrome documents two forms: the declarative API, where a form carries
toolnameandtooldescription, and the imperative API, where a script registers a tool with a typed input schema. We wrote about the whole layer in What Is WebMCP?
The gap is timing and reach. A WebMCP declaration is only visible once a capable browser has opened the page. An agent deciding where to send a user, or an agent running in a browser without the trial flag switched on, has nothing structured to read. A static index of actions covers that case, and the name we and a few others use for it is agent-tools.json.
What our file contains
The file we measured today is 7,142 bytes, served as application/json, stamped spec_version 1.1 and dated 19 September. It opens with a publisher block and a short section of notes for agents, then one object per action:
{
"spec_version": "1.1",
"updated": "2026-09-19",
"publisher": { "name": "NoCodeCSV", "url": "https://nocodecsv.com" },
"notes_for_agents": [
"All tools run in the user's browser...",
"The /dashboard workspace requires a signed-in user."
],
"tools": [
{
"name": "split_large_csv",
"webmcp_toolname": "splitLargeCsv (declarative) | splitCsvText (imperative)",
"intent": "Split a CSV that is too large for Excel into smaller files.",
"page": "https://nocodecsv.com/tools/csv-splitter",
"requires_auth": false,
"steps": [
{ "action": "upload_file", "selector": "#toolFileInput" },
{ "action": "set_value", "selector": "input[type=number]" },
{ "action": "submit", "selector": "button[type=submit]" }
]
}
]
}Fields that earn their place: requires_auth, so a caller knows whether sign-in is part of the job; steps with real selectors, so an agent does not have to infer the interface; and webmcp_toolname, so the static entry and the in-page declaration can be matched to each other. That last field is what makes the file testable, which is where the next section comes from.
What we measured against the live pages
A declaration file is only as good as the pages under it. We loaded each page in a Chromium build launched with the WebMCP testing flag, awaited navigator.modelContext.getTools() and compared the result with what the JSON claims.
| Page | Declared in the file | What getTools() returned | Parameters |
|---|---|---|---|
| /tools/csv-delimiter-converter | fixCsvDelimiter | fixCsvDelimiter | none |
| /tools/csv-splitter | splitLargeCsv, splitCsvText | both present | csvText (required), rowsPerFile |
| /tools/json-csv-converter | convertJsonCsv, convertJsonCsvText | both present | text and direction (both required) |
| /dashboard | uploadCsvFile, askQuestionAboutData | none โ the page redirects to sign-in | n/a |
| /tools/spreadsheet-charts | nothing โ listed as a link | none | n/a |
Four of five entries hold up. The one that does not is instructive: the dashboard entry is marked requires_auth: true, and when we loaded /dashboard without a session the browser landed on /sign-in?redirect_url=... with zero forms, zero file inputs and zero tools registered. So the declaration is accurate about the requirement, and an agent following it still cannot do the task. A list of capabilities is not a grant of access.
The remaining gap is the file input. On a tool page whose only input is a file picker, the tool appears in the list with an empty parameter set. That is why two entries in our file are imperative tools that take the CSV or JSON as text: splitCsvText accepts csvText plus an optional rowsPerFile, and convertJsonCsvText accepts text and direction. Both were introduced for exactly that reason, and the file records it so the next person editing it does not remove them as duplicates.
The 200 that is not your file
Here is a check worth copying if you publish anything at a static path. On one of our other sites, a request to /agent-tools.json returns 200 with content type application/json and 8,223 bytes. A request to a path that does not exist returns 200 with content type text/html and 28,843 bytes, because the host falls back to the single-page app shell for unknown routes.
Two different results, same status code. A curl that prints only %{http_code} cannot tell them apart, and neither can a monitoring check that watches for a 404. Print the type as well:
curl -s -o /dev/null -w "http=%{http_code} type=%{content_type} "
https://example.com/agent-tools.jsonThe check has already caught one real problem for us. On 19 September the same request against that site returned text/html for the path, which meant the file was not published and the agent on the other end would have received a web page. On 20 September it returns application/json. Nothing about the status code changed in between, which is the point.
How to keep it from drifting
- One entry per action, with a page URL. If two pages do the same job, the file should say which one callers should use.
- Test the auth flag signed out. We only know the dashboard entry behaves as documented because we loaded it without a session.
- Record the exact tool name the page declares, then re-run the check after any UI change. A rename in a form is silent otherwise.
- Point steps at markup that exists. Our selectors name file inputs that are in the DOM at load time, not inputs created when a button is clicked.
- Mark links as links. One of our five entries has no tool behind it:
webmcp_toolnameis null and a note explains that the page is a landing page. Implying a tool where there is only a page is the fastest way to make the file useless. - Stamp it and re-verify the stamp. The
updatedfield is only worth reading if someone re-runs the check on that date.
What still does not work
Two limits we hit ourselves, both worth stating rather than leaving for a reader to discover. A file picker does not become a tool parameter, so any action that begins with "upload your spreadsheet" is out of reach for a browser agent today; the workaround is a text-shaped tool, which is not always possible. And an authenticated tool is invisible to an unauthenticated agent, so the parts of a product behind a login are declared but not usable. Neither limit is a reason to skip the file. Both are reasons to describe the site accurately instead of optimistically.
If your site publishes tools, the order that worked for us was robots.txt first, then llms.txt, then this file, then the WebMCP declarations themselves. Each one is checkable, and the checks are one command each.
Frequently asked questions
What is agent-tools.json?
agent-tools.json is a file published at a site root that lists the actions the site offers. Each entry names the action, the page it lives on, whether sign-in is required, the steps a caller should take, and the tool name that page declares to the browser. An assistant reads the file to learn what the site can do without scraping page text and guessing.
Is agent-tools.json an official standard?
No. There is no specification behind the name yet. llms.txt is a community convention documented at llmstxt.org, and WebMCP is a Chrome API with published documentation. agent-tools.json is the piece we wrote for ourselves, published openly so a reader can compare it with their own. Treat any file with this name as a convention that a site chose to follow, not as something a browser enforces.
How is agent-tools.json different from llms.txt?
llms.txt is a reading list: the pages worth opening, with a short description of each. agent-tools.json is a task list: the actions a site can perform, where they live, and what the caller must supply. One answers what is worth reading, the other answers what can be done. Our llms.txt is 11,004 bytes of prose and links; our agent-tools.json is 7,142 bytes of named actions.
How is agent-tools.json different from WebMCP?
WebMCP is how a page exposes a tool to a browser-based agent: forms carry toolname and tooldescription, or a script calls registerTool with an inputSchema. agent-tools.json sits one step earlier. It is a static index of what exists where, so an agent that cannot run WebMCP, or that wants to decide whether a site is worth visiting, has something to read. In our file the two are linked: each entry records the webmcp_toolname the page declares.
Where does the file go, and what should it contain?
At the site root, next to robots.txt and llms.txt, so it is reachable at /agent-tools.json. Ours carries a spec_version, an updated date, a publisher block, notes for agents, and one object per action with name, intent, page, requires_auth, steps and output. The steps field points at real DOM targets: our selectors name file inputs that exist in the page markup rather than being created on click.
Can an agent actually use any of these tools?
In our test, four of the five entries resolved to a callable tool on a public page, and one did not. The dashboard pair sits behind a sign-in redirect, so an anonymous agent that follows the entry lands on a login page with no tools present. Declaring an action is not the same as granting access to it, and the file says so on that entry.
How do I check that the file is really being served?
Check the content type, not the status code. Static hosts that fall back to a single-page app answer 200 for paths that do not exist, so a 200 proves only that something came back. We measured exactly that on one of our other sites: a made-up sibling path returned 200 with content type text/html and 28,843 bytes, while agent-tools.json returned 200 with application/json and 8,223 bytes. One command shows both: curl -s -o /dev/null -w 'http=%{http_code} type=%{content_type} ' https://example.com/agent-tools.json
What can an agent still not do on a site like this?
Hand over a file, in the case we measured. A page whose only input is a file picker exposes a tool whose parameter list is empty, because the file input does not become a callable argument. That is why two of our declarations are imperative tools that accept the CSV or JSON as text instead. Sign-in walls are the other limit: a tool that only exists after authentication is invisible to an agent browsing without an account.
Tools mentioned in this guide
This article is about a JSON file, so the tools worth naming are the ones around it:
- OpenCode Go โ the page checks in this article were written as a script and run from a terminal, which is faster with an assistant than by hand. Try OpenCode Go
- Stack AI โ if an agent action needs to land somewhere you can read later, a workflow can turn an incoming request into a row, a summary or an alert. Try Stack AI
- Softr โ for a site that is really a catalogue, publishing the collection as a no-code app gives each item a stable URL, which is what any declaration file needs to point at. Try Softr
Some links above are affiliate links โ if you buy through them we may earn a commission at no extra cost to you. OpenCode Go uses our referral link; the other two currently point to each vendor's official page until our tracking links are approved.
Want This Layer Written and Verified for Your Site?
The file in this article is public, and the cross-check output is above. If you would rather have someone write and re-test the same set of declarations on your own pages, that is the work I do.
I build this layer for other sites โ llms.txt, agent-tools.json, WebMCP declarations โ and verify each one against the live pages. Details at /agent-ready.
Related reading
AI & Analysis โ other guides that pair well with this one.
Browse all guides in the NoCodeCSV blog.