Documentation Index Fetch the complete documentation index at: https://docs.fastapps.org/llms.txt
Use this file to discover all available pages before exploring further.
A tool is a Python class that:
Lives in server/tools/<widget>_tool.py
Extends BaseWidget
Defines inputs with Pydantic
Implements widget logic in execute()
Core Concepts
BaseWidget is the abstract base class that all FastApps widgets must inherit from. It handles all the MCP (Model Context Protocol) wiring and widget lifecycle management automatically.
Required Class Attributes
Attribute Type Description Example identifierstrUnique widget identifier. Must match the widget folder name in widgets/. Used as the resource URI identifier "greeting" for widgets/greeting/titlestrHuman-readable tool name displayed in ChatGPT interface. Shown when the model considers calling this tool "Show Greeting Widget"input_schemaType[BaseModel]Pydantic model defining the tool’s input parameters. ChatGPT uses this JSON schema to understand when and how to call your tool GreetingInputinvokingstrShort, localized status message shown to users while the tool is being executed. Maps to openai/toolInvocation/invoking "Preparing your greeting…"invokedstrShort, localized status message shown to users after the tool completes. Maps to openai/toolInvocation/invoked "Greeting ready!"
Optional Class Attributes
Attribute Type Description Example descriptionstrOptional tool description. Helps the model understand when to use this tool "Display a personalized greeting widget"widget_accessibleboolWhether the widget can initiate tool calls from its React component True for interactive widgets
from fastapps import BaseWidget
from pydantic import BaseModel
class GreetingInput ( BaseModel ):
name: str
message: str
class GreetingWidget ( BaseWidget ):
identifier = "greeting"
title = "Show Greeting Widget"
input_schema = GreetingInput
invoking = "Preparing your greeting…"
invoked = "Greeting ready!"
def execute ( self , inputs : GreetingInput, ctx ):
return {
"name" : inputs.name,
"message" : inputs.message,
"timestamp" : datetime.now().isoformat()
}
Common Patterns
Simple Data Display
class WeatherWidget ( BaseWidget ):
identifier = "weather"
title = "Show Weather Forecast"
input_schema = WeatherInput
invoking = "Fetching weather data…"
invoked = "Weather forecast ready!"
def execute ( self , inputs : WeatherInput, ctx ):
# Your business logic here
forecast = get_weather_forecast(inputs.city)
return {
"city" : inputs.city,
"temperature" : forecast.temperature,
"description" : forecast.description,
"humidity" : forecast.humidity
}
class SurveyInput ( BaseModel ):
questions: List[ str ]
class SurveyWidget ( BaseWidget ):
identifier = "survey"
title = "Create Survey Widget"
input_schema = SurveyInput
invoking = "Setting up your survey…"
invoked = "Survey ready for responses!"
def execute ( self , inputs : SurveyInput, ctx ):
return {
"questions" : inputs.questions,
"survey_id" : generate_survey_id(),
"created_at" : datetime.now().isoformat()
}
Conditional Logic
class ConditionalWidget ( BaseWidget ):
identifier = "conditional"
title = "Show Conditional Content"
input_schema = ConditionalInput
invoking = "Processing your request…"
invoked = "Content ready!"
def execute ( self , inputs : ConditionalInput, ctx ):
if inputs.user_type == "admin" :
return {
"content" : "Admin dashboard" ,
"permissions" : [ "read" , "write" , "delete" ],
"admin_panel" : True
}
else :
return {
"content" : "User dashboard" ,
"permissions" : [ "read" ],
"admin_panel" : False
}
Use Pydantic models to define and validate inputs:
from pydantic import BaseModel, Field, validator
from typing import List, Optional
class ProductSearchInput ( BaseModel ):
query: str = Field( ... , min_length = 1 , max_length = 100 )
category: Optional[ str ] = None
price_range: Optional[ tuple ] = Field( None , description = "Min and max price" )
limit: int = Field( default = 10 , ge = 1 , le = 100 )
@validator ( 'query' )
def validate_query ( cls , v ):
if len (v.strip()) == 0 :
raise ValueError ( 'Query cannot be empty' )
return v.strip()
@validator ( 'price_range' )
def validate_price_range ( cls , v ):
if v and v[ 0 ] > v[ 1 ]:
raise ValueError ( 'Min price must be less than max price' )
return v
Error Handling
Handle errors gracefully and provide meaningful feedback:
class RobustWidget ( BaseWidget ):
identifier = "robust"
title = "Robust Widget Example"
input_schema = RobustInput
invoking = "Processing…"
invoked = "Done!"
def execute ( self , inputs : RobustInput, ctx ):
try :
# Your business logic
result = risky_operation(inputs.data)
return { "status" : "success" , "data" : result}
except ValidationError as e:
ctx.logger.warning( f "Validation error: { e } " )
return {
"status" : "error" ,
"message" : "Invalid input data" ,
"details" : str (e)
}
except Exception as e:
ctx.logger.exception( f "Unexpected error: { e } " )
return {
"status" : "error" ,
"message" : "Something went wrong" ,
"fallback_data" : get_fallback_data()
}
Next Steps
MCP Integration Integrate external MCP servers using Metorial
API Integration Connect to external APIs