Before writing code, think about what the user actually wants and how to decompose it into MCP primitives.
Sign in to save downloads to your library and vote.
Preview
Design and Architecture
Before writing code, think about what the user actually wants and how to decompose it into MCP primitives.
Concepts
- Tool: A backend action the AI model can call. Takes input, returns data. Use
server.tool(). - Widget tool: A tool that returns visual UI. Same
server.tool()but with awidgetconfig and a React component inresources/. - Resource: Read-only data the client can fetch. Use
server.resource()orserver.resourceTemplate(). - Prompt: A reusable message template. Use
server.prompt().
Step 1: Identify What to Build
Extract the core actions from the user's request. Stick to what they asked -- don't invent extra features.
Examples:
| User says | Core actions |
|---|---|
| "weather app" | Get current weather, get forecast |
| "todo list" | Add todo, list todos, complete todo, delete todo |
| "recipe finder" | Search recipes, get recipe details |
| "translator" | Translate text, detect language |
| "stock tracker" | Get stock price, compare stocks |
| "quiz app" | Generate quiz, check answer |
Step 2: Does It Need a Widget?
For each action, decide if visual UI would meaningfully improve the experience.
YES → widget if:
- Browsing or comparing multiple items (search results, product cards)
- Visual data improves understanding (charts, maps, images, dashboards)
- Interactive selection is easier visually (seat picker, calendar, color picker)
NO → tool only if:
- Output is simple text (translation, calculation, status check)
- Input is naturally conversational (dates, amounts, descriptions)
- No visual element would meaningfully help
When in doubt, use a widget -- it makes the experience better.
Step 3: Design the API
Naming
Tools and widgets start with a verb: get-weather, search-recipes, add-todo, translate-text.
One tool = one focused capability
Don't create one massive tool that does everything. Break it into focused actions:
❌ manage-todos (too broad)
✅ add-todo, list-todos, complete-todo, delete-todo
One widget per flow
Different flows can have separate widgets. Don't split one flow into multiple widgets.
❌ search-recipes widget + view-recipe widget (same flow → merge)
✅ search-recipes widget (handles both list and detail views) + meal-planner widget (different flow)
Don't lazy-load
Tool calls are expensive. Return all needed data upfront.
❌ search-recipes widget + get-recipe-details tool (lazy-loading details)
✅ search-recipes widget returns full recipe data including details
Widget handles its own state
Selections, filters, and UI state live in the widget -- not as separate tools.
❌ select-recipe tool, set-filter tool (these are widget state)
✅ Widget manages selections and filters internally via useState or setState
exposeAsTool defaults to false
Widgets are not auto-registered as tools by default. When you create a custom tool with widget: { name: "my-widget" }, omitting exposeAsTool in the widget file is correct — the custom tool handles making the widget callable:
// resources/my-widget.tsx
export const widgetMetadata: WidgetMetadata = {
description: "...",
props: z.object({ ... }),
// exposeAsTool defaults to false — custom tool definition handles registration
};
Common App Patterns
Weather App
Widget tool: get-weather
- Input: { city }
- Widget: temperature, conditions, icon, humidity
- Output to model: text summary
Tool: get-forecast
- Input: { city, days }
- Returns: text or object with daily forecast
Todo List
Widget tool: list-todos
- Widget: interactive checklist with complete/delete buttons
- Widget calls add-todo, complete-todo, delete-todo via callTool
Tool: add-todo { title, priority? }
Tool: complete-todo { id }
Tool: delete-todo { id }
Recipe Finder
Widget tool: search-recipes
- Input: { query, cuisine? }
- Widget: recipe cards with images, ingredients, instructions
- Output to model: text summary of results
Resource: recipe://favorites (user's saved recipes)
Translator
Tool: translate-text
- Input: { text, targetLanguage, sourceLanguage? }
- Returns: text (translated result)
Tool: detect-language
- Input: { text }
- Returns: object({ language, confidence })
Stock Tracker
Widget tool: get-stock
- Input: { symbol }
- Widget: price chart, key metrics, news
- Output to model: price and change summary
Tool: compare-stocks
- Input: { symbols[] }
- Returns: object with comparison data
Mock Data Strategy
When the user doesn't specify a real API, use realistic mock data:
// Mock data - replace with real API
const mockWeather: Record<
string,
{ temp: number; conditions: string; humidity: number }
> = {
"New York": { temp: 22, conditions: "Partly Cloudy", humidity: 65 },
London: { temp: 15, conditions: "Overcast", humidity: 80 },
Tokyo: { temp: 28, conditions: "Sunny", humidity: 55 },
Paris: { temp: 18, conditions: "Light Rain", humidity: 75 },
};
function getWeather(city: string) {
// Add slight randomization to feel dynamic
const base = mockWeather[city] || {
temp: 20,
conditions: "Clear",
humidity: 60,
};
return {
...base,
temp: base.temp + Math.round((Math.random() - 0.5) * 4),
humidity: base.humidity + Math.round((Math.random() - 0.5) * 10),
};
}
Guidelines:
- Use real names (cities, recipes, products) -- not "Example 1"
- Add slight randomization so it feels dynamic
- Structure like a real API would return
- Comment with
// Mock data - replace with real API
Iterative Development
When the user asks to modify or extend existing code:
- Read the current
index.tsto see what exists - Preserve all existing tools, resources, and widgets
- Add new functionality alongside existing code
- Update existing widget files rather than creating duplicates
Ingestion metadata
- Source catalog
- awesome-llm-apps
- Repository
- Shubhamsaboo/awesome-llm-apps · main
- File path
- generative_ui_agents/ai-mcp-app-builder/apps/mcp-use-server/.agent/skills/mcp-builder/references/design-and-architecture.md
- Last refreshed
- 10/24/2026, 3:00:14 AM (5h ago)
- Refresh schedule
- Daily · 03:00 UTC
- Dedupe status
- Unique · deduped by (source, url)