machine contract

For agents

This site is built to be read by machines as well as people. The tools are deterministic functions that run entirely in your browser — no backend. What you type is never transmitted; when you pass input in a URL, the fragment (#) keeps it client-side. Below is the contract: where the machine-readable surfaces live, how to call a tool in the page, and what happens automatically where the browser supports WebMCP.

Type into a tool and nothing leaves the page — verify it in your network panel. Pass input after the URL's # and it stays client-side too; a ?query is sent to the server like any URL and may be logged.

Machine-readable surfaces

/llms.txt

Plain-text site index for language models — pages, projects, tools, lab, and every machine surface.

/index.md

This site's pages as markdown mirrors. Every page has one: /about.md, /now.md, /blog/<slug>.md, and so on.

/tools/index.json

Machine manifest of the whole tool suite: slug, description, category, status, page url, and per-tool manifest url.

/tools/<slug>.json

Per-tool manifest: the page url, keywords, the privacy note, and the in-page API contract.

In-page tool API

Each tool page hydrates a small island and then exposes its API on window.__tools[slug]:

  • run(input) — calls the underlying pure function and returns its result. The input is a plain object; the accepted fields are documented per tool by describe().
  • describe() — returns { slug, params, returns } so you can discover a tool's parameters and shape at runtime.

Results also render into the element with id tool-output, a stable selector if you would rather read the rendered DOM than call the API.

// on a live tool page, after hydration
window.__tools["base64"].describe();
const result = window.__tools["base64"].run({ input: "…" });

// or read the rendered output
document.getElementById("tool-output").textContent;

URL invocation

Documented parameters pre-fill a tool's inputs and run on load — so a URL is enough to drive a tool, no scripting required. Put them in the fragment, after #: the browser never sends the fragment to the server, so the input stays entirely client-side. The exact parameters are listed in each tool's describe() and its /tools/<slug>.json manifest.

https://aaronchartier.com/tools/base64#input=hello

A ?query form still works for back-compat, but a query string is part of the request and can be logged at the origin like any URL — prefer the # form for anything sensitive.

WebMCP

Where the browser exposes navigator.modelContext (the Web Model Context Protocol), every tool registers itself there automatically as you load its page — read-only, since the tools are deterministic and side-effect-free. Where the API is absent, this is a no-op and the tools remain reachable through the in-page API, query parameters, and the manifests above. No configuration, nothing to enable.

This page as markdown: /for-agents.md. Start at the tools index, or read the llms.txt and agents.json manifests directly.