Skill Store 33

Tool Metadata Design

This skill should be used when designing the tool surface behind an agent UI — action-oriented na…

by thelobbi · MIT

The skill

This skill should be used when designing the tool surface behind an agent UI — action-oriented naming, descriptions that drive correct model selection, input and output schemas, structuredContent vs content vs _meta, safety annotations, tool visibility, and the discovery metadata that decides whether the app ever gets invoked.

Designing the tool surface

A widget nobody triggers is worth nothing. Discovery is a metadata problem, and metadata is where most agent UIs quietly fail — the UI is beautiful and the model never calls it.

Naming

Action-oriented verb_noun, lowercase, underscore-separated.

✅ search_listings    render_listings_widget    complete_checkout
❌ listings           handler                   doStuff             get_data

The name is the model's strongest signal. render_listings_widget announces what it does and that it produces UI; listings announces nothing.

Consistent prefixes across a server help the model reason about a family: search_*, get_*, render_*, create_*, update_*.

Descriptions

The description is a prompt. Write it for the model, not for a docs page.

Cover four things:

  1. What it does, concretely, including the return shape.
  2. When to call it — the user situations that should trigger it.
  3. What it depends on — required prior calls, spelled out.
  4. When not to call it — the near-miss cases it should not absorb.
description:
  "Render the listings widget from prepared listing data. " +
  "Call search_listings first and pass the filtered listing IDs here. " +
  "Use when the user should visually compare properties. " +
  "Do not use for a single property — use render_listing_detail instead.",

Dependency statements are load-bearing in the decoupled pattern. Without "call roll_dice first", the model will invent arguments for the render tool.

Schemas

Input

  • Every parameter needs a description; the model reads them.
  • Constrain: enums over free strings, min/max on numbers, maxLength on strings, maxItems on arrays.
  • Required means required. Optional parameters with sensible defaults reduce malformed calls.
  • Keep the surface small. A 15-parameter tool gets called wrong.
inputSchema: {
  city: z.string().min(1).max(120).describe("City name, e.g. 'Portland, OR'"),
  days: z.number().int().min(1).max(14).default(7).describe("Forecast length in days"),
  units: z.enum(["metric", "imperial"]).default("metric"),
}

Output

Declare outputSchema whenever you return structuredContent. It documents the contract for the model, for the View, and for your own tests.

The three result channels

return {
  structuredContent: { city, days },                       // View renders this; model can inspect it
  content: [{ type: "text", text: `7-day forecast for ${city}.` }],  // model reads; text-only hosts read
  _meta: { /* client-specific, hidden from the model */ },
};
Channel Who sees it Put here
structuredContent View + model The data. Concise, typed, matching outputSchema.
content Model + text-only hosts A short natural-language summary.
_meta Client only Rendering hints, session tokens, anything the model must not reason over.

content is not optional. It is what makes the tool work in a host that does not render UI, and what lets the model answer a follow-up without re-calling. A tool whose only output is pixels is broken.

Keep structuredContent lean. It enters the model's context on every call — returning 500 rows to render 8 wastes budget and degrades reasoning.

Visibility

Setting Effect Use for
["model", "app"] (default) Model and View may call Normal tools
["app"] View only; hidden from the model Pagination, polling, cart mutations, chunked transfer
["model"] Model only Sensitive operations the UI must not initiate

App-only tools are the main lever for keeping conversation context clean. A View that calls a model-visible tool on every interaction floods the transcript.

visibility is context hygiene, not access control. Authorize server-side regardless.

ChatGPT's _meta["openai/widgetAccessible"] is the older equivalent; M365 Copilot honors openai/visibility but not widgetAccessible. Prefer _meta.ui.visibility.

Safety annotations

annotations: {
  readOnlyHint: true,       // widely honored
  destructiveHint: false,   // ignored by M365 Copilot
  idempotentHint: true,     // ignored by M365 Copilot
  openWorldHint: false,     // ignored by M365 Copilot
}

Set them all — they are cheap and some hosts use them. But never rely on destructiveHint to produce a confirmation prompt. Build confirmation into your own UI for anything irreversible.

Invocation status text

_meta: {
  "openai/toolInvocation/invoking": "Searching listings…",
  "openai/toolInvocation/invoked": "Found 12 listings.",
}

ChatGPT-only; unsupported in M365 Copilot. Put a real status in the widget itself so the experience holds up everywhere.

Discovery

Whether the model reaches for your tool at all comes down to:

  • Name — the primary signal.
  • Description — trigger situations in the user's vocabulary, not yours.
  • Parameter descriptions — they disambiguate between similar tools.
  • Server/app description — the umbrella context.

Test discovery explicitly: write the five phrasings a real user would use and verify each selects the right tool. Wrong selection is a metadata bug, not a model failure — fix the description.

Tool count

Under ~10 well-named tools, models select reliably. Past ~20, selection degrades noticeably.

  • Prefer one tool with an enum parameter over five near-identical tools.
  • Hide mechanical helpers with visibility: ["app"] — they cost nothing in the model's list.
  • Split a server that has genuinely outgrown one domain.

Anti-patterns

Anti-pattern Why it fails
resourceUri on every tool Widget remounts on every call; state lost
No content, only structuredContent Breaks text-only hosts and follow-ups
Description written for humans Model cannot tell when to use it
Unconstrained string parameters Malformed calls, injection surface
Returning the whole dataset Burns context, degrades reasoning
Business logic in the render handler Untestable; re-renders do real work
Relying on destructiveHint for confirmation Ignored by major hosts

Related

  • openai-apps-sdk — the decoupled data/render pattern.
  • host-capability-matrix — which _meta fields each host honors.
  • ui-security-sandbox — why server-side authorization is non-negotiable.

Use this skill

Connect your agent to the Skill Store MCP server and ask for it by ref: thelobbi/tool-metadata-design-15400607909.

Download SKILL.md · Markdown version

Details

Publisher
thelobbi
Category
AI & Agents › Tool & MCP integration
License
MIT
Updated
2026-10-08

References

More from thelobbi