Prompt 1 — build this starter from scratch
Use this when you have no site yet, or you want your own copy of this one. It describes the three tools, the file layout, the comment style, and the self-documenting page in enough detail that an agent can reproduce what you're looking at — then deploy it and test it.
Build a WebMCP-enabled personal site and deploy it to Netlify. It is a
working link hub with a guestbook that exposes exactly three WebMCP tools,
and the deployed page doubles as its own documentation.
STACK — no build step, deliberately.
- Static files, served exactly as written, so the code a visitor reads in
DevTools is byte-for-byte the code on disk:
public/index.html
public/data/profile.json
public/assets/webmcp.js (the tool declarations — the centerpiece)
public/assets/data.js (every read and write, shared by page + tools)
public/assets/ui.js (rendering, form handling, copy buttons)
public/assets/style.css (hand-written, dark developer-tool aesthetic)
- One Netlify Function: netlify/functions/messages.mts, written in TypeScript
with Config/Context types from @netlify/functions and an in-code
`export const config = { path: '/api/messages' }`. Use Netlify.env.get()
rather than process.env.
- Netlify Blobs for the guestbook — no provisioning and no migrations, so it
works on the first deploy. One blob per message, keyed
messages/<ISO timestamp>-<random>.json: timestamp-prefixed keys sort
chronologically as plain strings, and separate blobs mean two concurrent
writers can't clobber each other the way one shared JSON array would. Use
strong consistency, because an agent usually re-reads immediately after
writing to confirm and would otherwise miss its own write.
- netlify.toml: publish = "public", the functions directory, and headers
Origin-Agent-Cluster: ?1 plus Permissions-Policy: tools=(self).
- No framework, no bundler, no front-end TypeScript. ES modules, single
quotes, 2-space indent, no trailing semicolons, ~100 column wrap.
CONTENT — one source of truth.
public/data/profile.json holds name, tagline, location, availability, and a
links array of { label, url, description, tags }. The page renders from it
and the tools answer from it. Put every read and write in data.js and have
BOTH the UI and the tools call it. Never build a second code path for agents:
a tool is a thin wrapper around a capability the site already has, so a human
visitor and an agent can never be told different things.
THE THREE TOOLS — public/assets/webmcp.js.
Use exactly this API. It is current, and older patterns you may recall have
been removed from the spec:
const modelContext = document.modelContext ?? navigator.modelContext
if (modelContext && 'registerTool' in modelContext) {
const controller = new AbortController()
await modelContext.registerTool(definition, { signal: controller.signal })
}
1. get_profile — READ. No arguments, so inputSchema is
{ type: 'object', properties: {} }. Returns name, tagline, location,
availability, linkCount, and links (label, url, description).
annotations: { readOnlyHint: true }.
2. search_links — SEARCH. One required string argument, query, with its own
description telling the model to pass a keyword like "music" rather than
the user's entire sentence. Return { query, resultCount, results } instead
of a bare array: an empty array is ambiguous ("did it fail?"), while
resultCount: 0 next to the echoed query reads as a real answer.
annotations: { readOnlyHint: true }.
3. leave_message — WRITE. Arguments: name (optional, defaults to
"Anonymous") and message (required, 280 chars max). POSTs to
/api/messages. Deliberately NO readOnlyHint — its absence signals a side
effect, which is what makes an agent confirm the exact wording with the
user first. After a successful write, dispatch a
'webmcp:messages-changed' CustomEvent on document so the visible guestbook
re-renders: the user is watching while the agent works. Return
{ ok, saved, totalMessages }.
Rules for all three:
- snake_case, verb-first names. Descriptions written for a model: say what
the tool returns and when to choose it over the others.
- inputSchema is REQUIRED and always a JSON Schema object. Give every
property its own description.
- Return compact, self-describing data. Throw errors whose text tells the
agent how to retry ("message is required and must be 1-280 characters"),
not "Error: 400".
- Validate and enforce limits in the Function too. A tool declaration is a
hint to a cooperative agent, never a security boundary.
- Feature-detect and keep the site fully usable when modelContext is absent.
- There is no unregisterTool(). To remove a tool, abort the AbortSignal you
passed at registration — that is how a single-page app exposes
route-specific tools: register on mount, abort on unmount.
- Do NOT use navigator.modelContext.provideContext(), clearContext(), or
unregisterTool(). They were removed.
STYLE — the source is the documentation.
Comment far more heavily than you normally would, explain WHY rather than
what, and write for a stranger opening the file for the first time. Every
tool declaration gets a comment block covering what each field buys you.
Mark the sections of webmcp.js with //#region name … //#endregion, using
exactly these names: detect, tool:get_profile, tool:search_links,
tool:leave_message, register.
THE PAGE — the site and its docs are one artifact.
index.html must contain, in this order: the live profile, links, and
guestbook (with a plain HTML form that posts to the same endpoint the write
tool uses); a status line that reads document.modelContext at load and
reports honestly whether tools registered and how many; an annotated
walkthrough whose code blocks are fetched from assets/webmcp.js at runtime
and sliced out by those region markers, so the documentation cannot drift
from what actually runs; and a file map explaining why each file exists.
Render every user-supplied string with textContent, never innerHTML. Expose
the tool array on window for DevTools poking.
DEPLOY, THEN PROVE IT.
Deploy to Netlify and give me the live URL. Tell me how to see the tools:
Chrome 149+, enable chrome://flags/#enable-webmcp-testing, relaunch, and
install the Model Context Tool Inspector extension. Then exercise the
deployed site yourself and report the result of each step, quoting any error
text verbatim:
1. List every registered tool with its name, description, and input schema.
2. Call get_profile and tell me in one sentence who the page belongs to.
3. Call search_links with "music", then with a query you expect to match
nothing, and show me what the empty result looks like.
4. Call leave_message, then re-read the guestbook to confirm the write
landed and give me the new total.
5. Call leave_message with an empty message on purpose and quote the error.
Use the tools only for those checks — no DOM reading, no simulated clicks —
and say so plainly if a tool is unavailable.