← Blog
April 21, 2026 · 8 min read · Candas Özgenç

How we made every WidgetCraft widget addressable by AI agents

Eight intent-rich tools, not one generic create_widget, and why that matters when the user you're serving is an LLM.

We shipped the WidgetCraft MCP server this week. Eight tools, one per widget family — create_countdown, create_focus_timer, create_progress_bar, create_league_standings, create_github_repo_stats, create_ufc_card, create_youtube_channel_stats, create_homebrew_tracker. A user asks Claude for a countdown; Claude picks the tool; WidgetCraft returns a shareable URL that embeds in Notion, Obsidian, OBS, anywhere with an iframe.

The technical delivery was unremarkable — an @modelcontextprotocol/sdk wrapper over a Supabase insert. What was interesting was the *surface design*: the decision to ship eight verb-first, domain-specific tools instead of one flexible create_widget(type, config). That choice cost us code and saved us something more valuable.

The generic-tool trap

My first pass at this was a single create_widget taking { type, label, date, ... }. It worked in tests — Claude could call it and get a URL back. But in actual use, Claude kept asking follow-ups: "What type of widget do you want?" "A countdown? What's the label?" "What date format?"

The problem wasn't that Claude was confused about widgets. The problem was that I'd put the disambiguation burden on the agent. When the *tool* is "create any widget", the agent has to interview the user before calling it. Every interview round is a chance for the user to lose patience or give ambiguous input.

One tool per intent

The fix was splitting by intent, not by data model. create_countdown and create_homebrew_tracker both write to the same countdown widget with the same { label, date } config. They share internals. But:

  • create_countdown triggers on *"a countdown to X"*, *"how many days until Y"*.
  • create_homebrew_tracker triggers on *"my IPA fermentation, 14 days"*, *"track my kombucha"*.

Same underlying widget, different triggers. Claude picks the brewing tool when the brewing words show up, formats the label as "Belgian Tripel · ready to bottle" instead of asking the user what label they want, and computes the date as today + 14 days instead of asking for a specific date.

The generic tool would have gotten there eventually, with three more turns of conversation. The split tool gets there on turn one.

Tool descriptions as a paid ad

Here's the shift that took me longer to absorb: every tool description is a landing page for a search query. The query is the user's prompt. The conversion is Claude picking your tool. The copy matters.

Our descriptions now follow a specific structure:

1. Definition (one sentence): what the widget is 2. Use case (one sentence): when to call it, with example prompts 3. Return value (one sentence): what the user receives 4. Anti-patterns (one sentence): what *not* to call it for

Example — create_progress_bar:

Creates an embeddable time-progress widget showing the percentage completed for the year, quarter, month, week, and day. Use when the user wants a persistent visual reminder of time passing against their goals — particularly for year-end planning, OKR tracking, or dashboards that benchmark goals against the calendar. Returns a shareable widgetcraft.ai URL. No input required beyond optional visual theme. Do NOT use for tracking progress toward a specific dated event (use create_countdown) or for task-completion percentages (out of scope for this widget).

That "Do NOT use" clause is the one I was most reluctant to write and the one that paid off most. It explicitly rules out near-matches that would otherwise consume Claude's attention budget.

Zero setup or users won't try it

The other design call: the published @widgetcraft/mcp package has zero required environment variables. No Supabase credentials. No API keys. Users install and it works.

This is non-obvious for a product that writes to a database. The usual shape is "ship an npm package, users plug in their own database creds". That shape kills adoption. Anyone who has to provision Supabase just to try your MCP server isn't going to finish.

Our solution: the stdio CLI is a thin proxy. It forwards every tool call to our hosted endpoint at widgetcraft.ai/api/mcp, which holds the Supabase service-role key server-side in our Vercel env. The user's machine never sees a credential.

Self-hosters who want to run their own WidgetCraft can opt into DIRECT mode by setting SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY — the CLI then writes straight to their database. Same code path, different configuration.

The bigger bet

MCP servers are going to be the API-marketing of the next era. SEO taught a generation of products that the searchable surface — keywords, titles, descriptions, meta — drives the funnel. AI-agent surfaces are the same game: the catalog you expose determines who finds you.

If your tool surface looks like a cable company's IVR menu ("for billing, press 1"), agents route away from you. If it reads like a curated help center, agents route toward you.

I'll trade extra code for that.


*Source:* github.com/JidAiLabs/widgetcraft-mcp · *Install:* npm i -g @widgetcraft/mcp