Tools
What the agent can DO — standard WebMCP registerTool with safety annotations
A tool is one operation of your app the agent is allowed to run. Registration is pure standard WebMCP — there is no Napster-specific wrapper, and any WebMCP-compatible agent can call the result:
document.modelContext.registerTool({
name: 'checkout.placeOrder',
description: 'Submit the cart for purchase.',
inputSchema: {
type: 'object',
properties: {
paymentMethodId: { type: 'string' },
addressId: { type: 'string' },
},
required: ['paymentMethodId', 'addressId'],
},
annotations: {
readOnlyHint: false,
destructiveHint: true, // ⇒ the agent confirms with the user before calling
idempotentHint: true, // safe to retry
},
async execute({ paymentMethodId, addressId }) {
const order = await placeOrder(paymentMethodId, addressId); // your app's own code
return { content: [{ type: 'text', text: `Order ${order.id} placed` }] };
},
});The execute calls the same functions your buttons and forms already call — the toolkit doesn't replace them, it exposes the ones you choose. The return value uses the standard MCP result shape ({ content: [{ type: 'text', text }] }).
Safety annotations
Safety rides on the standard MCP annotation hints — portable to any consumer, no proprietary tiers:
| Hint | Meaning |
|---|---|
readOnlyHint | The tool only reads state — the agent may call it freely. |
destructiveHint | Convention: confirm with the user before calling. Use for anything final — payments, sends, deletions. |
idempotentHint | Safe to retry with the same arguments. |
untrustedContentHint | The output may carry untrusted or injected content. |
openWorldHint | The tool reaches beyond the site itself — external systems, the open web. Most website tools are closed-world and leave this unset. |
A tool with no annotations is treated as a reversible write: the agent announces what it's doing and runs it. The rule of thumb: pure read → readOnlyHint: true; reversible write → neither hint; final/irreversible → destructiveHint: true.
Writing tools agents actually use well
- Names are
namespace.verb—products.search,cart.add— snake or camel within segments. - Descriptions are the agent's manual. Say what the tool does, when to reach for it, and what it returns — the agent decides relevance from this text alone.
- Schemas are contracts. Mark required fields, describe each property; the agent fills arguments from conversation, and a precise schema is the difference between clean calls and garbage.
- Expose operations, not endpoints. One user-meaningful action per tool ("book a table"), not a thin wrapper per API route.
Reading the surface back (consumer side)
Only relevant if you build something that consumes tools — an inspector, a custom bridge. Two wire-contract details: getTools() returns each inputSchema as a JSON string (parse before use — that's Chromium's native contract, matched for interoperability), and executeTool(tool, argsJson) resolves with a JSON string of the result envelope. Always pass the handle getTools() returned, never a hand-built object, and re-read the list on the toolchange event.
To unregister, pass an AbortSignal when registering — registerTool(descriptor, { signal: controller.signal }) — and call controller.abort() when the tool should go away. registerTool itself returns nothing.