Hands on
A tool that changes things
Your application does more than display things, and you want an agent to be able to change them — with the person’s say-so where it matters. This example has a tool that reads, a tool that adds, and a tool that deletes.
Try it
In XataWorks, open:
https://xataworks.ca/developers/examples/notes/
Then ask: “Call the delete_note tool on the open page with the title Old draft. Do not read the page first.”
The notes live in the page’s memory. Reload it and the three notes are back, so nothing you or an agent does here is lost for anyone else.
The application
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notes</title>
<meta name="description" content="A WebMCP example: read, change and delete.">
<link rel="stylesheet" href="../example.css">
</head>
<body>
<header>
<h1>Notes</h1>
<p>An example application for the XataWorks developer guide.
Reloading the page restores the three notes.
<a href="../../a-tool-that-changes-things.html">How it works</a></p>
</header>
<main>
<ul class="notes" id="notes"></ul>
<p class="out" id="out">Nothing has changed yet.</p>
</main>
<script>
const notes = [
{ title: "Shopping list", body: "Coffee, oats, lemons." },
{ title: "Old draft", body: "An outline nobody needs any more." },
{ title: "Meeting agenda", body: "Budget, hiring, the launch." }
];
function render(message) {
const list = document.getElementById("notes");
list.replaceChildren(...notes.map((note) => {
const item = document.createElement("li");
const title = document.createElement("strong");
title.textContent = note.title;
item.append(title, note.body);
return item;
}));
if (message) document.getElementById("out").textContent = message;
}
function text(value) {
return { content: [{ type: "text", text: value }] };
}
function register(registry) {
registry.registerTool({
name: "list_notes",
description: "List every note, with its title and body.",
inputSchema: { type: "object", properties: {} },
annotations: { readOnlyHint: true, openWorldHint: false },
execute: async () => text(JSON.stringify(notes))
});
registry.registerTool({
name: "add_note",
description: "Add a note with a title and a body.",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "A short title" },
body: { type: "string", description: "The note itself" }
},
required: ["title", "body"]
},
annotations: { readOnlyHint: false, consequentialHint: false,
openWorldHint: false },
execute: async ({ title, body }) => {
notes.push({ title, body });
render("Added “" + title + "”.");
return text("Added the note “" + title + "”.");
}
});
registry.registerTool({
name: "delete_note",
description: "Delete the note with this exact title. " +
"It cannot be undone.",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "The note's title" }
},
required: ["title"]
},
annotations: { consequentialHint: true, destructiveHint: true,
openWorldHint: false },
execute: async ({ title }) => {
const index = notes.findIndex((note) => note.title === title);
if (index < 0) {
const titles = notes.map((note) => note.title).join(", ");
return {
isError: true,
content: [{ type: "text",
text: "No note is titled “" + title + "”. " +
"The notes are: " + titles + "." }]
};
}
notes.splice(index, 1);
render("Deleted “" + title + "”.");
return text("Deleted the note “" + title + "”.");
}
});
registry.registerTool({
name: "notes_house_style",
description: "How notes in this app should be titled " +
"and written. Read this before adding a note.",
inputSchema: { type: "object", properties: {} },
annotations: { readOnlyHint: true, skill: true,
openWorldHint: false },
execute: async () => text(
"Title a note with two to four words in sentence case. " +
"Write the body as one line. " +
"Never delete a note without saying which one first.")
});
}
render();
const registry = document.modelContext || navigator.modelContext;
if (registry) register(registry);
</script>
</body>
</html>
Three tools, three classes
XataWorks puts every page tool in one of three classes, from the hints the page declares, and the class decides what the person is asked:
list_notesdeclaresreadOnlyHint: true: read-only. It looks things up and changes nothing.add_notedeclaresconsequentialHint: false: mutating — it changes something, and the change is easy to undo.delete_notedeclaresconsequentialHint: trueanddestructiveHint: true: consequential. It cannot be undone, and its description says so.
The fourth tool, notes_house_style, is a skill: it is
explained in publishing a skill.
How the hints combine, and what happens when a page declares
nothing, is in safety classes.
What the person is asked
First, the site-permission question you met in Hello, WebMCP. Answer it with Allow non-consequential read/write tools on this website, and listing and adding run without another question. Deleting is consequential, which is above what you allowed, so it is asked about on its own — every time, unless the person says otherwise:
Allow this call only deletes this note and records nothing. The arrow offers wider answers; they are all in answering a permission request.
delete_note with its approving and refusing answers opened in turn; and Allow this call only. No sound.Errors the agent can use
When there is no note with the title asked for,
delete_note does not throw. It returns a result that
says what went wrong and lists the titles that do exist, so the
agent can correct itself or ask the person — rather than
concluding the deletion happened, or giving up.
XataWorks hands the agent whatever execute returns, as
it is. The isError flag follows the draft
standard’s result shape and is there for the agent to read;
the words are what it acts on.
What to take into your own application
- Split operations by consequence. A tool that both edits and deletes can only be classed as the more dangerous of the two, and every edit gets the deletion’s question.
- Say in the description when something cannot be undone. The agent reads it, and so does the person, in the tools palette.
- Return errors that name the way forward.