# What is Tambo?
URL: /
import LearnMore from "@/components/learn-more";
Tambo is a **React package** for building AI-powered applications with generative UI, where users interact through natural language.
Use Tambo to build AI chats, copilots, or completely custom AI interactions.
## Core Elements
### React package `@tambo-ai/react`
The react package provides hooks that help you build the list of components and tools Tambo can use, send user messages to the Tambo API, and interact with responses and message threads.
Wrap your app with the `` and use the hooks within your components to send messages and show responses.
```tsx
import { TamboProvider } from "@tambo-ai/react";
export function Home() {
return (
);
}
```
### Tambo API
The API processes user messages, interacts with LLMs to generate AI responses, manages conversation threads, orchestrates tool calls, and handles user authentication.
import { MessageCircleMore } from "lucide-react";
### UI Component Library
The component library provides pre-built React components with Tambo integrated for common AI application interfaces like chat, AI-powered forms, and visualizations. Use these to speed up your AI application building.
import { Box } from "lucide-react";
## Key Features
* **Generative UI Components** - Render dynamic React components in response to user messages
* **Streaming Support** - Real-time streaming for all AI-generated content with UX-enhancing hooks
* **Message History** - Automatic conversation storage and management
* **State Management** - AI-integrated state hooks to persist user input and component state
* **Suggested Actions** - Guide users through your app's functionality
* **Tool calling loop** - Tambo will automatically orchestrate tool calls during response generation
* **Component Library** - Ready-to-use components integrated with Tambo installable via CLI
* **Model Configuration** - Fine-tune AI behavior with custom parameters and reasoning capabilities
# React Hooks
URL: /api-reference/react-hooks
The `@tambo-ai/react` npm package provides hooks that expose state values and functions to make building AI apps with Tambo simple.
Here you'll find a description of each state value and function, organized by hook.
### useTamboRegistry
This hook provides helpers for component and tool registration.
#### registerComponent
`const { registerComponent } = useTamboRegistry()`
This function is used to register components with Tambo, allowing them to be potentially used in Tambo's responses.
#### registerTool
`const { registerTool } = useTamboRegistry()`
This function is used to register tools with Tambo.
#### registerTools
`const { registerTools } = useTamboRegistry()`
This function allows registering multiple tools at once.
#### addToolAssociation
`const { addToolAssociation } = useTamboRegistry()`
This function creates an association between components and tools.
#### componentList
`const { componentList } = useTamboRegistry()`
This value provides access to the list of registered components.
#### toolRegistry
`const { toolRegistry } = useTamboRegistry()`
This value provides access to the registry of all registered tools.
#### componentToolAssociations
`const { componentToolAssociations } = useTamboRegistry()`
This value provides access to the associations between components and tools.
### useTamboThread
This hook provides access to the current thread and functions for managing thread interactions.
#### thread
`const { thread } = useTamboThread()`
The current thread object containing messages and metadata. Messages can be accessed via `thread.messages`. This value is kept up-to-date automatically by Tambo when messages are sent or received.
#### sendThreadMessage
`const { sendThreadMessage } = useTamboThread()`
Function to send a user message to Tambo and receive a response. A call to this function will update the provided `thread` state value.
To have the response streamed, use `sendThreadMessage(message, {streamResponse: true})`.
#### generationStage
`const { generationStage } = useTamboThread()`
Current stage of message generation. Possible values are:
* `IDLE`: The thread is not currently generating any response (Initial stage)
* `CHOOSING_COMPONENT`: Tambo is determining which component to use for the response
* `FETCHING_CONTEXT`: Gathering necessary context for the response by calling a registered tool
* `HYDRATING_COMPONENT`: Generating the props for a chosen component
* `STREAMING_RESPONSE`: Actively streaming the response
* `COMPLETE`: Generation process has finished successfully
* `ERROR`: An error occurred during the generation process
#### inputValue
`const { inputValue } = useTamboThread()`
Current value of the thread input field.
#### generationStatusMessage
`const { generationStatusMessage } = useTamboThread()`
Status message describing the current generation state, as generated by Tambo.
#### isIdle
`const { isIdle } = useTamboThread()`
Boolean indicating whether the thread is in an idle state (`generationStage` is `IDLE`, `COMPLETE`, or `ERROR`).
#### switchCurrentThread
`const { switchCurrentThread } = useTamboThread()`
Function to change the active thread by id. This will update the `thread` state value to the fetched thread.
#### addThreadMessage
`const { addThreadMessage } = useTamboThread()`
Function to append a new message to the thread.
#### updateThreadMessage
`const { updateThreadMessage } = useTamboThread()`
Function to modify an existing thread message.
#### setLastThreadStatus
`const { setLastThreadStatus } = useTamboThread()`
Function to update the status of the most recent thread message.
#### setInputValue
`const { setInputValue } = useTamboThread()`
Function to update the input field value.
### useTamboThreadList
This hook provides access to the list of all threads for a project and their loading states.
#### data
`const { data } = useTamboThreadList()`
Array of threads or null if not yet loaded.
#### isPending
`const { isPending } = useTamboThreadList()`
Boolean indicating if threads are currently being fetched.
#### isSuccess
`const { isSuccess } = useTamboThreadList()`
Boolean indicating if threads were successfully fetched.
#### isError
`const { isError } = useTamboThreadList()`
Boolean indicating if an error occurred while fetching threads.
#### error
`const { error } = useTamboThreadList()`
Error object containing details if the fetch failed.
### useTamboThreadInput
This hook provides utilities for building an input interface that sends messages to Tambo.
#### value
`const { value } = useTamboThreadInput()`
Current value of the input field.
#### setValue
`const { setValue } = useTamboThreadInput()`
Function to update the input field value.
#### submit
`const { submit } = useTamboThreadInput()`
Function to submit the current input value, with optional context and streaming configuration.
#### isPending
`const { isPending } = useTamboThreadInput()`
Boolean indicating if a submission is in progress.
#### isSuccess
`const { isSuccess } = useTamboThreadInput()`
Boolean indicating if the last submission was successful.
#### isError
`const { isError } = useTamboThreadInput()`
Boolean indicating if the last submission failed.
#### error
`const { error } = useTamboThreadInput()`
Error object containing details if the submission failed.
### useTamboSuggestions
This hook provides utilities for managing AI-generated message suggestions.
#### suggestions
`const { suggestions } = useTamboSuggestions()`
List of available AI-generated suggestions for the next message.
#### selectedSuggestionId
`const { selectedSuggestionId } = useTamboSuggestions()`
ID of the currently selected suggestion.
#### accept
`const { accept } = useTamboSuggestions()`
Function to accept and apply a suggestion, with an option for automatic submission.
#### acceptResult
`const { acceptResult } = useTamboSuggestions()`
Detailed mutation result for accepting a suggestion.
#### generateResult
`const { generateResult } = useTamboSuggestions()`
Detailed mutation result for generating new suggestions.
#### isPending
`const { isPending } = useTamboSuggestions()`
Boolean indicating if a suggestion operation is in progress.
#### isSuccess
`const { isSuccess } = useTamboSuggestions()`
Boolean indicating if the last operation was successful.
#### isError
`const { isError } = useTamboSuggestions()`
Boolean indicating if the last operation resulted in an error.
#### error
`const { error } = useTamboSuggestions()`
Error object containing details if the operation failed.
### useTamboClient
This hook provides direct access to the Tambo client instance.
#### client
`const { client } = useTamboClient()`
The Tambo client instance for direct API access.
### useTamboComponentState
This hook is similar to React's `useState`, but allows Tambo to see the state values to help respond to later messages.
`const [myValue, setMyValue] = useTamboComponentState()`
#### value and setValue
`const { value } = useTamboComponentState()`
Current state value stored in the thread message for the given key.
#### setValue
`const { setValue } = useTamboComponentState()`
Function to update the state value, synchronizing both local state and server state.
### useTamboContextHelpers
This hook provides dynamic control over context helpers.
#### getContextHelpers
`const { getContextHelpers } = useTamboContextHelpers()`
Returns the current map of registered helper functions keyed by name.
#### addContextHelper
`const { addContextHelper } = useTamboContextHelpers()`
Adds or replaces a helper at the given key.
#### removeContextHelper
`const { removeContextHelper } = useTamboContextHelpers()`
Removes a helper by key so it is no longer included in outgoing messages.
### useTamboContextAttachment
This hook provides utilities for managing context attachments and custom suggestions.
#### attachments
`const { attachments } = useTamboContextAttachment()`
Array of active context attachments currently displayed as badges above the message input.
#### addContextAttachment
`const { addContextAttachment } = useTamboContextAttachment()`
Function to add a new context attachment. Accepts an object with `name`, optional `icon`, and optional `metadata`. The `id` is automatically generated.
#### removeContextAttachment
`const { removeContextAttachment } = useTamboContextAttachment()`
Function to remove a specific context attachment by its ID.
#### clearContextAttachments
`const { clearContextAttachments } = useTamboContextAttachment()`
Function to remove all active context attachments at once.
#### customSuggestions
`const { customSuggestions } = useTamboContextAttachment()`
Current custom suggestions that override auto-generated suggestions. Returns `null` if using auto-generated suggestions.
#### setCustomSuggestions
`const { setCustomSuggestions } = useTamboContextAttachment()`
Function to set custom suggestions or clear them by passing `null`.
### useCurrentInteractablesSnapshot
`const snapshot = useCurrentInteractablesSnapshot()`
Returns a cloned snapshot of the current interactable components.
# TypeScript Types
URL: /api-reference/typescript-types
The `@tambo-ai/react` package exports TypeScript types and interfaces to help you build type-safe AI applications.
## TamboTool
The `TamboTool` interface defines the structure for registering tools with Tambo.
```typescript
interface TamboTool {
name: string;
description: string;
tool: (...args: any[]) => any;
toolSchema: z.ZodFunction | JSONSchemaLite;
transformToContent?: (
result: any,
) => Promise | ChatCompletionContentPart[];
}
```
### Properties
#### name
The unique identifier for the tool. This is how Tambo references the tool internally.
```typescript
name: string;
```
#### description
A clear description of what the tool does. This helps the AI understand when to use the tool.
```typescript
description: string;
```
#### tool
The function that implements the tool's logic. Can be synchronous or asynchronous.
```typescript
tool: (...args: any[]) => any;
```
#### toolSchema
A Zod function schema that defines the tool's parameters and return type. Can also be a JSON Schema object.
```typescript
toolSchema: z.ZodFunction | JSONSchemaLite;
```
**Example:**
```typescript
toolSchema: z.function()
.args(z.string().describe("The city name"))
.returns(z.object({ temperature: z.number(), condition: z.string() }));
```
#### transformToContent (optional)
A function that transforms the tool's return value into an array of content parts. Use this when your tool needs to return rich content like images or audio.
```typescript
transformToContent?: (result: any) => Promise | ChatCompletionContentPart[];
```
By default, tool responses are stringified and wrapped in a text content part. The `transformToContent` function allows you to return rich content including images, audio, or mixed media.
**Example:**
```typescript
transformToContent: (result) => [
{ type: "text", text: result.description },
{ type: "image_url", image_url: { url: result.imageUrl } },
];
```
[Learn more about returning rich content from tools](/concepts/tools/adding-tools#returning-rich-content).
## TamboComponent
The `TamboComponent` interface defines the structure for registering React components with Tambo.
```typescript
interface TamboComponent {
name: string;
description: string;
component: ComponentType;
propsSchema?: z.ZodTypeAny | JSONSchema7;
propsDefinition?: any;
loadingComponent?: ComponentType;
associatedTools?: TamboTool[];
}
```
### Properties
#### name
The unique identifier for the component.
```typescript
name: string;
```
#### description
A clear description of what the component displays or does. This helps the AI understand when to use the component.
```typescript
description: string;
```
#### component
The React component to render.
```typescript
component: ComponentType;
```
#### propsSchema (recommended)
A Zod schema that defines the component's props.
```typescript
propsSchema?: z.ZodTypeAny | JSONSchema7;
```
#### propsDefinition (deprecated)
A JSON object defining the component's props. Use `propsSchema` instead.
```typescript
propsDefinition?: any;
```
#### loadingComponent (optional)
A component to display while the main component is loading.
```typescript
loadingComponent?: ComponentType;
```
#### associatedTools (optional)
An array of tools that are associated with this component.
```typescript
associatedTools?: TamboTool[];
```
## ChatCompletionContentPart
Content parts that can be sent to or received from the AI.
```typescript
interface ChatCompletionContentPart {
type: "text" | "image_url" | "input_audio";
text?: string;
image_url?: { url: string; detail?: "auto" | "high" | "low" };
input_audio?: { data: string; format: "wav" | "mp3" };
}
```
This type is used in the `transformToContent` function to define rich content responses.
## TamboThreadMessage
A message in a Tambo thread.
```typescript
interface TamboThreadMessage {
id: string;
role: "user" | "assistant" | "system" | "tool";
content: ChatCompletionContentPart[];
createdAt: string;
renderedComponent?: React.ReactNode;
component?: {
componentName: string;
props: any;
};
actionType?: string;
error?: string;
}
```
## TamboThread
A Tambo conversation thread.
```typescript
interface TamboThread {
id: string;
messages: TamboThreadMessage[];
contextKey?: string;
createdAt: string;
updatedAt: string;
}
```
## GenerationStage
The current stage of AI response generation.
```typescript
enum GenerationStage {
IDLE = "IDLE",
CHOOSING_COMPONENT = "CHOOSING_COMPONENT",
FETCHING_CONTEXT = "FETCHING_CONTEXT",
HYDRATING_COMPONENT = "HYDRATING_COMPONENT",
STREAMING_RESPONSE = "STREAMING_RESPONSE",
COMPLETE = "COMPLETE",
ERROR = "ERROR",
}
```
## ContextAttachment
Represents a context attachment that appears as a badge above the message input.
```typescript
interface ContextAttachment {
id: string;
name: string;
icon?: React.ReactNode;
metadata?: Record;
}
```
### Properties
#### id
Unique identifier for the attachment. Auto-generated when adding a context attachment.
```typescript
id: string;
```
#### name
Display name shown in the badge.
```typescript
name: string;
```
#### icon
Optional icon to display in the badge.
```typescript
icon?: React.ReactNode;
```
#### metadata
Additional data passed to the AI as context.
```typescript
metadata?: Record;
```
## ContextAttachmentState
The state interface returned by the `useTamboContextAttachment` hook.
```typescript
interface ContextAttachmentState {
attachments: ContextAttachment[];
addContextAttachment: (context: Omit) => void;
removeContextAttachment: (id: string) => void;
clearContextAttachments: () => void;
customSuggestions: Suggestion[] | null;
setCustomSuggestions: (suggestions: Suggestion[] | null) => void;
}
```
### Properties
#### attachments
Array of active context attachments currently displayed as badges.
```typescript
attachments: ContextAttachment[];
```
#### addContextAttachment
Function to add a new context attachment. The `id` is automatically generated.
```typescript
addContextAttachment: (context: Omit) => void;
```
#### removeContextAttachment
Function to remove a specific context attachment by its ID.
```typescript
removeContextAttachment: (id: string) => void;
```
#### clearContextAttachments
Function to remove all active context attachments.
```typescript
clearContextAttachments: () => void;
```
#### customSuggestions
Current custom suggestions that override auto-generated ones. Returns `null` if using auto-generated suggestions.
```typescript
customSuggestions: Suggestion[] | null;
```
#### setCustomSuggestions
Function to set custom suggestions or clear them by passing `null`.
```typescript
setCustomSuggestions: (suggestions: Suggestion[] | null) => void;
```
## ContextHelperData
Data structure returned by a context helper function.
```typescript
type ContextHelperData = Record;
```
This type represents the data sent to the AI when a context attachment is active. You can customize this structure using the `getContextHelperData` prop on `TamboProvider`.
# Component Props and Performance
URL: /best-practices/component-data-props
When Tambo chooses a component to use, it will look at the component's `propsSchema` to know what props to pass to the component, and how they should be structured.
If the size of the props object passed to a component is large, it can impact Tambo's performance, since Tambo needs to generate more data. Essentially, the response time of a request to Tambo is directly related to, and mostly determined by, the size (token count) of the props object generated for the chosen component.
This is most relevant when using components that need to show real data, such as a component that shows a list of fetched objects.
For example, consider this component which shows a list of meetings:
```tsx
const MeetingsList = ({ meetings: {id: string, title: string, date: string, location: string, attendees: string[]}[] }) => {
return (
{meetings.map(meeting => )}
)
}
```
Where the registration of the component might be:
```tsx
registerComponent({
name: "MeetingsList",
description: "A list of meetings",
component: MeetingsList,
propsSchema: z.object({
meetings: z.array(
z.object({
id: z.string(),
title: z.string(),
date: z.string(),
location: z.string(),
attendees: z.array(z.string()),
}),
),
}),
});
```
When the user asks for a list of meetings and Tambo decides to use this component, Tambo will need to generate every meeting object. If there are many meetings, this can take a long time.
### A Workaround
If the problem is the size of the props object, one way to solve it is to structure the component's props such that they will always be small.
Instead of having Tambo generate the list of meeting objects, we can have Tambo generate a 'meetings request', and have the component fetch its own data.
Heres what the MeetingsList component would look like restructured in this way:
```tsx
const MeetingsList = ({
meetingsRequest: { startDate: string, endDate: string },
}) => {
const [meetings, setMeetings] = useState([]);
useEffect(() => {
fetchMeetings(startDate, endDate);
}, [startDate, endDate]);
const fetchMeetings = async (startDate: string, endDate: string) => {
const meetings = await getMeetings(startDate, endDate); // Assuming there is a function to fetch meetings somewhere
setMeetings(meetings);
};
return (
{meetings.map((meeting) => (
))}
);
};
```
Where the registration of the component would be updated to:
```tsx
Tambo.registerComponent({
name: "MeetingsList",
description: "A list of meetings",
component: MeetingsList,
propsSchema: z.object({
meetingsRequest: z.object({
startDate: z.string(),
endDate: z.string(),
}),
}),
});
```
Now, Tambo only needs to generate the startDate and endDate strings.
### Considerations
In the restructured MeetingsList above, we used `startDate` and `endDate` as the props. This assumes that the 'meetings api' we fetch data from has parameters for startDate and endDate. In general, the 'request object' that we allow Tambo to generate should match the parameters of the api we fetch data from.
This also means that when structuring components like this, Tambo will only be able to filter in ways that the API allows. For example, if the user asks for meetings with people named 'John', but the API (and the component props) only allows filtering by startDate and endDate, Tambo will not be able to filter by 'John'. With the original MeetingsList component, Tambo can look at the list of meetings in context and decide which meetings to pass to the component completely based on the user's message.
# CSS & Tailwind Configuration
URL: /cli/configuration
The Tambo CLI automatically configures your CSS and Tailwind setup based on your project's Tailwind CSS version. This page explains what changes are made and how to configure them manually if needed.
## What Gets Configured
When you run Tambo CLI commands (`full-send`, `add`, `update`, `upgrade`), the CLI:
1. **Detects your Tailwind CSS version** (v3 or v4)
2. **Updates your `globals.css`** with required CSS variables
3. **Updates your `tailwind.config.ts`** (v3 only) with basic configuration
4. **Preserves your existing styles** and configuration
The CLI automatically detects your Tailwind version from your `package.json`
and applies the appropriate configuration format. You don't need to specify
which version you're using.
## CSS Variables Added
Tambo components require specific CSS variables to function properly. The CLI adds these in the appropriate format for your Tailwind version:
### Core Tailwind Variables
These standard Tailwind CSS variables are added with Tambo's default color scheme:
```css
/* Light mode */
--background: /* White or appropriate light background */ --foreground:
/* Dark text color */
--primary: /* Tambo brand primary color */
--secondary: /* Secondary accent color */
--muted: /* Muted backgrounds and borders */ --border: /* Border colors */
/* ... additional standard Tailwind variables */;
```
### Tambo-Specific Variables
These custom variables control Tambo component layouts and behavior:
```css
/* Layout dimensions */
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
/* Container and backdrop styles */
--container: /* Light container background */ --backdrop:
/* Modal backdrop opacity */
--muted-backdrop: /* Subtle backdrop for overlays */ /* Border radius */
--radius: 0.5rem;
```
The Tambo-specific variables (`--panel-left-width`, `--panel-right-width`,
`--sidebar-width`, `--container`, `--backdrop`, `--muted-backdrop`) are
essential for proper component functionality. Removing these will break
component layouts.
## Tailwind CSS v3 Configuration
For Tailwind v3 projects, the CLI uses the `@layer base` approach:
### Complete globals.css for v3
```css title="app/globals.css"
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Default Tailwind CSS Variables customized with tambo colors */
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 235 12% 21%;
--primary-foreground: 0 0% 98%;
--secondary: 218 11% 46%;
--secondary-foreground: 0 0% 100%;
--muted: 217 14% 90%;
--muted-foreground: 217 14% 68%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--border: 207 22% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 30 80% 54.9%;
--chart-2: 339.8 74.8% 54.9%;
--chart-3: 219.9 70.2% 50%;
--chart-4: 160 60% 45.1%;
--chart-5: 280 64.7% 60%;
/* Tambo Specific Variables needed for tambo components */
--radius: 0.5rem;
--container: 210 29% 97%;
--backdrop: 210 88% 14% / 0.25;
--muted-backdrop: 210 88% 14% / 0.1;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
.dark {
/* Default Tailwind CSS Variables customized with tambo colors */
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 30 80% 54.9%;
--chart-2: 339.8 74.8% 54.9%;
--chart-3: 219.9 70.2% 50%;
--chart-4: 160 60% 45.1%;
--chart-5: 280 64.7% 60%;
/* Tambo Specific Variables needed for tambo components */
--radius: 0.5rem;
--container: 210 29% 97%;
--backdrop: 210 88% 14% / 0.25;
--muted-backdrop: 210 88% 14% / 0.1;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
```
### tailwind.config.ts for v3
```tsx title="tailwind.config.ts"
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: "class",
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
// Your existing theme config is preserved and merged
};
export default config;
```
## Tailwind CSS v4 Configuration
Tailwind v4 uses CSS-first configuration with a different approach:
### Complete globals.css for v4
```css title="app/globals.css"
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
@theme inline {
/* Tailwind CSS Variables customized with tambo colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
/* Tambo Specific Variables needed for tambo components */
--color-container: var(--container);
--color-backdrop: var(--backdrop);
--color-muted-backdrop: var(--muted-backdrop);
}
:root {
/* Default Tailwind CSS Variables customized with tambo colors */
--background: oklch(1 0 0);
--foreground: oklch(0.14 0 285);
--card: oklch(1 0 0);
--card-foreground: oklch(0.14 0 285);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.14 0 285);
--primary: oklch(0.31 0.02 281);
--primary-foreground: oklch(0.98 0 0);
--secondary: oklch(0.54 0.027 261);
--secondary-foreground: oklch(1 0 0);
--muted: oklch(0.92 0 260);
--muted-foreground: oklch(0.73 0.022 260);
--accent: oklch(0.97 0 286);
--accent-foreground: oklch(0.21 0 286);
--destructive: oklch(0.64 0.2 25);
--border: oklch(0.93 0 242);
--input: oklch(0.92 0 286);
--ring: oklch(0.14 0 285);
--chart-1: oklch(0.72 0.15 60);
--chart-2: oklch(0.62 0.2 6);
--chart-3: oklch(0.53 0.2 262);
--chart-4: oklch(0.7 0.13 165);
--chart-5: oklch(0.62 0.2 313);
/* Tambo Specific Variables needed for tambo components */
--container: oklch(0.98 0 247);
--backdrop: oklch(0.25 0.07 252 / 0.25);
--muted-backdrop: oklch(0.25 0.07 252 / 0.1);
--radius: 0.5rem;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
.dark {
/* Dark Mode Tailwind CSS Variables customized with tambo colors */
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.72 0.15 60);
--chart-2: oklch(0.62 0.2 6);
--chart-3: oklch(0.53 0.2 262);
--chart-4: oklch(0.7 0.13 165);
--chart-5: oklch(0.62 0.2 313);
/* Tambo Specific Variables needed for tambo components */
--container: oklch(0.98 0 247);
--backdrop: oklch(0.25 0.07 252 / 0.25);
--muted-backdrop: oklch(0.25 0.07 252 / 0.1);
--radius: 0.5rem;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
font-family: Arial, Helvetica, sans-serif;
}
}
```
With Tailwind v4, most configuration is done in CSS using `@theme inline`. The
JavaScript config file is not needed.
## Manual Configuration
If you need to manually configure these files or the automatic setup doesn't work:
### 1. Check Your Tailwind Version
```bash
npm list tailwindcss
# or check package.json
```
### 2. Copy the Appropriate CSS
Choose the v3 or v4 format based on your version and copy the complete CSS above into your `globals.css` file.
### 3. Update Tailwind Config (v3 only)
For v3, ensure your `tailwind.config.ts` includes `darkMode: "class"` and the proper content paths.
### 4. Verify Required Variables
Ensure these Tambo-specific variables are present:
* `--panel-left-width`
* `--panel-right-width`
* `--sidebar-width`
* `--container`
* `--backdrop`
* `--muted-backdrop`
* `--radius`
## Merging with Existing Styles
If you already have CSS variables defined, you'll want to merge them carefully
rather than replacing your entire file. The CLI automatically preserves
existing variables, but manual setup requires more care.
### If you have existing CSS variables:
1. **Keep your existing variables** that aren't listed in the Tambo configuration
2. **Add missing Tambo variables** from the appropriate version above
3. **Update conflicting variables** if you want to use Tambo's color scheme
4. **Preserve your custom styling** outside of the CSS variables
### If you have existing `@layer base` blocks:
For Tailwind v3, add the Tambo variables inside your existing `@layer base` block rather than creating a duplicate.
## Troubleshooting
### Components Look Broken
**Problem**: Components have no styling or look broken
**Solution**: Check that all CSS variables are present in your `globals.css`
### Dark Mode Not Working
**Problem**: Dark mode styles not applying
**Solution**:
* For v3: Ensure `darkMode: "class"` in `tailwind.config.ts`
* For v4: Check `@custom-variant dark` is present
* Verify `.dark` class variables are defined
### Version Mismatch
**Problem**: Wrong CSS format for your Tailwind version
**Solution**:
* Check your Tailwind version with `npm list tailwindcss`
* Use v3 format (HSL) for Tailwind 3.x
* Use v4 format (OKLCH) for Tailwind 4.x
### Layout Issues
**Problem**: Panels or sidebars have wrong dimensions
**Solution**: Ensure Tambo layout variables (`--panel-left-width`, etc.) are defined and have appropriate values
### Existing Styles Overridden
**Problem**: Your custom CSS variables got replaced
**Solution**: The CLI preserves existing variables, but if manually copying, merge with your existing styles rather than replacing them
## CLI Behavior
### What the CLI Does
* ✅ **Preserves existing styles**: Your custom CSS is kept
* ✅ **Adds only missing variables**: Won't override your existing variables
* ✅ **Backs up files**: Creates `.backup` files before making changes
* ✅ **Shows diffs**: Previews changes before applying them
* ✅ **Asks permission**: Prompts before modifying existing files
### What the CLI Doesn't Do
* ❌ **Override existing variables**: Your customizations are preserved
* ❌ **Change your color scheme**: Only adds missing standard variables
* ❌ **Modify other CSS**: Only touches CSS variable definitions
* ❌ **Break existing config**: Merges with your existing Tailwind config
The CLI is designed to be safe and non-destructive. It preserves your existing
configuration and only adds what's needed for Tambo components to work.
# Global Options
URL: /cli/global-options
All Tambo CLI commands support these global options. You can use them with any command to modify behavior, skip prompts, or handle common scenarios.
## Available Options
### `--version`
Shows the current version of the Tambo CLI.
```bash
npx tambo --version
# Output: 1.2.3
```
### `--yes, -y`
Auto-answers "yes" to all confirmation prompts.
**Examples:**
```bash
# Skip all prompts during setup
npx tambo init --yes
# Install components without confirmation
npx tambo add form graph --yes
# Update all components without asking
npx tambo update installed --yes
# Migrate components automatically
npx tambo migrate --yes
```
**Use cases:**
* Automated deployments
* CI/CD pipelines
* Batch operations
* When you're confident about the changes
### `--legacy-peer-deps`
Installs dependencies using npm's `--legacy-peer-deps` flag. This resolves common dependency conflicts.
**Examples:**
```bash
# Install with legacy peer deps
npx tambo init --legacy-peer-deps
# Add components with legacy peer deps
npx tambo add message-thread-full --legacy-peer-deps
# Upgrade project with legacy peer deps
npx tambo upgrade --legacy-peer-deps
```
**When to use:**
* Getting peer dependency warnings
* Working with older React versions
* Complex dependency trees
* Corporate environments with strict package policies
If you see errors like "unable to resolve dependency tree" or peer dependency
warnings, try adding `--legacy-peer-deps` to your command.
### `--prefix `
Specifies a custom directory for components instead of the default `components/tambo`.
**Examples:**
```bash
# Install components in src/components/ui
npx tambo add form --prefix=src/components/ui
# List components in custom directory
npx tambo list --prefix=src/components/ui
# Update components in custom location
npx tambo update installed --prefix=src/components/custom
# Migrate from custom source to custom destination
npx tambo migrate --prefix=src/components/tambo
```
**Common prefix patterns:**
* `src/components/ui` - Traditional UI components directory
* `src/components/tambo` - Dedicated Tambo directory in src
* `app/components/ui` - App router components
* `lib/components` - Library-style organization
### `--dry-run`
Preview changes before applying them. Available for commands that modify files.
**Examples:**
```bash
# Preview migration changes
npx tambo migrate --dry-run
# Preview component updates
npx tambo update installed --dry-run
# Preview upgrade changes
npx tambo upgrade --dry-run
```
**Output example:**
```
🔍 Dry run mode - no changes will be made
The following changes would be applied:
📁 Move: components/ui/form.tsx → components/tambo/form.tsx
📁 Move: components/ui/graph.tsx → components/tambo/graph.tsx
📝 Update: lib/tambo.ts (import paths)
Run without --dry-run to apply these changes.
```
## Combining Options
You can combine multiple options in a single command:
```bash
# Install components with custom prefix, skip prompts, and handle conflicts
npx tambo add form graph --prefix=src/components/ui --yes --legacy-peer-deps
# Upgrade with all safety options
npx tambo upgrade --dry-run --prefix=src/components/ui
# Migrate automatically with custom paths
npx tambo migrate --yes --prefix=src/components/custom
```
## Command-Specific Options
Some commands have additional options beyond these global ones:
### `create-app` specific options
```bash
# Initialize git repository
npx tambo create-app my-app --init-git
# Use specific template
npx tambo create-app my-app --template=mcp
```
### `add` specific options
```bash
# Preview component before installing
npx tambo add form --preview
# Install multiple components
npx tambo add form graph canvas-space
```
## Best Practices
### For Development
```bash
# Safe exploration - always preview first
npx tambo migrate --dry-run
npx tambo upgrade --dry-run
# Quick iterations
npx tambo add form --yes
npx tambo update form --yes
```
## Troubleshooting
### Common Issues
**Issue: Command not found**
```bash
# Check CLI version
npx tambo --version
# Update to latest
npm install -g @tambo-ai/cli@latest
```
**Issue: Permission errors**
```bash
# Use npx instead of global install
npx tambo init --yes
```
**Issue: Dependency conflicts**
```bash
# Use legacy peer deps
npx tambo add form --legacy-peer-deps
```
**Issue: Wrong directory**
```bash
# Check current components
npx tambo list
# Use correct prefix
npx tambo list --prefix=src/components/ui
```
# Overview
URL: /cli
import { Card, Cards } from "fumadocs-ui/components/card";
The Tambo CLI is a tool to help you get Tambo apps setup quickly.
Here you'll find a description of each command available in the tambo cli.
## Installation
The Tambo CLI is available as an npm package and can be used with `npx`:
```bash
npx tambo
```
## Quick Start
For new projects, the fastest way to get started is:
```bash
# Create a new Tambo app
npm create tambo-app@latest my-tambo-app
# Or add to existing project
npx tambo full-send
```
## Command Categories
### Project Setup
* [`create-app`](/cli/commands/create-app) - Create a new Tambo app from template
* [`init`](/cli/commands/init) - Initialize Tambo in existing project
* [`full-send`](/cli/commands/full-send) - Complete setup with components
### Component Management
* [`add`](/cli/commands/add) - Add Tambo components to your project
* [`list`](/cli/commands/list) - List installed components
* [`update`](/cli/commands/update) - Update components to latest versions
### Project Maintenance
* [`upgrade`](/cli/commands/upgrade) - Upgrade entire project (packages + components)
* [`migrate`](/cli/commands/migrate) - Migrate from legacy component structure
## Need Help?
* Check out our [common workflows](/cli/workflows) for typical usage patterns
* See [global options](/cli/global-options) available for all commands
* Browse individual command documentation in the Commands section
Learn typical CLI usage patterns and workflows
Get started with the full-send command
# Common Workflows
URL: /cli/workflows
This guide covers the most common workflows you'll use with the Tambo CLI, organized by scenario and use case.
## Getting Started Workflows
### New Project Setup
For brand new projects, use the template approach:
```bash
# Create new project with template
npm create tambo-app@latest my-tambo-app
cd my-tambo-app
# Complete setup with API key
npx tambo init
# Start development
npm run dev
```
### Adding to Existing Project
For existing React/Next.js projects:
```bash
# Quick setup with components
npx tambo full-send
# Or step-by-step approach
npx tambo init
npx tambo add message-thread-full
```
After running `init` or `full-send`, make sure to add your API key to
`.env.local`: `NEXT_PUBLIC_TAMBO_API_KEY=your_api_key_here`
## Component Management Workflows
### Adding Components Strategically
Start with core components, then add specialized ones:
```bash
# 1. Start with a message interface
npx tambo add message-thread-collapsible
# 2. Add form capabilities
npx tambo add form
# 3. Add visualization components
npx tambo add graph canvas-space
# 4. Add advanced interactions
npx tambo add control-bar
```
### Checking What's Installed
```bash
# See what components you have
npx tambo list
# Check if updates are available
npx tambo update --dry-run
```
## Development Workflows
### Building a Chat Interface
```bash
# 1. Setup project
npx tambo init
# 2. Add chat component
npx tambo add message-thread-full
# 3. Configure in your app
# Add TamboProvider to layout.tsx
# Import and use MessageThreadFull component
```
### Building a Form Experience
```bash
# 1. Add form components
npx tambo add form input-fields
# 2. Register form-related tools in lib/tambo.ts
# 3. Create form validation components
```
### Building a Data Visualization App
```bash
# 1. Add visualization components
npx tambo add graph canvas-space
# 2. Add control interface
npx tambo add control-bar
# 3. Register data tools for fetching/processing
```
## Maintenance Workflows
### Keeping Everything Updated
```bash
# Option 1: Update everything at once
npx tambo upgrade
# Option 2: Update selectively
npx tambo update installed # All components
npx tambo update form graph # Specific components
```
### Migrating from Legacy Structure
If you installed Tambo components before the directory structure change (more info [here](https://docs.tambo.co/cli/commands/migrate)):
```bash
# 1. Check what needs migration
npx tambo migrate --dry-run
# 2. Perform migration
npx tambo migrate
# 3. Test everything still works
npm run dev
```
## Troubleshooting Workflows
### Dependency Conflicts
If you encounter peer dependency issues:
```bash
# Use legacy peer deps flag
npx tambo add message-thread-full --legacy-peer-deps
npx tambo upgrade --legacy-peer-deps
```
### Component Not Working
```bash
# 1. Check if component is properly installed
npx tambo list
# 2. Update to latest version
npx tambo update
# 3. Check your TamboProvider setup
# Make sure API key is set
# Verify component is imported correctly
```
### Clean Reinstall
```bash
# 1. Remove existing components
rm -rf components/tambo/*
# 2. Reinstall fresh
npx tambo add message-thread-full form graph
# 3. Update configuration
npx tambo upgrade
```
## Quick Reference
### Most Common Commands
```bash
# Quick setup
npx tambo full-send
# Add components
npx tambo add message-thread-full form
# Update everything
npx tambo upgrade
# Check status
npx tambo list
```
### Flags You'll Use Often
For detailed information about all available flags and options, see the [Global Options](/cli/global-options) page.
Quick reference:
* `--yes` - Skip confirmation prompts
* `--legacy-peer-deps` - Fix dependency conflicts
* `--prefix=` - Custom component directory
* `--dry-run` - Preview changes before applying
# Chat Starter App
URL: /examples-and-templates/chat-starter-app
[Github](https://github.com/tambo-ai/tambo-template?tab=readme-ov-file#tambo-template)
```bash title="Install the starter app:"
npm create tambo-app@latest my-tambo-app
```
This template app shows how to setup the fundamental parts of an AI application using tambo:
**Component registration**
See in [src/lib/tambo.ts](https://github.com/tambo-ai/tambo-template/blob/main/src/lib/tambo.ts) how a graph component is registered with tambo.
**Tool Registration**
See in [src/lib/tambo.ts](https://github.com/tambo-ai/tambo-template/blob/main/src/lib/tambo.ts) how population data tools are registered with tambo.
**UI for sending messages to tambo and showing responses**
The components used within [src/components/tambo/message-thread-full.tsx](https://github.com/tambo-ai/tambo-template/blob/main/src/components/tambo/message-thread-full.tsx) use hooks from tambo's react SDK to send messages and show the thread history.
**Wrap the app with the `TamboProvider`**
In [src/app/chat/page.tsx](https://github.com/tambo-ai/tambo-template/blob/main/src/app/chat/page.tsx) we wrap the page with the `TamboProvider` to enable the usage of tambo's react SDK within the message sending and thread UI components.
# Supabase MCP Client App
URL: /examples-and-templates/supabase-mcp-client
Add MCP server tools and UI tools to a React app using tambo to build an AI app with minimal custom code.
Use this as a starting point to build apps to interact with any MCP server.
[Github](https://github.com/tambo-ai/supabase-mcp-client/tree/main?tab=readme-ov-file#supabase-mcp-client-react-app)
This application makes use of tambo's `TamboMcpProvider` to easily add the tools defined by the official [Supabase MCP server](https://github.com/supabase-community/supabase-mcp).
Custom react components to show interactive visualizations of the responses from the Supabase tools are registered with tambo in [src/lib/tambo.ts](https://github.com/tambo-ai/supabase-mcp-client/blob/main/src/lib/tambo.ts)
# Suggestions
URL: /concepts/suggestions
Tambo automatically analyzes user interactions and generates contextual suggestions after each assistant message. You can also override these with custom suggestions using the `useTamboContextAttachment` hook.
## Using Suggestions
### The useTamboSuggestions Hook
The main way to interact with suggestions is through the `useTamboSuggestions` hook:
```tsx title="thread-with-suggestions.tsx"
import { useTamboSuggestions } from "@tambo-ai/react";
function ThreadWithSuggestions() {
const {
suggestions, // Array of current suggestions
isLoading, // Whether suggestions are being generated or loading
isAccepting, // Whether a suggestion is being applied
error, // Current error message, if any
accept, // Function to apply a suggestion
} = useTamboSuggestions({
maxSuggestions: 3, // Optional: Number of suggestions to generate (1-10)
});
if (isLoading) return ;
if (error) return {error};
return (
{suggestions.map((suggestion) => (
))}
);
}
```
### Hook Configuration
The `useTamboSuggestions` hook accepts an optional configuration object:
```tsx title="thread-with-suggestions.tsx"
interface UseTamboSuggestionsOptions {
/** Maximum number of suggestions to generate (1-10, default 3) */
maxSuggestions?: number;
}
```
### When Suggestions Appear
Suggestions are automatically generated after each assistant (tambo) message in the thread. The hook manages:
* Loading states while generating suggestions
* Error handling for failed generations
* Automatic cleanup when messages change
### Accepting Suggestions
The `accept` function provides two modes:
```tsx title="thread-with-suggestions.tsx"
// Just set the suggestion text in the input
accept(suggestion);
// Set text and automatically submit
accept(suggestion, true);
```
## Suggestion Types
### Basic Suggestion Structure
```tsx title="thread-with-suggestions.tsx"
interface Suggestion {
/** Unique identifier for the suggestion */
id: string;
/** Short title or summary of the suggestion */
title: string;
/** Detailed explanation of the suggestion */
detailedSuggestion: string;
/** ID of the message this suggestion is for */
messageId: string;
/** Additional metadata for the suggestion */
metadata?: Record;
}
```
### State Management Types
The hook uses additional types for state management:
```tsx title="thread-with-suggestions.tsx"
type SuggestionState = {
/** List of current suggestions */
items: Suggestion[];
/** Whether suggestions are currently being loaded */
loading: boolean;
/** Current error message, if any */
error: string | null;
};
```
## Error Handling
The hook provides built-in error handling for common scenarios:
```tsx title="thread-with-suggestions.tsx"
const ERROR_MESSAGES = {
GENERATION: "Unable to generate suggestions right now",
EMPTY: "No suggestions available for this message",
ACCEPT: "Unable to apply suggestion, please try again",
API_ERROR: "Failed to communicate with tambo",
};
```
## Integration Example
Here's a complete example showing how to integrate suggestions with a message thread:
```tsx title="thread-with-suggestions.tsx"
import { useTamboThread, useTamboSuggestions } from "@tambo-ai/react";
function MessageThread() {
const { thread } = useTamboThread();
const { suggestions, isLoading, isAccepting, error, accept } =
useTamboSuggestions();
// Get the latest message
const latestMessage = thread.messages[thread.messages.length - 1];
const isLatestFromHydra = latestMessage?.role === "assistant";
return (
);
}
```
Suggestions are automatically generated for each tambo message when the
`useTamboSuggestions` hook is used. You don't need to manually trigger
suggestion generation.
## Custom Suggestions
You can override auto-generated suggestions with your own custom suggestions using the `useTamboContextAttachment` hook:
```tsx
import { useTamboContextAttachment } from "@tambo-ai/react";
function ComponentSelector({ component }) {
const { setCustomSuggestions } = useTamboContextAttachment();
const handleSelectComponent = () => {
setCustomSuggestions([
{
id: "1",
title: "Edit this component",
detailedSuggestion: `Modify the ${component.name} component`,
messageId: "",
},
{
id: "2",
title: "Add a feature",
detailedSuggestion: `Add a new feature to ${component.name}`,
messageId: "",
},
]);
};
return ;
}
```
To clear custom suggestions and return to auto-generated ones:
```tsx
const { setCustomSuggestions } = useTamboContextAttachment();
setCustomSuggestions(null);
```
Custom suggestions are particularly useful when combined with [context attachments](/concepts/additional-context/context-attachments) to provide focused, contextual actions based on what the user is currently viewing or working on.
# Components
URL: /getting-started/components
## Component Library
Our component library provides ready-to-use UI elements specifically designed for AI interactions:
* Chat interfaces
* Message threads
* Input fields
* Response indicators
* Suggestion buttons
* and Generative UI components
## Explore Our Components
For a complete showcase of all available components, interactive examples, and implementation guides, visit our dedicated component site:
Explore our full component library at ui.tambo.co
## Try our CLI command
The quickest way to add Tambo components to your project is with our CLI:
```bash
npx tambo full-send
```
This will install a set of common components and hook them up to Tambo in your project.
For more customization options and advanced component usage, refer to the component documentation at [ui.tambo.co](https://ui.tambo.co).
import { Card, Cards } from "fumadocs-ui/components/card";
# Integrate
URL: /getting-started/integrate
This example assumes an application using NextJs, but NextJs is not required.
If you have an existing React application and want to add Tambo functionality, follow these steps:
### Step 1: Install tambo-ai
```bash
npx tambo full-send
```
This command will:
* Setup Tambo in your existing project and get you an API key
* Install components that are hooked up to tambo-ai
* Show you how to wrap your app with the ``
If you prefer manual setup, you can run `npx tambo init` which will just get you set up with an API key. If you don't have an account yet, sign up for free first.
### Step 2: Add the TamboProvider
Once the installation completes, update your `src/app/layout.tsx` file to enable Tambo:
```tsx title="src/app/layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
You need to create a `.env.local` file in the root of your project to store
your Tambo API key: `NEXT_PUBLIC_TAMBO_API_KEY=your_api_key_here`
Replace `your_api_key_here` with the actual API key you received during setup.
This file should not be committed to version control as it contains sensitive
information.
Note that the `TamboProvider` only works in the browser. On Next.js, specify
`"use client"` at the top of your file to ensure that the `TamboProvider` is
rendered in the browser.
### Step 3: Add the MessageThreadCollapsible component
The `` component that the `full-send` command installed provides a complete chat interface for your AI assistant. Here's how to add it to your `src/app/page.tsx` file:
```tsx title="src/app/page.tsx"
"use client";
import { MessageThreadCollapsible } from "../source/components/message-thread-collapsible";
export default function Home() {
return (
);
}
```
### Run your app
```bash
npm run dev
```
When you are done, you should see a chat interface like this:
# Start Here
URL: /getting-started/quickstart
Download and run our generative UI chat template to get an understanding of the fundamental features of Tambo. This application will show you how to build generative UI, integrate tools, send messages to Tambo, and stream responses.
import { ImageZoom } from "fumadocs-ui/components/image-zoom";
### Template Installation
#### 1. Download the code
```bash title="Create and navigate to your project"
npm create tambo-app@latest my-tambo-app && cd my-tambo-app
```
This will copy the source code of the template app into your directory and install the dependencies. See the source repo [here.](https://github.com/tambo-ai/tambo-template)
#### 2. Get a Tambo API key
```bash title="Initialize Tambo"
npx tambo init
```
To send messages to Tambo, you need to create a Project through Tambo and generate an API key to send with requests. This command will walk you through the setup of your first Tambo project, generate an API key, and set the API key in your project automatically.
#### 3. Run the app
```bash title="Run the app"
npm run dev
```
Start the app and go to `localhost:3000` in your browser to start sending messages to Tambo!
### Customize
To get a better understanding of what's happening in this application, try to make a change to update Tambo's capabilities.
In `/src/lib/tambo.ts` you'll see how the template registers components and tools with Tambo. In `/src/app/chat/page.tsx` you'll see how those tools and components are passed to the `TamboProvider` to 'register' them.
#### Add a component
Let's create and register a new component with Tambo to give our AI chat a new feature.
In `src/components` create a new file called `recipe-card.tsx` and paste the following code into it:
```tsx title="recipe-card.tsx"
"use client";
import { ChefHat, Clock, Minus, Plus, Users } from "lucide-react";
import { useState } from "react";
interface Ingredient {
name: string;
amount: number;
unit: string;
}
interface RecipeCardProps {
title?: string;
description?: string;
ingredients?: Ingredient[];
prepTime?: number; // in minutes
cookTime?: number; // in minutes
originalServings?: number;
}
export default function RecipeCard({
title,
description,
ingredients,
prepTime = 0,
cookTime = 0,
originalServings,
}: RecipeCardProps) {
const [servings, setServings] = useState(originalServings || 1);
const scaleFactor = servings / (originalServings || 1);
const handleServingsChange = (newServings: number) => {
if (newServings > 0) {
setServings(newServings);
}
};
const formatTime = (minutes: number) => {
if (minutes < 60) {
return `${minutes}m`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
return remainingMinutes > 0
? `${hours}h ${remainingMinutes}m`
: `${hours}h`;
};
const totalTime = prepTime + cookTime;
return (
);
}
```
Register it with Tambo by updating the `components` array in `src/lib/tambo.ts` with the following entry:
```tsx title="tambo.ts"
{
name: "RecipeCard",
description: "A component that renders a recipe card",
component: RecipeCard,
propsSchema: z.object({
title: z.string().describe("The title of the recipe"),
description: z.string().describe("The description of the recipe"),
prepTime: z.number().describe("The prep time of the recipe in minutes"),
cookTime: z.number().describe("The cook time of the recipe in minutes"),
originalServings: z
.number()
.describe("The original servings of the recipe"),
ingredients: z
.array(
z.object({
name: z.string().describe("The name of the ingredient"),
amount: z.number().describe("The amount of the ingredient"),
unit: z.string().describe("The unit of the ingredient"),
})
)
.describe("The ingredients of the recipe"),
}),
},
```
Now refresh the browser page and send a message like "Show me a recipe" and you should see Tambo generate and stream in an instance of your `RecipeCard` component.
#### Add a tool
You might notice that when using our added `RecipeCard` component above, Tambo generates recipe data completely from scratch. To allow Tambo to retrieve the list of ingredients we actually have, we can add a tool to get them.
In `src/lib/tambo.ts` add the following entry to the 'tools' array:
```tsx title="tambo.ts"
{
name: "get-available-ingredients",
description:
"Get a list of all the available ingredients that can be used in a recipe.",
tool: () => [
"pizza dough",
"mozzarella cheese",
"tomatoes",
"basil",
"olive oil",
"chicken breast",
"ground beef",
"onions",
"garlic",
"bell peppers",
"mushrooms",
"pasta",
"rice",
"eggs",
"bread",
],
toolSchema: z.function().returns(z.array(z.string())),
},
```
Now refresh the browser page and send a message like "Show me a recipe I can make" and you should see Tambo look for the available ingredients and then generate a `RecipeCard` using them.
### Going Further
This template app just scratches the surface of what you can build with Tambo. By using Tambo in creative ways you can make truly magical custom user experiences!
# Custom LLM Parameters
URL: /models/custom-llm-parameters
Tambo uses an LLM behind the scenes to process user messages. You can change what model Tambo uses, and while Tambo uses certain parameters by default when calling the LLM, you can override these parameters to customize behavior depending on your chosen provider.
Custom parameters are available for **OpenAI-compatible providers**. Other
providers (OpenAI, Anthropic, etc.) are limited to common parameters for
compatibility.
## How Does It Work?
Custom LLM parameters allow you to override default model settings with provider-specific configurations. Parameters are stored per model, letting you optimize different models independently.
**Example configuration:**
* `temperature`: 0.7
* `maxOutputTokens`: 1000
* `topP`: 0.9
* `presencePenalty`: 0.1
## Why Use Custom LLM Parameters?
* **Fine-tune output quality** - Control randomness, length, and creativity
* **Optimize for use cases** - Different parameters for creative writing vs. code generation
* **Provider compatibility** - Support both standard and custom parameters
* **Model-specific tuning** - Configure each model independently
## Configuring Parameters in the Dashboard
All LLM parameters are configured through your project settings in the dashboard.
### Step 1: Access Provider Settings
1. Navigate to your project in the dashboard
2. Go to **Settings** → **LLM Providers**
3. Select your provider and model
### Step 2: Add Parameters
The dashboard shows suggested parameters based on your selected model:
**For Common Parameters:**
1. Under **Custom LLM Parameters**, you'll see suggested parameters like `temperature`, `maxOutputTokens`, `topP`, etc.
2. Click **+ temperature** (or any other parameter) to add it
3. Enter the value (e.g., `0.7` for temperature)
4. Click **Save** to apply the configuration
**For Custom Parameters (OpenAI-Compatible Only):**
1. If you don't see the parameter you need in the suggestions, you can add custom parameters
2. Click to add a parameter manually
3. Enter the parameter name (e.g., `max_tokens`, `logit_bias`)
4. Enter the value in the appropriate format (string, number, boolean, array, or object)
5. Click **Save** to apply
The dashboard automatically shows relevant parameter suggestions based on your
selected provider and model. These suggestions include common parameters that
work across all providers.
### Example Configuration
**Setting up a creative writing model:**
1. Select your provider and model
2. Click **+ temperature** → Enter `0.9`
3. Click **+ topP** → Enter `0.95`
4. Click **+ presencePenalty** → Enter `0.6`
5. Click **+ maxOutputTokens** → Enter `2000`
6. Click **Save**
## Basic Parameters
### Common Parameters (All Providers)
These are the parameters **supported by tambo** and **suggested for use** across all LLM providers. For providers other than OpenAI-compatible, users can **only use these common parameters** - custom parameters are not available. These parameters are supported across all tambo providers:
| Parameter | Type | Description | Range/Example |
| ---------------------- | ------ | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| **`temperature`** | number | Controls randomness in output. Lower values for deterministic responses, higher values for creative responses. | `0.0-0.3` (deterministic), `0.7-1.0` (creative) |
| **`maxOutputTokens`** | number | Maximum number of tokens to generate. Helps control response length and costs. | `100-4000` (varies by model) |
| **`maxRetries`** | number | Number of retry attempts for failed API calls. | `1-5` |
| **`topP`** | number | Nucleus sampling threshold. Alternative to temperature for controlling randomness. | `0.0-1.0` |
| **`topK`** | number | Top-K sampling limit. Restricts sampling to top K most likely tokens. | `1-100` |
| **`presencePenalty`** | number | Penalty for introducing new topics. Higher values encourage staying on topic. | `-2.0 to 2.0` |
| **`frequencyPenalty`** | number | Penalty for token repetition. Higher values reduce repetitive text. | `-2.0 to 2.0` |
| **`stopSequences`** | array | Array of strings that stop generation when encountered. | `["\n", "###"]` |
| **`seed`** | number | Random seed for deterministic sampling. Same seed + prompt = same output. | Any integer |
| **`headers`** | object | Custom HTTP headers for requests. | `{"Authorization": "Bearer token"}` |
While these parameters are supported across all tambo providers, **tambo does
not guarantee specific model behavior** when using these parameters. Different
models may interpret the same parameter values differently, and results can
vary based on the model, prompt, and context. Always test parameter
combinations with your specific use case.
### Parameter Data Types
| Type | Description | Examples |
| ----------- | ----------------- | ------------------------------------------ |
| **string** | Text values | `"stop"`, `"You are a helpful assistant"` |
| **number** | Numeric values | `0.7`, `1000` |
| **boolean** | True/false values | `true`, `false` |
| **array** | JSON arrays | `["\n", "###"]`, `[1, 2, 3]` |
| **object** | JSON objects | `{"key": "value"}`, `{"temperature": 0.5}` |
## OpenAI-Compatible Providers
OpenAI-compatible providers support both **suggested parameters** (the common parameters above) and **custom parameters** for advanced use cases.
### Suggested Parameters for OpenAI-Compatible
All common parameters listed above are available as suggestions for OpenAI-compatible providers.
### Custom Parameters for OpenAI-Compatible
For full flexibility with OpenAI-compatible APIs, you can add any custom parameter supported by the provider. These parameters are configured through the tambo UI and passed directly to the OpenAI-compatible API.
#### Available Custom Parameters
These are examples of custom parameters that may be supported by OpenAI-compatible providers following OpenAI's API:
| Parameter | Type | Description |
| ---------------- | ------- | --------------------------------------------------- |
| **`max_tokens`** | number | Alternative to `maxOutputTokens` |
| **`logit_bias`** | object | Modify token probabilities (e.g., `{"1234": -100}`) |
| **`user`** | string | End-user identifier for monitoring |
| **`suffix`** | string | Text to append after completion |
| **`logprobs`** | number | Include log probabilities in response |
| **`echo`** | boolean | Include prompt in completion |
| **`best_of`** | number | Generate multiple completions, return best |
When adding custom parameters, **results are not guaranteed**. Different
OpenAI-compatible providers may interpret or support these parameters
differently. The examples above are suggestions only—always verify with your
specific provider's documentation and test thoroughly before production use.
## Advanced Usage Patterns
### Creative Writing Model
* `temperature`: 0.9 (high creativity)
* `topP`: 0.95 (diverse word choices)
* `presencePenalty`: 0.6 (encourage new topics)
* `maxOutputTokens`: 2000 (longer responses)
### Code Generation Model
* `temperature`: 0.2 (low randomness)
* `topP`: 0.1 (focused word choices)
* `frequencyPenalty`: 0.3 (reduce repetition)
* `stopSequences`: \["\n\n", "###"] (stop at logical breaks)
### Deterministic Analysis Model
* `temperature`: 0.0 (completely deterministic)
* `seed`: 42 (reproducible results)
* `maxOutputTokens`: 500 (controlled length)
### Custom OpenAI-Compatible Setup
* `temperature`: 0.7
* `max_tokens`: 1000
* `logit_bias`: `{"50256": -100}` (modify token probabilities)
* `user`: "analytics-user" (for monitoring)
* `presence_penalty`: 0.1
## Integration with Projects
Parameters are configured per project and stored with the following hierarchy:
* **Provider** (e.g., "openai", "openai-compatible")
* **Model** (e.g., "gpt-4", "claude-3-sonnet", "custom-model-v1")
* **Parameters** (key-value configuration)
This allows different projects to have different parameter sets for the same model, enabling fine-tuned optimization across use cases.
## Best Practices
* **Start with defaults**: Begin with suggested parameters before adding custom ones
* **Test incrementally**: Change one parameter at a time to understand effects
* **Document configurations**: Note which parameter sets work best for specific use cases
* **Monitor usage**: Higher token limits and retries can increase API costs
* **Use custom parameters sparingly**: Only for OpenAI-compatible providers when needed
## Troubleshooting
**Parameters not applying?**
* Verify you're using an OpenAI-compatible provider for custom parameters
* Check parameter syntax matches the expected type (string/number/boolean/array/object)
**Model not responding as expected?**
* Lower temperature values (0.0-0.3) for more deterministic responses
* Adjust `topP` and `topK` for fine-grained control over randomness
* Use `stopSequences` to prevent rambling responses
**API errors with custom parameters?**
* Ensure custom parameter names match the provider's API documentation
* Verify parameter values are within acceptable ranges
* Check that the provider supports the custom parameter you're trying to use
# Labels
URL: /models/labels
Streaming may behave inconsistently in models other than **OpenAI**. We're
aware of the issue and are actively working on a fix. Please proceed with
caution when using streaming on non-OpenAI models.
Models in tambo carry a **status label**, shown when you select a model from the LLM settings\
(**Dashboard → Project → Settings → LLM Providers**).
### Why Use Labels?
* **Set expectations**: Understand tambo’s confidence level for each model.
* **Guide selection**: Prefer `tested` models for production; approach others with care.
* **Highlight caveats**: `known-issues` labels call out specific behaviors we've observed.
### Label Definitions
| Label | Meaning |
| -------------- | ------------------------------------------------------------------ |
| `tested` | Validated on common tambo tasks. Recommended for most workflows. |
| `untested` | Available, but not yet validated. Use it—but test in your context. |
| `known-issues` | Usable, but we’ve observed behaviors worth noting (see below). |
### Observed Behaviors & Notes
These behaviors were noted during testing on common tambo tasks. The models below are still usable—just keep the caveats in mind.
#### Google
* **Gemini 2.5 Pro / 2.5 Flash / 2.0 Flash / 2.0 Flash Lite**:
* May occasionally resist rendering as requested. Sometimes it completes the request, but behavior can be inconsistent.
* Try clarifying instructions (e.g., “Return a bulleted list only”).
* Outputs may have formatting quirks. Be cautious when structure matters.
#### Anthropic
* **Claude 3.5 Haiku**:
* May fail to fully render components, even when receiving the correct data.
* *Example:* When rendering a graph component, it may leave it in a loading state without streaming the data into props.
#### Mistral
* **Mistral Large 2.1 / Medium 3**:
* Similar to Gemini, may inconsistently follow rendering instructions.
* Try clarifying the prompt structure.
* Formatting idiosyncrasies can occur—validate outputs where structure is important.
For production-critical formatting, use **Tested** models and validate
outputs. When using **Untested** or **Known Issues** models, run a small
prompt suite to check behavior in your specific workload.
### Usage Patterns
* **Prefer `tested`** models for reliability. If using others, test with your use case.
* **Use inline notes** in the picker to spot caveats quickly.
### Integration
You can change providers and models at the project level under **LLM Provider Settings**. tambo will apply your token limits and defaults accordingly.
# Reasoning Models
URL: /models/reasoning-models
Reasoning models are specialized LLMs that expose their internal thought process before generating a final response. These models excel at complex tasks requiring multi-step reasoning, problem-solving, and logical analysis by spending additional compute time "thinking" through the problem.
You can add reasoning parameters through your project's LLM provider settings.
Tambo currently supports reasoning capabilities for **OpenAI models** (GPT-5,
GPT-5 Mini, GPT-5 Nano, O3) and **Google Gemini models** (2.5 Pro, 2.5 Flash).
Each provider uses different parameter names to control reasoning behavior.
## What Are Reasoning Models?
Traditional LLMs generate responses token-by-token in a single forward pass. Reasoning models add an intermediate "thinking" phase where the model:
1. **Analyzes the problem** - Breaks down complex queries into sub-problems
2. **Explores solutions** - Considers multiple approaches and their tradeoffs
3. **Verifies reasoning** - Checks its logic before committing to an answer
4. **Generates response** - Produces the final output based on verified reasoning
This thinking process is captured as **reasoning tokens** that you can access, display, and analyze alongside the final response.
## Supported Models
### OpenAI Models
OpenAI's reasoning models expose their thought process through dedicated parameters:
| Model | Description |
| -------------- | --------------------------------------------- |
| **GPT-5** | Latest flagship model with advanced reasoning |
| **GPT-5 Mini** | Faster, cost-effective reasoning model |
| **GPT-5 Nano** | Smallest, most efficient reasoning model |
| **O3** | Specialized reasoning model |
**OpenAI Reasoning Parameters:**
| Parameter | Type | Description | Values |
| ------------------ | ------ | --------------------------------------- | ------------------------------------------ |
| `reasoningEffort` | string | Controls intensity of reasoning process | `"minimal"`, `"low"`, `"medium"`, `"high"` |
| `reasoningSummary` | string | Enables reasoning token output | `"auto"`, `"detailed"` |
### Google Gemini Models
Gemini models use a "thinking budget" approach to control reasoning:
| Model | Description |
| -------------------- | ------------------------------------------------ |
| **gemini-2.5-pro** | Most capable Gemini model with extended thinking |
| **gemini-2.5-flash** | Fast, efficient reasoning for production use |
**Gemini Reasoning Parameters:**
| Parameter | Type | Description |
| -------------------------------- | ------- | --------------------------------------- |
| `thinkingConfig` | object | Configuration for thinking behavior |
| `thinkingConfig.thinkingBudget` | number | Token budget allocated for thinking |
| `thinkingConfig.includeThoughts` | boolean | Whether to include thinking in response |
## Configuring Reasoning in the Dashboard
Reasoning parameters are configured in your project's LLM provider settings on the dashboard. Here's how to set them up:
### Step 1: Access Provider Settings
1. Navigate to your project in the dashboard
2. Go to **Settings** → **LLM Providers**
3. Select your provider and model that supports reasoning
### Step 2: Configure OpenAI Reasoning Parameters
For OpenAI models (GPT-5, GPT-5 Mini, GPT-5 Nano, O3):
1. Under **Custom LLM Parameters**, you'll see suggested parameters
2. Click **+ reasoningEffort** to add it
3. Set the value to control reasoning intensity:
* **`"low"`** - Quick reasoning for simple tasks (faster, cheaper)
* **`"medium"`** - Balanced reasoning for most use cases (recommended)
* **`"high"`** - Deep reasoning for complex problems (slower, more expensive)
4. Click **+ reasoningSummary** to add it
5. Set the value to **`"auto"`** to receive reasoning tokens in responses
6. Click **Save** to apply the configuration
When you select an OpenAI reasoning model, the dashboard automatically shows
**reasoningEffort** and **reasoningSummary** as suggested parameters. Just
click them to add!
### Step 3: Configure Gemini Reasoning Parameters
For Gemini models (2.5 Pro, 2.5 Flash):
1. Under **Custom LLM Parameters**, look for the **+ thinkingConfig** suggestion
2. Click **+ thinkingConfig** to add it
3. Configure the thinking behavior with a JSON object:
```json
{
"thinkingBudget": 5000,
"includeThoughts": true
}
```
4. Set **`thinkingBudget`** based on your needs:
* **1000-2000**: Quick reasoning for simple tasks
* **3000-5000**: Standard reasoning for most use cases (recommended)
* **7000+**: Extended reasoning for complex problems
5. Set **`includeThoughts`** to **`true`** to receive thinking tokens
6. Click **Save** to apply the configuration
When you select a Gemini reasoning model, the dashboard automatically shows
**thinkingConfig** as a suggested parameter. Just click it to add!
### Example Configurations
**OpenAI - Balanced Reasoning:**
* Parameter: `reasoningEffort` → Value: `"medium"`
* Parameter: `reasoningSummary` → Value: `"auto"`
**OpenAI - Deep Reasoning:**
* Parameter: `reasoningEffort` → Value: `"high"`
* Parameter: `reasoningSummary` → Value: `"auto"`
**Gemini - Standard Reasoning:**
* Parameter: `thinkingConfig` → Value: `{"thinkingBudget": 5000, "includeThoughts": true}`
**Gemini - Extended Reasoning:**
* Parameter: `thinkingConfig` → Value: `{"thinkingBudget": 8000, "includeThoughts": true}`
## When to Use Reasoning Models
Reasoning models work best for tasks that benefit from step-by-step thinking:
**✅ Best for:**
* Complex problem-solving and analysis
* Mathematical calculations and proofs
* Code review and debugging
* Strategic planning requiring multiple steps
* Tasks where showing work builds user trust
**⚠️ Not ideal for:**
* Simple Q\&A or fact retrieval
* Real-time chat requiring instant responses
* High-volume, cost-sensitive applications
## Displaying Reasoning in Your App
Tambo components automatically handle reasoning display - no additional code required.
### Built-in Support
When you add Tambo components from the CLI, reasoning support is included out-of-the-box:
```bash
npx tambo add message
```
These components include the `ReasoningInfo` sub-component that:
* **Auto-displays** reasoning in a collapsible dropdown
* **Shows thinking progress** with step counts during streaming
* **Auto-collapses** when the final response arrives
* **Auto-scrolls** to follow reasoning as it streams
If you're using Tambo's pre-built components (message, thread-content,
message-thread-full, etc.), reasoning display is already built-in. Just
configure reasoning parameters in your dashboard and it works automatically.
### Custom Implementation
If building custom components, reasoning is available in the `ThreadMessage` type:
```typescript
interface ThreadMessage {
id: string;
content: string;
role: "user" | "assistant";
reasoning?: string[]; // Array of reasoning strings
// ... other fields
}
```
Access it like any other message property:
```tsx
{
message.reasoning?.map((step, index) => (
Step {index + 1}: {step}
));
}
```
## Best Practices
### Performance Optimization
**Balance reasoning effort with cost and latency in your dashboard configuration:**
**Development Environment:**
* Use `reasoningEffort: "high"` for thorough testing
* Enable `reasoningSummary: "auto"` to debug thinking process
* Higher costs acceptable for development
**Production Environment:**
* Use `reasoningEffort: "medium"` for balanced performance
* Enable `reasoningSummary: "auto"` to maintain reasoning capabilities
* Optimize for cost-performance balance
**High-Volume Applications:**
* Use `reasoningEffort: "low"` or disable reasoning parameters
* Consider using non-reasoning models for simple queries
* Monitor token usage and costs closely
### Cost Considerations
Reasoning tokens are billed separately and typically cost more than standard tokens:
* **Monitor usage** - Track reasoning token consumption in your dashboard
* **Optimize effort** - Use lower reasoning effort when appropriate
* **Token budgets** - For Gemini, set appropriate `thinkingBudget` values based on task complexity
## Troubleshooting
**Reasoning not appearing in responses?**
* Verify you're using a supported model (GPT-5, GPT-5 Mini, GPT-5 Nano, O3, or Gemini 2.5)
* Check your dashboard settings under **LLM Providers** → **Custom LLM Parameters**
* For OpenAI: Ensure `reasoningSummary` is added and set to `"auto"`
* For Gemini: Ensure `thinkingConfig` has `includeThoughts: true`
* Click **Save Settings** after making changes
**Reasoning tokens consuming too many resources?**
* In your dashboard settings, lower `reasoningEffort` from `"high"` to `"medium"` or `"low"`
* For Gemini models, reduce the `thinkingBudget` value in `thinkingConfig`
* Create separate projects with different configurations for simple vs. complex queries
* Monitor token usage in your Tambo Cloud usage dashboard
* Consider using non-reasoning models for high-volume, simple tasks
## Additional Resources
Reasoning parameters are part of Tambo's custom LLM parameter system. For
information on configuring other model parameters, see [Custom LLM
Parameters](/models/custom-llm-parameters).
* **OpenAI Reasoning Models**: [OpenAI Documentation](https://platform.openai.com/docs/guides/reasoning)
* **Gemini Thinking Config**: [Google AI Documentation](https://ai.google.dev/gemini-api/docs/thinking)
* **Tambo Cloud GitHub**: [View Implementation](https://github.com/tambo-ai/tambo-cloud)
# Tambo MCP Server
URL: /tambo-mcp-server
import { Tabs, Tab } from "fumadocs-ui/components/tabs";
Tambo's MCP server provides tools for an LLM to retrieve information from our documentation about how to use Tambo. Add our MCP server to your IDE to give your coding assistant knowledge of how to use Tambo.
## Quick Setup
The fastest way to add tambo MCP to Cursor is by clicking the "Add to IDE" button on our homepage and selecting your preferred IDE. For other IDEs or manual setup, follow the instructions below.
## Setup Instructions
1. Click the install button on the homepage, or go directly to your global MCP configuration file at `~/.cursor/mcp.json`.
2. Add the following configuration:
```bash
"mcpServers": {
"tambo": {
"url": "https://mcp.tambo.co/mcp"
},
}
```
4. Save the file
5. Restart Cursor
**Claude Desktop**
1. Open Claude Desktop
2. Go to Settings (from the Claude menu) → Developer → Edit Config
3. Add the following configuration:
```bash
"mcpServers": {
"tambo": {
"url": "https://mcp.tambo.co/mcp"
},
}
```
4. Save the file
5. Restart Claude Desktop
**Claude Code**
```bash
claude mcp add --transport sse tambo-server https://mcp.tambo.co/mcp
```
1. Go to your global MCP configuration file at `.vscode/mcp.json`.
2. Add the following configuration:
```bash
"servers": {
"tambo": {
"type": "http",
"url": "https://mcp.tambo.co/mcp"
}
}
```
4. Save the file
5. Restart VSCode
6. Alternatively, run the **MCP: Add Server** command from the Command Palette, choose the type of MCP server to add and provide the server information. Next, select Workspace Settings to create the `.vscode/mcp.json` file in your workspace if it doesn't already exist
1. Click the hammer icon (🔨) in Cascade
2. Click Configure to open \~/.codeium/windsurf/mcp\_config.json
3. Add the following configuration:
```bash
"mcpServers": {
"tambo": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.tambo.co/mcp"]
},
}
```
4. Save the file
5. Click Refresh (🔄) in the MCP toolbar
1. Open settings with `Cmd + ,`
2. Add the following configuration:
```json
{
"context_servers": {
"tambo": {
"command": {
"path": "npx",
"args": ["-y", "mcp-remote", "https://mcp.tambo.co/mcp"],
"env": {}
},
"settings": {}
}
}
}
```
## Verification
After setting up the MCP server, you can verify it's working:
1. Open a new chat or file
2. Try asking the AI assistant about tambo
3. Check the IDE's output/console for any error messages
# add
URL: /cli/commands/add
`npx tambo add `
Adds a component hooked up to Tambo to your app.
This command will install the component file directly into the `/components/tambo` directory of your app so you can easily customize the behavior and styles. It will also install the components' dependencies and update your styles.
**Available Components:**
* `message-thread-full` - Full-screen chat interface with history and typing indicators
* `message-thread-panel` - Split-view chat with integrated workspace
* `message-thread-collapsible` - Collapsible chat for sidebars
* `control-bar` - Spotlight-style command palette
* `form` - AI-powered form components
* `graph` - Interactive graph visualization
* `canvas-space` - Canvas workspace for visual AI interactions
* `input-fields` - Smart input field components
**Examples:**
```bash
# Add a single component
npx tambo add form
# Add multiple components
npx tambo add form graph canvas-space
# Add to custom directory
npx tambo add form --prefix=src/components/ui
# Skip confirmation prompts
npx tambo add form --yes
# Use legacy peer deps (for dependency conflicts)
npx tambo add form --legacy-peer-deps
```
## Automatic Configuration
The `add` command automatically configures your CSS and Tailwind setup based on your project's Tailwind CSS version:
* **Detects your Tailwind version** (v3 or v4) automatically
* **Updates your `globals.css`** with required CSS variables
* **Updates your `tailwind.config.ts`** (v3 only) with basic configuration
* **Preserves your existing styles** and configuration
The CLI preserves your existing configuration and only adds what's needed for
Tambo components to work. Your custom styles and colors won't be overridden.
For detailed information about what gets configured or for manual setup, see:
import { Card, Cards } from "fumadocs-ui/components/card";
Complete guide to CSS variables and Tailwind configuration changes
# create-app
URL: /cli/commands/create-app
`npx tambo create-app [directory]` or `npm create tambo-app my-app`
Creates a new Tambo app from a template. Choose from pre-built templates to get started quickly with different use cases.
**Available Templates:**
* `standard` - Tambo + Tools + MCP - general purpose AI app template with MCP integration
* `analytics` - Generative UI analytics template with drag-and-drop canvas and data visualization
* More templates coming soon!
**Examples:**
```bash
# Create app with interactive prompts
npx tambo create-app
# Create in current directory
npx tambo create-app .
# Create with specific template
npx tambo create-app my-app --template=mcp
# Initialize git repository automatically
npx tambo create-app my-app --init-git
# Use legacy peer deps
npx tambo create-app my-app --legacy-peer-deps
```
**Manual Setup After Creating:**
```bash
cd my-app
npx tambo init # Complete setup with API key
npm run dev # Start development server
```
# full-send
URL: /cli/commands/full-send
`npx tambo full-send`
For instant project setup, performs the same project setup steps as `init`, and also installs a few useful components.
**What it does:**
1. Sets up authentication and API key
2. Creates the `lib/tambo.ts` configuration file
3. Prompts you to select starter components to install
4. Installs selected components and their dependencies
5. Provides setup instructions with code snippets
**Examples:**
```bash
# Full setup with component selection
npx tambo full-send
# Skip prompts and use defaults
npx tambo full-send --yes
# Use legacy peer deps
npx tambo full-send --legacy-peer-deps
```
**Manual Provider Setup:**
After running `full-send`, add the TamboProvider to your layout file:
```tsx title="app/layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { components } from "../lib/tambo";
import { MessageThreadFull } from "@/components/tambo/message-thread-full";
export default function Layout({ children }) {
return (
{children}
);
}
```
## Manual Styling Setup
The `full-send` command automatically configures your CSS and Tailwind based on your version. If you need to manually verify or update the configuration:
### Tailwind CSS v3 Configuration
**Check your `globals.css` file includes:**
```css title="app/globals.css"
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Tailwind CSS Variables customized with Tambo colors */
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 235 12% 21%;
--primary-foreground: 0 0% 98%;
--secondary: 218 11% 46%;
--secondary-foreground: 0 0% 100%;
--muted: 217 14% 90%;
--muted-foreground: 217 14% 68%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--border: 207 22% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 30 80% 54.9%;
--chart-2: 339.8 74.8% 54.9%;
--chart-3: 219.9 70.2% 50%;
--chart-4: 160 60% 45.1%;
--chart-5: 280 64.7% 60%;
/* Tambo-specific variables for component layouts */
--radius: 0.5rem;
--container: 210 29% 97%;
--backdrop: 210 88% 14% / 0.25;
--muted-backdrop: 210 88% 14% / 0.1;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
.dark {
/* Dark mode variables */
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 30 80% 54.9%;
--chart-2: 339.8 74.8% 54.9%;
--chart-3: 219.9 70.2% 50%;
--chart-4: 160 60% 45.1%;
--chart-5: 280 64.7% 60%;
/* Tambo-specific variables for component layouts */
--radius: 0.5rem;
--container: 210 29% 97%;
--backdrop: 210 88% 14% / 0.25;
--muted-backdrop: 210 88% 14% / 0.1;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
}
```
**Verify your `tailwind.config.ts` includes:**
```tsx title="tailwind.config.ts"
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: "class",
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
// ... your existing theme configuration
};
export default config;
```
### Tailwind CSS v4 Configuration
**For v4, check your `globals.css` includes:**
```css title="app/globals.css"
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
@theme inline {
/* Color mappings for Tailwind v4 */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
/* Tambo-specific color mappings */
--color-container: var(--container);
--color-backdrop: var(--backdrop);
--color-muted-backdrop: var(--muted-backdrop);
}
:root {
/* Tailwind v4 uses oklch() color format */
--background: oklch(1 0 0);
--foreground: oklch(0.14 0 285);
--card: oklch(1 0 0);
--card-foreground: oklch(0.14 0 285);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.14 0 285);
--primary: oklch(0.31 0.02 281);
--primary-foreground: oklch(0.98 0 0);
--secondary: oklch(0.54 0.027 261);
--secondary-foreground: oklch(1 0 0);
--muted: oklch(0.92 0 260);
--muted-foreground: oklch(0.73 0.022 260);
--accent: oklch(0.97 0 286);
--accent-foreground: oklch(0.21 0 286);
--destructive: oklch(0.64 0.2 25);
--border: oklch(0.93 0 242);
--input: oklch(0.92 0 286);
--ring: oklch(0.14 0 285);
--chart-1: oklch(0.72 0.15 60);
--chart-2: oklch(0.62 0.2 6);
--chart-3: oklch(0.53 0.2 262);
--chart-4: oklch(0.7 0.13 165);
--chart-5: oklch(0.62 0.2 313);
/* Tambo-specific layout variables */
--container: oklch(0.98 0 247);
--backdrop: oklch(0.25 0.07 252 / 0.25);
--muted-backdrop: oklch(0.25 0.07 252 / 0.1);
--radius: 0.5rem;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
.dark {
/* Dark mode with oklch() colors */
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.72 0.15 60);
--chart-2: oklch(0.62 0.2 6);
--chart-3: oklch(0.53 0.2 262);
--chart-4: oklch(0.7 0.13 165);
--chart-5: oklch(0.62 0.2 313);
/* Tambo-specific layout variables */
--container: oklch(0.98 0 247);
--backdrop: oklch(0.25 0.07 252 / 0.25);
--muted-backdrop: oklch(0.25 0.07 252 / 0.1);
--radius: 0.5rem;
--panel-left-width: 500px;
--panel-right-width: 500px;
--sidebar-width: 3rem;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
font-family: Arial, Helvetica, sans-serif;
}
}
```
The `full-send` command detects your Tailwind version and applies the
appropriate CSS format automatically. You typically won't need to manually
configure these files unless you're customizing the default Tambo styling.
Tambo components require specific CSS variables to function properly. The
layout variables (`--panel-left-width`, `--panel-right-width`,
`--sidebar-width`) control component dimensions and should not be removed.
For detailed information about what gets configured or for manual setup, see:
import { Card, Cards } from "fumadocs-ui/components/card";
Complete guide to CSS variables and Tailwind configuration changes
# init
URL: /cli/commands/init
`npx tambo init`
Walks you through creating a Tambo project and getting your free API Key. If you don't already have an account, sign up for free to get your API key.
After obtaining your API key, you'll need to set it up in your project:
Create a `.env.local` file in the root of your project to store your Tambo API
key: `NEXT_PUBLIC_TAMBO_API_KEY=your_api_key_here` Replace
`your_api_key_here` with the actual API key you received during setup. This
file should not be committed to version control as it contains sensitive
information.
**Examples:**
```bash
# Interactive setup
npx tambo init
# Skip prompts and use defaults
npx tambo init --yes
# Use legacy peer deps
npx tambo init --legacy-peer-deps
```
# list
URL: /cli/commands/list
`npx tambo list`
Lists all installed Tambo components in your project, showing which components are in the current location (`/components/tambo`).
**Examples:**
```bash
# List all installed components
npx tambo list
# List components in custom directory
npx tambo list --prefix=src/components/ui
```
## Example Output
When you run `npx tambo list`, you'll see output similar to this:
```bash
$ npx tambo list
Found existing src/ directory
? Would you like to use the existing src/ directory for components? yes
ℹ Installed components:
In tambo/:
Tambo components:
- canvas-space
- control-bar
- form
- graph
- input-fields
- message-input
- message-suggestions
- message-thread-collapsible
- message-thread-full
- message-thread-panel
- message
- scrollable-message-container
- thread-content
- thread-dropdown
- thread-history
Tambo support components:
- markdown-components
- message-generation-stage
- suggestions-tooltip
- thread-container
Total: 19 component(s) (19 from Tambo, 0 custom)
```
### With Custom Directory
If you're using a custom directory with `--prefix`, the output shows that location:
```bash
$ npx tambo list --prefix=src/components/ui
ℹ Installed components:
In ui/:
Tambo components:
- message-thread-collapsible
- control-bar
- form
Tambo support components:
- markdown-components
Total: 4 component(s) (4 from Tambo, 0 custom)
```
### No Components Found
If no components are installed, you'll see:
```bash
$ npx tambo list
ℹ No Tambo components found in components/tambo/
Get started by installing components:
npx tambo add message-thread-full
npx tambo add form graph
```
## Understanding the Output
The `list` command organizes components into two categories:
### Tambo Components
These are the main UI components you can use in your application:
* **Chat Components**: `message-thread-*`, `message-*`, `thread-*`
* **Input Components**: `input-fields`, `form`, `control-bar`
* **Visualization**: `graph`, `canvas-space`
### Tambo Support Components
These are utility components that support the main Tambo components:
* **Markdown Support**: `markdown-components`
* **UI Helpers**: `message-generation-stage`, `suggestions-tooltip`
* **Containers**: `thread-container`, `scrollable-message-container`
The CLI automatically detects whether you're using a `src/` directory
structure and will prompt you about component location preferences. It
distinguishes between main Tambo components and supporting utility components.
The total count shows both Tambo components and any custom components you may
have added. This helps you track what's installed and identify any non-Tambo
components in your Tambo directory.
# migrate
URL: /cli/commands/migrate
`npx tambo migrate`
Migrates Tambo components from the legacy `components/ui` directory to the new `components/tambo` directory and updates import paths automatically.
This command addresses the change in Tambo CLI's installation approach - earlier versions installed components in `components/ui`, while current versions use the dedicated `components/tambo` directory for better organization.
**What it does:**
1. Moves component files from `ui/` to `tambo/` directory
2. Updates import paths in moved files
3. Preserves custom (non-Tambo) components in original location
4. Shows preview of changes before applying
**Examples:**
```bash
# Preview migration changes (recommended first)
npx tambo migrate --dry-run
# Perform migration with confirmation
npx tambo migrate
# Skip confirmation prompts
npx tambo migrate --yes
```
**Manual Migration Steps:**
If you prefer to migrate manually:
1. **Move Files**: Move all `.tsx` files from `/components/ui/` to `/components/tambo/`
2. **Update Imports**: Change all imports from `@/components/ui/` to `@/components/tambo/`
3. **Update Registry**: Update component registrations in `lib/tambo.ts`
4. **Test**: Verify all components still work correctly
# update
URL: /cli/commands/update
`npx tambo update ` or `npx tambo update installed`
Updates specific Tambo components or all installed components to their latest versions from the registry.
**Examples:**
```bash
# Update specific components
npx tambo update form graph
# Update ALL installed components at once
npx tambo update installed
# Update with custom directory
npx tambo update form --prefix=src/components/ui
# Skip confirmation prompts
npx tambo update installed --yes
```
## CSS Configuration Updates
When updating components, the CLI may also update your CSS variables and Tailwind configuration to ensure compatibility with the latest component versions.
Component updates may require new CSS variables or Tailwind configuration
changes. The CLI will detect your Tailwind version and apply the appropriate
format automatically.
For detailed information about what gets configured, see:
import { Card, Cards } from "fumadocs-ui/components/card";
Learn what changes are made to your globals.css and tailwind.config files
# upgrade
URL: /cli/commands/upgrade
`npx tambo upgrade`
Upgrades your entire tambo project including npm packages, components, and cursor rules to the latest versions.
**What it upgrades:**
1. **NPM Packages**: Updates known safe packages (UI components, utilities, and tambo dependencies)
2. **Components**: Updates all installed tambo components to latest versions
3. **Cursor Rules**: Updates AI coding assistant rules for better development experience
4. **Configuration**: Updates CSS variables and Tailwind configuration as needed
**Examples:**
```bash
# Interactive upgrade with prompts
npx tambo upgrade
# Auto-accept all changes
npx tambo upgrade --accept-all
# Upgrade with custom component directory
npx tambo upgrade --prefix=src/components/ui
# Use legacy peer deps
npx tambo upgrade --legacy-peer-deps
```
## Safe Package Updates
The upgrade command only updates packages that are known to be safe for automatic updates:
The CLI automatically filters to only show updates for UI components,
utilities, and tambo-specific packages. Core framework packages (like React,
Next.js, TypeScript) are excluded to prevent breaking changes.
**Safe packages include:**
* tambo packages (`@tambo-ai/react`, `@tambo-ai/typescript-sdk`)
* UI components (`@radix-ui/*`, `framer-motion`, `lucide-react`)
* Styling utilities (`tailwindcss`, `clsx`, `tailwind-merge`)
* Development tools (`eslint-config-next`, selected @types packages such as `@types/dompurify`)
## Configuration Updates
The upgrade command performs comprehensive updates to your project configuration:
### CSS & Tailwind Updates
The CLI will update your `globals.css` and `tailwind.config.ts` files to ensure they're compatible with the latest tambo components and your current Tailwind CSS version.
The upgrade process preserves your existing styles and configuration while
adding any new CSS variables required by updated components.
### What Gets Updated
* **CSS Variables**: New variables required by component updates
* **Tailwind Configuration**: Basic configuration for v3 projects
* **Component Dependencies**: Latest versions of all component dependencies
* **Package Versions**: Updates to known safe packages only
While the CLI creates automatic backups, consider committing your changes to
version control before running `upgrade` to ensure you can easily revert if
needed.
For detailed information about configuration changes, see:
import { Card, Cards } from "fumadocs-ui/components/card";
Complete guide to CSS variables and Tailwind configuration changes
# Configuration
URL: /concepts/additional-context/configuration
Context helpers are most commonly configured at the app root using `TamboProvider`. You provide a plain object where each key is the context name and each value is a function that returns the context value (or null/undefined to skip).
## Basic Configuration (recommended)
Configure prebuilt context helpers at the root:
```tsx
import { TamboProvider } from "@tambo-ai/react";
import {
currentTimeContextHelper,
currentPageContextHelper,
} from "@tambo-ai/react";
function AppRoot({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
**Notes:**
* Each helper is a function that returns a value to include, or null/undefined to skip.
* The object key becomes the context name sent to the model.
* Helpers can be synchronous or asynchronous.
## When NOT passing at the root: use the Context Helpers Provider
If you don’t want to pass `contextHelpers` at the root (or you need to scope helpers per page/layout), use `TamboContextHelpersProvider` where needed. This is useful for route-specific helpers or experiments without changing the root.
```tsx
import { TamboContextHelpersProvider } from "@tambo-ai/react";
import { currentPageContextHelper } from "@tambo-ai/react";
function Page() {
return (
{/* Components that send messages (e.g., MessageThreadFull, inputs, etc.) */}
);
}
```
**Notes:**
* This pattern is intended for cases where you are NOT passing `contextHelpers` at the root, or you want page-specific registration.
* You still need `TamboProvider` somewhere higher in your app to enable message sending (typically in your app root).
* If helpers with the same key are registered at different times, the most recently mounted registration takes effect while it’s mounted.
## Message Context Structure
When helpers resolve, they are merged into the message context keyed by your helper names:
```tsx
({
sessionId: "abc123",
userId: "user456",
}),
}}
>
```
Example merged context:
```json
{
"userTime": {
"timestamp": "2025-01-15T20:30:00.000Z"
},
"session": {
"sessionId": "abc123",
"userId": "user456"
}
}
```
# Context Attachments
URL: /concepts/additional-context/context-attachments
Context attachments enable you to display visual badges above the message input that automatically provide the AI with additional context. This helps the AI understand what the user is focused on and provide more relevant responses.
## Overview
Context attachments appear as removable badges above the message input. When present, they automatically send contextual information to the AI, helping it:
* Focus on specific files, components, or pages the user is viewing
* Understand which part of your application the user is working on
* Provide more targeted and relevant responses
* Enable visual feedback showing what context is active
The feature also supports custom suggestions, allowing you to override Tambo's auto-generated suggestions with your own contextual suggestions.
## Basic Usage
The `useTamboContextAttachment` hook provides methods to manage context attachments:
```tsx
import { useTamboContextAttachment } from "@tambo-ai/react";
function FileViewer({ file }) {
const { addContextAttachment, removeContextAttachment } =
useTamboContextAttachment();
const handleSelectFile = () => {
addContextAttachment({
name: file.name,
icon: ,
metadata: {
filePath: file.path,
type: file.type,
},
});
};
return ;
}
```
## Context Attachment Structure
Each context attachment has the following structure:
```tsx
interface ContextAttachment {
id: string; // Auto-generated unique identifier
name: string; // Display name shown in the badge
icon?: React.ReactNode; // Optional icon to display
metadata?: Record; // Additional data for the AI
}
```
## Hook API
The `useTamboContextAttachment` hook returns:
```tsx
const {
attachments, // Array of active context attachments
addContextAttachment, // Add a new context attachment
removeContextAttachment, // Remove by ID
clearContextAttachments, // Remove all attachments
customSuggestions, // Current custom suggestions
setCustomSuggestions, // Set custom suggestions
} = useTamboContextAttachment();
```
### Adding Context Attachments
Add a context attachment by calling `addContextAttachment` with a name and optional metadata:
```tsx
const { addContextAttachment } = useTamboContextAttachment();
// Simple text context
addContextAttachment({
name: "Dashboard Page",
});
// With icon and metadata
addContextAttachment({
name: "Button.tsx",
icon: ,
metadata: {
filePath: "/src/components/Button.tsx",
lineNumber: 42,
},
});
```
The `id` field is automatically generated. If you add a context with the same `name` as an existing one, it will be ignored to prevent duplicates.
### Removing Context Attachments
Remove a specific context attachment by its ID:
```tsx
const { removeContextAttachment } = useTamboContextAttachment();
removeContextAttachment(contextId);
```
Or clear all attachments at once:
```tsx
const { clearContextAttachments } = useTamboContextAttachment();
clearContextAttachments();
```
### Displaying Active Attachments
Display the current attachments as badges:
```tsx
const { attachments, removeContextAttachment } = useTamboContextAttachment();
return (
{attachments.map((attachment) => (
{attachment.icon}
{attachment.name}
))}
);
```
## Customizing Context Data
By default, context attachments send a standard structure to the AI. You can customize this by providing a `getContextHelperData` function to `TamboProvider`:
```tsx
import { TamboProvider } from "@tambo-ai/react";
({
selectedFile: {
name: context.name,
path: context.metadata?.filePath,
instruction: "Focus on this file when responding",
// Add any custom fields
language: context.metadata?.language,
lastModified: context.metadata?.lastModified,
},
})}
>
;
```
The function can be async to fetch additional data:
```tsx
{
const fileContent = await fetchFileContent(context.metadata?.filePath);
return {
selectedFile: {
name: context.name,
content: fileContent,
instruction: "Use this file content to help the user",
},
};
}}
>
```
## Custom Suggestions
Context attachments can be combined with custom suggestions to override Tambo's auto-generated suggestions:
```tsx
import { useTamboContextAttachment } from "@tambo-ai/react";
function ComponentSelector({ component }) {
const { addContextAttachment, setCustomSuggestions } =
useTamboContextAttachment();
const handleSelectComponent = () => {
addContextAttachment({
name: component.name,
metadata: { componentId: component.id },
});
setCustomSuggestions([
{
id: "1",
title: "Edit this component",
detailedSuggestion: `Modify the ${component.name} component`,
messageId: "",
},
{
id: "2",
title: "Add a feature",
detailedSuggestion: `Add a new feature to ${component.name}`,
messageId: "",
},
]);
};
return ;
}
```
To clear custom suggestions and return to auto-generated ones:
```tsx
setCustomSuggestions(null);
```
## Complete Example
Here's a complete example showing context attachments in a file browser:
```tsx
import { useTamboContextAttachment } from "@tambo-ai/react";
import { FileIcon, XIcon } from "lucide-react";
function FileBrowser({ files }) {
const {
attachments,
addContextAttachment,
removeContextAttachment,
setCustomSuggestions,
} = useTamboContextAttachment();
const handleFileClick = (file) => {
addContextAttachment({
name: file.name,
icon: ,
metadata: {
filePath: file.path,
language: file.language,
},
});
setCustomSuggestions([
{
id: "1",
title: "Explain this file",
detailedSuggestion: `Explain what ${file.name} does`,
messageId: "",
},
{
id: "2",
title: "Suggest improvements",
detailedSuggestion: `Suggest improvements to ${file.name}`,
messageId: "",
},
]);
};
return (
{/* Active Context Badges */}
{attachments.length > 0 && (
{attachments.map((attachment) => (
{attachment.icon}
{attachment.name}
))}
)}
{/* File List */}
{files.map((file) => (
))}
);
}
```
## How It Works
When you add a context attachment:
1. The attachment is added to the `attachments` array and displayed as a badge
2. A context helper is automatically registered with `TamboContextHelpersProvider`
3. The context data is sent along with every message until the attachment is removed
4. When removed, the context helper is automatically unregistered
This automatic synchronization means you don't need to manually manage context helpers—just add and remove attachments as needed.
## Integration with Existing Context
Context attachments work alongside other context helpers:
* **Prebuilt helpers** like `currentTimeContextHelper` continue to work
* **Custom helpers** from `contextHelpers` prop are still sent
* **Manual context** via `additionalContext` is merged with attachment context
* All context sources are combined when sending messages
## Use Cases
Context attachments are particularly useful for:
* **Code editors**: Focus on specific files or functions
* **Dashboards**: Indicate which widget or section is active
* **Forms**: Show which field or step is being worked on
* **Multi-page apps**: Display the current page or route
* **Component libraries**: Highlight selected components
# Custom Context Helpers
URL: /concepts/additional-context/custom-helpers
Custom context helpers let you automatically include application-specific data with every message. A helper is just a function that returns a value (or null/undefined to skip). The object key you provide becomes the context name in messages.
## Defining Helpers
You can define helpers as simple functions:
```tsx
// Reusable helpers
const sessionHelper = async () => ({
sessionId: getSessionId(),
startTime: getSessionStartTime(),
user: await getCurrentUser(),
});
const environmentHelper = async () => ({
env: process.env.NODE_ENV,
version: process.env.NEXT_PUBLIC_APP_VERSION,
feature_flags: await getFeatureFlags(),
});
// Use in TamboProvider
;
```
## Inline Custom Helpers
Define helpers inline where you configure the provider:
```tsx
({
userAgent: navigator.userAgent,
platform: navigator.platform,
screenResolution: {
width: window.screen.width,
height: window.screen.height,
},
}),
}}
/>
```
## Overriding Prebuilt Helpers
Replace a prebuilt helper by using the same key:
```tsx
({
formattedTime: new Date().toLocaleString("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
}),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
}),
}}
/>
```
## Async vs Sync
Helpers can be synchronous or asynchronous:
```tsx
// Synchronous
const syncHelper = () => ({
theme: getTheme(),
language: getLanguage(),
});
// Asynchronous
const asyncHelper = async () => ({
user: await fetchUser(),
permissions: await fetchPermissions(),
});
```
Tip: Return null/undefined from a helper to skip including it in a particular environment or condition (e.g., return null for browser-only helpers on the server).
# Dynamic Control
URL: /concepts/additional-context/dynamic-control
Context helpers can be dynamically managed using the `useTamboContextHelpers` hook.
## Available Functions
```tsx
import { useTamboContextHelpers } from "@tambo-ai/react";
const {
getContextHelpers, // Get the current map of helpers
addContextHelper, // Add or replace a helper
removeContextHelper, // Remove a helper
} = useTamboContextHelpers();
```
**Note:**
* Helpers are just functions; to “disable” a helper, replace it with a function that returns null.
* You can also configure helpers declaratively using `TamboContextHelpersProvider`; both approaches write to the same global registry.
## Add/Replace Helpers at Runtime
Useful when context depends on user actions or app state:
```tsx
function ProjectContextController({ projectId }: { projectId: string }) {
const { addContextHelper, removeContextHelper } = useTamboContextHelpers();
useEffect(() => {
if (!projectId) return;
addContextHelper("currentProject", async () => ({
projectId,
projectName: await getProjectName(projectId),
projectData: await getProjectData(projectId),
}));
return () => {
removeContextHelper("currentProject");
};
}, [projectId]);
return null;
}
```
## Removing Helpers
```tsx
const { removeContextHelper } = useTamboContextHelpers();
// User logs out
removeContextHelper("session");
```
## Building a Settings UI
`getContextHelpers` returns the current map of helper functions keyed by name.
You can build toggles by swapping in a real function or a no-op function that returns null:
```tsx
function ContextSettings() {
const { getContextHelpers, addContextHelper } = useTamboContextHelpers();
const helpers = getContextHelpers();
return (
Privacy Settings
{Object.keys(helpers).map((key) => (
{key}
))}
);
}
```
## Register helpers from multiple pages using the Provider
You can also register helpers declaratively at the page or layout level with `TamboContextHelpersProvider`. Helpers are registered when the provider mounts and are active only while that provider remains mounted (for example, while you’re on that page/layout). If the same key is registered multiple times at different times, the most recently mounted provider takes effect for as long as it’s mounted.
**App layout (optional global helpers):**
```tsx title="app/layout.tsx"
import { TamboProvider, TamboContextHelpersProvider } from "@tambo-ai/react";
import { currentTimeContextHelper } from "@tambo-ai/react";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
);
}
```
**Page A (adds a page-specific helper):**
```tsx title="app/dashboard/page.tsx"
"use client";
import { TamboContextHelpersProvider } from "@tambo-ai/react";
import { currentPageContextHelper } from "@tambo-ai/react";
import { MessageThreadFull } from "@/components/tambo/message-thread-full";
export default function DashboardPage() {
return (
);
}
```
**Page B (adds a different helper or overrides a key):**
```tsx title="app/settings/page.tsx"
"use client";
import { TamboContextHelpersProvider } from "@tambo-ai/react";
const getUserSettingsContext = async () => ({
theme: "dark",
notifications: true,
});
export default function SettingsPage() {
return (
({ url: "/settings", title: "Settings" }),
}}
>
{/* Components that send messages */}
);
}
```
* If you only want a helper active while a page is mounted, register it in that page’s provider (like above), or use the hook with add/remove in a `useEffect` with cleanup.
* Consider namespacing keys if multiple parts of the app might register the same context (e.g., `dashboard:userPage`).
## What Happens Behind the Scenes
When you send a message, Tambo automatically:
1. Calls all configured helper functions.
2. Filters out null/undefined results.
3. Merges the results with any manual `additionalContext`.
4. Sends everything with the message.
# Examples
URL: /concepts/additional-context/examples
Practical examples of context helpers for common use cases.
## E-commerce Context
```tsx
const cartHelper = async () => ({
itemCount: getCartItemCount(),
totalValue: getCartTotal(),
items: getCartItems().map((item) => ({
id: item.id,
name: item.name,
price: item.price,
})),
});
const preferencesHelper = async () => ({
currency: getUserCurrency(),
shippingAddress: await getUserShippingAddress(),
paymentMethods: (await getSavedPaymentMethods()).length,
});
// Register globally with TamboProvider
;
// Or register locally per page/layout with TamboContextHelpersProvider
;
```
## Analytics Context
```tsx
const analyticsHelper = async () => ({
sessionDuration: getSessionDuration(),
pageViews: getPageViewCount(),
referrer: document.referrer,
utm_params: getUTMParameters(),
userSegment: await getUserSegment(),
});
// Local registration example
;
```
## Location-Based Context
```tsx
const locationHelper = async () => {
try {
const position: GeolocationPosition = await new Promise(
(resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
},
);
return {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
};
} catch {
return { error: "Location unavailable" };
}
};
// Register only where geolocation is needed
;
```
## Authentication Context
```tsx
const authHelper = async () => {
const user = await getCurrentUser();
return {
isAuthenticated: !!user,
userId: user?.id,
role: user?.role,
permissions: user?.permissions || [],
organizationId: user?.organizationId,
};
};
// Either global or page-scoped
;
```
## Feature Flags Context
```tsx
const featureHelper = async () => ({
flags: await getFeatureFlags(),
experiments: await getActiveExperiments(),
userCohort: getUserCohort(),
});
;
```
## Multi-tenant Context
```tsx
const tenantHelper = async () => {
const tenant = await getCurrentTenant();
return {
tenantId: tenant.id,
tenantName: tenant.name,
subscription: tenant.subscriptionTier,
customizations: tenant.customSettings,
};
};
;
```
## Complete Example
```tsx
import { TamboProvider, TamboContextHelpersProvider } from "@tambo-ai/react";
import {
currentTimeContextHelper,
currentPageContextHelper,
} from "@tambo-ai/react";
const sessionHelper = async () => ({
sessionId: getSessionId(),
startTime: getSessionStartTime(),
duration: getSessionDuration(),
});
const userHelper = async () => ({
id: getUserId(),
role: getUserRole(),
preferences: await getUserPreferences(),
});
const appHelper = () => ({
version: APP_VERSION,
environment: process.env.NODE_ENV,
buildTime: BUILD_TIMESTAMP,
});
function App() {
return (
{/* Page-level overrides/additions */}
({ ...customSessionForThisPage })
}}
>
);
}
```
# Additional Context
URL: /concepts/additional-context
Additional Context helps you add information that will be sent along with the user's message. You can include prebuilt helpers, create custom helpers, add visual context attachments, or send per-message context to provide more relevant AI responses.
## Overview
When configured, context helpers automatically add metadata to messages without extra code in your components. This contextual information helps the AI:
* Provide time-aware responses (e.g., "Good morning" at the right time)
* Understand what page or section the user is currently viewing
* Give location-specific recommendations
* Provide more relevant help based on the user's current context
* Access custom business logic and application state
* Focus on specific components, files, or sections through visual badges
## How to Add Context
There are four ways to add additional context to messages:
### 1. Prebuilt Context Helpers
Tambo provides prebuilt helper functions that can be passed to the provider:
```tsx
import { TamboProvider } from "@tambo-ai/react";
import {
currentTimeContextHelper,
currentPageContextHelper,
} from "@tambo-ai/react";
;
```
You can also wrap parts of your app with `TamboContextHelpersProvider` to declare helpers in-place. This is useful when you don't want to set `contextHelpers` at the root, or when you need page/layout-specific helpers that only apply while that page is mounted:
```tsx
import { TamboContextHelpersProvider } from "@tambo-ai/react";
import { currentPageContextHelper } from "@tambo-ai/react";
export default function DashboardPage() {
return (
{/* Components that send messages (e.g., MessageThreadFull, inputs, etc.) */}
);
}
```
This is equivalent to passing the same helpers via `TamboProvider`’s `contextHelpers` prop. If the same key is registered in multiple places at different times, the most recently mounted registration takes effect while it’s mounted.
### 2. Custom Context Helpers
Create your own helpers to automatically include application-specific data. Each helper is a function that returns a value, or null to skip.
```tsx
// Define helpers outside components
const profileHelper = async () => ({
userId: getCurrentUserId(),
role: getUserRole(),
preferences: await getUserPreferences(),
});
({
theme: getTheme(),
version: getAppVersion(),
}),
}}
/>;
```
### 3. Context Attachments
Add visual context badges that appear above the message input using the `useTamboContextAttachment` hook:
```tsx
import { useTamboContextAttachment } from "@tambo-ai/react";
const { addContextAttachment } = useTamboContextAttachment();
addContextAttachment({
name: "Button.tsx",
icon: ,
metadata: { filePath: "/src/components/Button.tsx" },
});
```
Context attachments provide both visual feedback and automatic context to the AI. Learn more in [Context Attachments](/concepts/additional-context/context-attachments).
### 4. Manual Context via Message Functions
Add custom context on a per-message basis using the `additionalContext` parameter:
```tsx
// Using useTamboThreadInput hook
const { submit } = useTamboThreadInput();
await submit({
streamResponse: true,
additionalContext: {
customData: "specific to this message",
userPreferences: { theme: "dark" },
},
});
```
All four methods can be used together—context from helpers, attachments, and manual context are merged when sending messages.
## When to Use Each Method
| Method | Use When | Example |
| ------------------- | -------------------------------------------------------- | ------------------------------------------------------ |
| Prebuilt Helpers | You need standard metadata like time or page info | Use `currentTimeContextHelper` for scheduling features |
| Custom Helpers | You have app-wide context that should always be included | User profile, app settings, feature flags |
| Context Attachments | User selects something to focus on with visual feedback | Selected files, components, or sections to work on |
| Manual Context | Context is specific to a particular interaction | Search filters, form data, temporary state |
## Available Prebuilt Context Helpers (On by Default)
These helpers are automatically enabled when using the corresponding provider. You can disable them by overriding them with a function that returns null.
### currentTimeContextHelper
Provides the user's current time and timezone information.
Example result:
```json
{
"timestamp": "2025-01-15T20:30:00.000Z"
}
```
### currentPageContextHelper
Provides information about the current page or route.
Example result:
```json
{
"url": "/dashboard/analytics",
"title": "Analytics Dashboard"
}
```
### currentInteractablesContextHelper
**Automatically enabled** when using `TamboInteractableProvider`. Provides information about all interactable components currently on the page that the AI can read and modify.
We found this to be the most effective way to provide the AI with information about the current page and the components on the page. It's easy to disable or customize as needed.
Example result:
```json
{
"description": "These are the interactable components currently visible on the page...",
"components": [
{
"id": "Note-abc123",
"componentName": "Note",
"description": "A simple note",
"props": { "title": "My Note", "content": "Hello world" },
"propsSchema": "Available - use component-specific update tools"
}
]
}
```
See [Interactable Components](/concepts/components/interactable-components) for details on customizing or disabling this helper.
**Notes:**
* Helpers are plain functions. Return a value to include it, or null/undefined to skip.
* The helper key becomes the context name in messages.
# Image Attachments
URL: /concepts/message-threads/image-attachments
### Image attachments
Users can attach images to their messages through file picker, drag & drop, or clipboard paste.
## Using MessageInput with images
The `MessageInput` component provides built-in image attachment functionality:
```tsx
import {
MessageInput,
MessageInputTextarea,
MessageInputFileButton,
MessageInputSubmitButton,
MessageInputStagedImages,
MessageInputToolbar,
} from "@tambo-ai/react";
function ChatInterface() {
return (
);
}
```
## Image input methods
* **File picker**: Click the file button to select images
* **Drag & drop**: Drag images directly onto the input area
* **Clipboard paste**: Use Ctrl+V (or Cmd+V) to paste images
## Custom image handling
Use the `useMessageImages` hook for custom image management:
```tsx
import { useMessageImages } from "@tambo-ai/react";
function CustomImageHandler() {
const { images, addImages, removeImage, clearImages } = useMessageImages();
const handleFileUpload = async (files: FileList) => {
await addImages(Array.from(files));
};
return (
{images.map((image) => (
))}
);
}
```
Only image files are accepted. Non-image files will be rejected with an error.
# Message Threads
URL: /concepts/message-threads
Message threads are the core building block for creating interactive AI experiences with Tambo. They provide a structured way to manage conversations between users and Tambo, with hooks that make it easy to interact with thread state, send messages, and display responses. Each thread maintains a history of messages and can include both text content and rendered components, allowing for rich conversational interfaces where users can see both chat history and dynamic UI components.
```tsx
const { thread } = useTambo()
const { value, setValue, submit } = useTamboThreadInput();
//send a message to Tambo under the current thread
const userMessage = "What is the weather like today?"
setValue(userMessage);
await submit({
streamResponse: true, // We recommend streaming the response to the user
});
...
//render the thread's messages, which will include responses from Tambo
thread.messages.map((message) => (
Sent by: {message.role}
message text: {message.content[0]?.text}
component: {message.renderedComponent}
))
```
# Initial Messages
URL: /concepts/message-threads/initial-messages
The Initial Messages feature allows you to set up a "conversation state" when creating new threads in Tambo. This is useful for providing system instructions, context, or welcome messages that should be present at the start of every new conversation.
## React SDK Usage
### Basic Example
```tsx
import { TamboProvider, TamboThreadMessage } from "@tambo-ai/react";
const initialMessages: TamboThreadMessage[] = [
{
id: "system-message",
role: "system",
content: [
{
type: "text",
text: "You are a helpful assistant specialized in customer support.",
},
],
createdAt: new Date().toISOString(),
componentState: {},
},
{
id: "welcome-message",
role: "assistant",
content: [{ type: "text", text: "Hello! How can I help you today?" }],
createdAt: new Date().toISOString(),
componentState: {},
},
];
function App() {
return (
);
}
```
### With Components
You can also include components in initial messages:
```tsx
const initialMessagesWithComponent: TamboThreadMessage[] = [
{
id: "system-message",
role: "system",
content: [{ type: "text", text: "You are a helpful assistant." }],
createdAt: new Date().toISOString(),
componentState: {},
},
{
id: "welcome-component",
role: "assistant",
content: [{ type: "text", text: "Welcome! Here's a quick overview:" }],
createdAt: new Date().toISOString(),
componentState: {},
component: {
componentName: "WelcomeCard",
props: {
title: "Welcome to Support",
description: "I can help you with your questions.",
},
},
},
];
```
## Behavior
### New Threads
* Initial messages are included when creating a **new** thread (placeholder thread)
* They are sent to the AI model as part of the conversation context
* They appear in the thread's message history
### Existing Threads
* Initial messages are **not** added to existing threads
* Only applies when `threadId` is undefined (new thread creation)
### Validation
The API validates initial messages to ensure:
* Each message has valid `content` and `role`
* Roles are one of: `system`, `user`, `assistant`
* Text content parts have the required `text` property
* Content arrays are not empty
## Interaction with Custom Instructions
Initial messages work alongside the "custom instructions" feature:
1. **Custom Instructions**: Set via the project settings dashboard, applied to all threads in the project
2. **Initial Messages**: Set programmatically via the SDK/API, applied per thread creation
Both are sent to the AI model, with custom instructions typically processed first, followed by initial messages, then the conversation history.
## Error Handling
Invalid initial messages will cause the thread creation to fail with a descriptive error:
```
Initial message at index 0 must have content
Initial message at index 1 has invalid role "invalid-role". Allowed roles are: system, user, assistant
Initial message at index 2, content part 0 with type 'text' must have text property
```
## Examples
### Customer Support Bot
```tsx
const supportMessages: TamboThreadMessage[] = [
{
role: "system",
content: [
{
type: "text",
text: "You are a customer support agent for TechCorp. Be helpful, professional, and always ask for the customer's order number if they have an issue.",
},
],
// ... other required fields
},
{
role: "assistant",
content: [
{
type: "text",
text: "Hi! I'm here to help with any questions about your TechCorp products. What can I assist you with today?",
},
],
// ... other required fields
},
];
```
### Educational Assistant
```tsx
const educationMessages: TamboThreadMessage[] = [
{
role: "system",
content: [
{
type: "text",
text: "You are an educational assistant. Always encourage learning, ask clarifying questions, and provide step-by-step explanations.",
},
],
// ... other required fields
},
{
role: "assistant",
content: [
{
type: "text",
text: "Welcome to your learning session! What topic would you like to explore today?",
},
],
// ... other required fields
},
];
```
# Sending Thread Messages
URL: /concepts/message-threads/sending-messages
### Sending thread messages
We recommend using the `useTamboThreadInput` hook to send messages to the thread.
```tsx
import { useTamboThreadInput } from "@tambo-ai/react";
const { value, setValue, submit } = useTamboThreadInput();
```
The `value` is the value of the message that you want to send.
The `setValue` is the function that you can use to update the value of the message.
To send a user's message to tambo, you can use the `submit` function:
You can also pass in an optional `options` object to the `submit` function.
```tsx
setValue("What is the weather like today?");
await submit({
streamResponse: true, // We recommend streaming the response to the user
});
```
import { Card, Cards } from "fumadocs-ui/components/card";
Learn more about streaming.
You can use `isPending` and `error` to show a loading state or an error state:
```tsx
if (isPending) {
return
Loading...
;
}
```
```tsx
if (error) {
return
Error: {error.message}
;
}
```
You can also use the `sendThreadMessage` function to send messages to the thread:
```tsx
const { sendThreadMessage } = useTamboThread();
await sendThreadMessage("What is the weather like today?", {
streamResponse: true,
});
```
### Adding Context to Messages
When sending messages, you can include additional context that will be sent along with the user's message. This is useful for providing extra information that tambo should consider when generating a response.
#### With `useTamboThreadInput`
```tsx
await submit({
streamResponse: true,
additionalContext: {
pageInfo: {
url: "/weather",
},
location: "San Francisco",
units: "fahrenheit",
},
});
```
#### With `sendThreadMessage`
```tsx
await sendThreadMessage("What is the weather like today?", {
streamResponse: true,
additionalContext: {
pageInfo: {
url: "/weather",
},
location: "San Francisco",
units: "fahrenheit",
},
});
```
The `additionalContext` parameter accepts a `Record` object, allowing you to pass any structured data as context.
#### Context Helpers
Tambo can automatically include context helpers with every message when they are configured. Configure helpers either:
* Globally on `TamboProvider` via the `contextHelpers` prop, or
* Locally on a page/layout using `TamboContextHelpersProvider` (active only while mounted)
**Prebuilt helpers include:**
* **currentTimeContextHelper**: Returns current timestamp
* **currentPageContextHelper**: Returns current page URL/title information
You can also define custom context helpers and register them either globally with `TamboProvider` or locally with `TamboContextHelpersProvider`. Any active helpers will be automatically merged into the message context. If the same key is registered in multiple places at different times, the most recently mounted registration takes precedence while it’s mounted.
Learn more about additionalContext.
Your manual `additionalContext` will be merged with any resolved helper results. For example, if the `currentTimeContextHelper` context helper is passed, the above code would result in a combined context like:
```json
{
"pageInfo": {
"url": "/weather"
},
"location": "San Francisco",
"units": "fahrenheit",
"userTime": {
"timestamp": "2025-01-15T20:30:00.000Z"
}
}
```
# Showing Responses
URL: /concepts/message-threads/showing-responses
### Showing thread messages and components
Since tambo keeps the thread state updated, all you need to do is show the thread's messages somewhere. If a message from tambo includes a component, it can be accessed with `message.renderedComponent`:
```tsx
const { thread } = useTamboThread()
...
thread.messages.map((message) => (
Sent by: {message.role}
message text: {message.content[0]?.text}
component: {message.renderedComponent}
))
```
For canvas-style UIs where you want to display only the most recent component, you can walk backwards through the messages to find the latest one with a `renderedComponent`:
```tsx
const latestComponent = thread.messages
.slice()
.reverse()
.find((message) => message.renderedComponent)?.renderedComponent;
...
{latestComponent}
```
This approach is useful for building interactive canvases, dashboards, or any UI where you want to show the most up-to-date component state without displaying the full conversation history.
# Switching Thread
URL: /concepts/message-threads/switching-thread
### Setting the current thread
By default, tambo will create a new thread for you and use that thread for the `thread` state value.
If you want to switch to another thread you can use the `switchCurrentThread` function:
```tsx
const { switchCurrentThread } = useTamboThread()
...
switchCurrentThread(threadId)
```
The `thread` state will be updated, and anywhere you are rendering the threads list of messages will now be showing the new thread's messages!
# Tracking Response Stage
URL: /concepts/message-threads/thread-status
You may want to show your users something unique during each stage of tambo's response generation process, rather than a static 'loading' message for the entire duration.
To enable this, each thread has a `generationStage` property that can be used to track the progress of the response generation process.
You can expose the generation stage ( or a value you derive from it ) to your users so they know what their AI assistant is doing.
```typescript title="generation-stage.tsx"
const { generationStage } = useTamboThread();
...
if (generationStage === GenerationStage.CHOOSING_COMPONENT) {
return
Tambo is choosing the best component to use...
;
}
if (generationStage === GenerationStage.FETCHING_CONTEXT) {
return
Tambo is fetching additional data to hydrate the component...
;
}
...
```
### Stages
A thread's generation stage is an enum with the following possible values:
```typescript title="generation stages"
enum GenerationStage {
IDLE = "IDLE", // The thread is idle ( initial stage )
CHOOSING_COMPONENT = "CHOOSING_COMPONENT", // Tambo is choosing the best component to use based on the user's message
FETCHING_CONTEXT = "FETCHING_CONTEXT", // Tambo is fetching additional data to hydrate a chosen component
HYDRATING_COMPONENT = "HYDRATING_COMPONENT", // Tambo is hydrating the component
STREAMING_RESPONSE = "STREAMING_RESPONSE", // Tambo is streaming the response
COMPLETE = "COMPLETE", // Tambo has completed successfully
ERROR = "ERROR", // Tambo has encountered an error
CANCELLED = "CANCELLED", // The latest message generation was cancelled.
}
```
### Status Message
Instead of determining a custom message for each stage, you can use the `statusMessage` property, which is a context-aware string that describes the current stage of the generation process, as generated by Tambo.
For example, if the generation was triggered by a user message asking about the weather in Tokyo, during the `CHOOSING_COMPONENT` stage the `statusMessage` might be something like `"Looking for a component to help with your request about the weather in Tokyo"`.
Use this along with the `isProcessing` helper property which returns `true` if the `generationStage` is `CHOOSING_COMPONENT`, `FETCHING_CONTEXT`, `HYDRATING_COMPONENT`, or `STREAMING_RESPONSE`.
```typescript title="generation-stage.tsx"
const { statusMessage, isProcessing } = useTamboThread();
if (isProcessing) {
return
{statusMessage}
;
}
```
# Model Context Protocol (MCP)
URL: /concepts/model-context-protocol
Model Context Protocol (MCP) allows you to give Tambo access to tools defined by other people. Many popular services publish MCP servers, and Tambo provides a simple way to connect to them. This is a quick way to allow your users to conversationally interact with other services without needing to write any of the interaction code yourself.
## Connection Types
Tambo supports two ways to connect to MCP servers:
1. **[Server-side MCP](/concepts/model-context-protocol/providers/serverside-mcp-connection)** (recommended) - Configure MCP servers through the Tambo dashboard. Best for shared services, OAuth authentication, and optimal performance.
2. **[Client-side MCP](/concepts/model-context-protocol/providers/clientside-mcp-connection)** - Connect directly from the browser to MCP servers accessible to end users. Best for local servers and user-specific services.
## Quick Start
```tsx
```
# Component Streaming Status
URL: /concepts/streaming/component-streaming-status
The `useTamboStreamStatus` hook lets you track both overall and per-prop streaming status, so you can show loading or completed states for each prop as they arrive.
## How Does It Work?
Without per-prop tracking, you're forced to show nothing until all props are ready (poor UX) or show everything with default values (potentially confusing). This hook gives you granular control over what renders when.
```tsx title="basic-usage.tsx"
import { useTamboStreamStatus } from "@tambo-ai/react";
function Note({ title, content, createdAt }) {
const { streamStatus, propStatus } = useTamboStreamStatus();
// Wait for everything to complete
if (!streamStatus.isSuccess) return ;
return (
{title}
{content}
{propStatus["createdAt"]?.isSuccess &&
{createdAt}
}
);
}
```
This guide builds on **[Streaming](/concepts/streaming)** and **[Response
Component Streaming](/concepts/streaming/response-component-streaming)**. We
recommend reading those first.
## Status Types
### Global Stream Status (`streamStatus`)
Tracks the overall state of the entire component:
| Field | Type | Description |
| -------------- | --------- | -------------------------------------------------------- |
| `isPending` | `boolean` | No tokens received yet, show initial loading state |
| `isStreaming` | `boolean` | Active data transmission (generation OR props streaming) |
| `isSuccess` | `boolean` | All props completed successfully |
| `isError` | `boolean` | Any fatal error occurred in generation or props |
| `streamError?` | `Error` | First error encountered during streaming |
### Per-Prop Status (`propStatus`)
Tracks individual prop streaming states:
| Field | Type | Description |
| ------------- | --------- | ------------------------------------------------- |
| `isPending` | `boolean` | No tokens received for this specific prop yet |
| `isStreaming` | `boolean` | Partial content received, prop is still updating |
| `isSuccess` | `boolean` | Prop finished streaming successfully |
| `error?` | `Error` | Error that occurred during streaming of this prop |
## Usage Patterns
### Wait for Everything to Complete
```tsx title="wait-for-all.tsx"
function ArticleCard({ title, content, author }) {
const { streamStatus } = useTamboStreamStatus();
if (streamStatus.isPending) return ;
if (streamStatus.isError)
return
Error: {streamStatus.streamError?.message}
;
if (!streamStatus.isSuccess) return ;
return (
{title}
{content}
By {author}
);
}
```
### Progressive Rendering
Show each prop as it becomes available:
```tsx title="progressive-rendering.tsx"
function DashboardWidget({ title, data, config }) {
const { propStatus } = useTamboStreamStatus();
return (
)}
);
}
```
## When to Use Component Streaming Status
**✅ Use When**
* Different props have different loading times
* You want progressive rendering
* You need fine-grained error handling
* You have complex UI that depends on multiple props
**❌ Don't Use When**
* You only have simple text content
* You're doing SSR/SSG (client-side only)
## Integration with Other Tambo Features
### With Stream Status Provider
```tsx title="with-provider.tsx"
import { TamboPropStreamProvider, useTamboStreamStatus } from "@tambo-ai/react";
function EnhancedComponent({ title, content, metadata }) {
const { streamStatus } = useTamboStreamStatus();
return (
{title}
{content}
);
}
```
The **[Stream Status
Provider](/concepts/streaming/tambo-prop-stream-provider)** provides
declarative streaming state management with built-in loading and complete
states for individual props.
### With Streaming Props Hook
```tsx title="with-streaming-props.tsx"
import { useTamboStreamingProps, useTamboStreamStatus } from "@tambo-ai/react";
function StatefulComponent({ streamedTitle, streamedContent }) {
const { propStatus } = useTamboStreamStatus();
const [state, setState] = useState({ title: "", content: "" });
useTamboStreamingProps(state, setState, {
title: streamedTitle,
content: streamedContent,
});
return (
{state.title}
{propStatus["content"]?.isSuccess &&
{state.content}
}
);
}
```
The **[Streaming Props into State](/concepts/streaming/streaming-props)**
feature allows you to automatically update component state as props stream in,
providing seamless integration with React state management.
# Streaming
URL: /concepts/streaming
Tambo allows you to show response messages and components in real-time as they are being generated, creating more responsive and engaging user experiences. Instead of waiting for the entire response to complete, Tambo handles updating the thread's final message as data streams in, allowing your users to see content appear progressively. This is especially useful for longer responses or when components need to be populated with data that arrives incrementally.
# Response Component Streaming
URL: /concepts/streaming/response-component-streaming
When you `message.renderedComponent` is called, it will return a component and it will update the props as the values stream in.
## How Does It Work?
Inside the provider the `thread.messages` array is updated with the `renderedComponent` property.
```tsx title="basic-streaming.tsx"
import { useTambo } from "@tambo-ai/react";
function ChatInterface() {
const { thread } = useTambo();
return (
{thread.messages.map((message, index) => (
{/* Updates in real-time as props stream in */}
{message.renderedComponent}
))}
);
}
```
## Why Use Response Component Streaming?
* **Real-time feedback** - Content appears as it's generated
* **Better perceived performance** - Immediate visual feedback reduces wait times
* **Progressive disclosure** - Content appears in logical order
* **Enhanced engagement** - Dynamic updates keep users focused
## Prop Streaming Order
**Critical**: Props stream in the order you define them in your Zod schema. Define the most important props first:
```tsx title="schema-order-example.tsx"
import { z } from "zod";
// ✅ Good: Title streams first, then content
const ArticleSchema = z.object({
title: z.string(), // First in schema = streams first
content: z.string(), // Second in schema = streams second
author: z.string(), // Third in schema = streams third
});
// ❌ Bad: Content streams before title
const ArticleSchema = z.object({
content: z.string(), // First in schema = streams first
title: z.string(), // Second in schema = streams second
});
```
## Handling Streaming Props
Since Tambo renders components before all props are complete, your components must handle `undefined` values gracefully. Here are three approaches in order of complexity:
### 1. Default Values (Simplest)
```tsx title="default-values.tsx"
const ArticleCard = ({
title = "Loading title...",
content = "Loading content...",
author = "Loading author...",
}: {
title?: string;
content?: string;
author?: string;
}) => {
return (
);
};
```
**Important**: If your component uses objects with internal state (like form
controls, charts, or complex UI components), use `useEffect` or the **[Stream
Status Provider](/concepts/streaming/tambo-prop-stream-provider)** to properly
handle state updates as props stream in.
# Response Text Streaming
URL: /concepts/streaming/response-text-streaming
When you send a message to Tambo, Tambo will handle updating the thread's final message as data streams in rather than waiting for the entire response to complete.
Send a message to the current thread:
```tsx title="streaming.tsx"
import { useTambo } from "@tambo-ai/react";
const { sendThreadMessage } = useTambo();
await sendThreadMessage(inputValue);
//or
const { submit } = useTamboThreadInput();
await submit();
```
Render your thread messages like normal.
```tsx title="streaming.tsx"
import { useTambo } from "@tambo-ai/react";
const { thread } = useTambo();
...
{thread.messages.map((message, index) => (
{message.content[0].text}
))}
;
```
# Streaming Props into State
URL: /concepts/streaming/streaming-props
When working with AI-generated content in Tambo, you often need to update your component state when new content streams in. The `useTamboStreamingProps` hook simplifies this common pattern.
## Building on Previous Concepts
This guide combines two key Tambo concepts:
1. **[Streaming](/docs/concepts/streaming)** - The ability to receive AI-generated content in real-time as it's being created
2. **[Component State](/docs/concepts/component-state)** - Tambo's state management for tracking component state as context for AI
We recommend you read those first.
When using streaming, props arrive incrementally, and you need an efficient way to update your component state without complex useEffect dependencies. `useTamboStreamingProps` solves this exact problem.
## Why this matters
React was designed around discrete renders, but large language models deliver **continuous streams** of tokens.\
Trying to glue the two paradigms together with ad-hoc `useEffect` logic quickly leads to:
* Duplicate diffing code across components
* Oversized dependency arrays that are hard to reason about
* Extra re-renders that waste CPU and battery
`useTamboStreamingProps` centralises the diffing and batching so your component only re-renders when something **actually** changed.
**Real-world numbers:** while streaming at \~4 tokens / sec we measured **\~80 % fewer renders** and **\~65 % less main-thread time** compared with a naive `useEffect` that writes state on every chunk.
* **Stale closures** – forgetting to include the latest setter in the dependency array
* **Infinite loops** – updating state unconditionally inside an effect that depends on that same state
* **Out-of-order writes** – multiple async effects racing to write conflicting values
* **Performance hits from excessive renders** – diffing large objects on every chunk
* **Missed updates** – early-return logic that skips valid but falsy values
## Using `useTamboStreamingProps`
```tsx
import {
useTamboComponentState,
useTamboStreamingProps,
} from "@tambo-ai/react";
function MyComponent({ streamedTitle, streamedBody }) {
// 1. Set up your Tambo component state
const [state, setState] = useTamboComponentState("myState", {
title: "",
body: "",
});
// 2. Connect streaming props to your state
useTamboStreamingProps(state, setState, {
title: streamedTitle,
body: streamedBody,
});
// That's it! Your state will automatically update when props change
return (
{state?.title}
{state?.body}
);
}
```
## Benefits
* Eliminates repetitive useEffect code
* Automatically detects and applies changes
* Only updates when values actually change
* Works with any state shape
* Type-safe with TypeScript
## Without vs With
**Before (repetitive pattern):**
```tsx
useEffect(() => {
if (state) {
const shouldUpdateTitle = streamedTitle && streamedTitle !== state.title;
const shouldUpdateBody = streamedBody && streamedBody !== state.body;
if (shouldUpdateTitle || shouldUpdateBody) {
setState({
...state,
title: shouldUpdateTitle ? streamedTitle : state.title,
body: shouldUpdateBody ? streamedBody : state.body,
});
}
}
}, [streamedTitle, streamedBody]);
```
**After (clean and simple):**
```tsx
useTamboStreamingProps(state, setState, {
title: streamedTitle,
body: streamedBody,
});
```
Don't want to use Tambo's state management? You can use your own!
```tsx
const [state, setState] = useState({
title: "",
body: "",
});
useTamboStreamingProps(state, setState, {
title: streamedTitle,
body: streamedBody,
});
```
Works the same, but you don't get the benefits of Tambo's state management.
## When **not** to use `useTamboStreamingProps`
* The prop arrives **once** (e.g. static config fetched at build time).
* You need **custom reconciliation** that can't be expressed as a shallow merge.
* The incoming object is **huge** and diffing would dominate render time.
* You already debounce/queue updates **up-stream** (e.g. via a websocket buffer).
* You're on React **\< 18** and can't rely on concurrent rendering semantics.
## Glossary
* **dependency array** - the second argument to [`useEffect`](https://react.dev/reference/react/useEffect#dependencies) that tells React when the effect should re-run.
* **useEffect** - a React hook for performing side-effects after a component render. See the [official docs](https://react.dev/reference/react/useEffect) for details.
# Stream Status Provider
URL: /concepts/streaming/tambo-prop-stream-provider
The `TamboPropStreamProvider` uses a **compound component pattern** to manage streaming state. You wrap your content with the main provider and use its sub-components to define what renders at each streaming stage.
```tsx title="basic-usage.tsx"
import { TamboPropStreamProvider } from "@tambo-ai/react";
function WeatherCard({ location, temperature, condition }) {
return (
Loading weather...
{location}
{temperature}°C, {condition}
);
}
```
## Why Use the Stream Status Provider?
* **Declarative rendering** - Use compound components to define what shows in each state
* **Per-prop tracking** - Handle individual prop streaming (e.g., show title while body loads)
* **Enhanced UX** - Add highlighting, animations, or transitions during streaming
This guide builds on **[Streaming](/concepts/streaming)** and **[Component
Streaming Status](/concepts/streaming/component-streaming-status)**. We
recommend reading those first.
## API Reference
### Compound Components
**``** - Renders when the stream is pending (no data yet)
**``** - Renders when the stream is actively streaming data
**``** - Renders when data is successfully received and complete
### useTamboStream Hook
Access the stream context within the provider:
```tsx
const { data, streamStatus, getStatusForKey } = useTamboStream();
```
The **[useTamboStream](/concepts/streaming/use-tambo-stream)** hook provides
access to the stream context within the provider.
## Usage Examples
### Per-Prop Streaming
Track individual properties using the `streamKey` prop to show content progressively:
```tsx title="per-property-streaming.tsx"
function ArticleCard({ title, summary }) {
return (
Loading title...
{title}
Loading summary...
{summary}
);
}
```
### Error Handling and All States
Handle all possible streaming states including errors and empty states:
```tsx title="complete-error-handling.tsx"
function RobustComponent({ data }) {
const { streamStatus } = useTamboStreamStatus();
return (
AI is generating content...
No content generated yet
{data.content}
{streamStatus.isError && (
Error: {streamStatus.streamError?.message}
)}
);
}
```
### Real-Time Streaming with Fallbacks
Show streaming values immediately as they arrive with controlled fallbacks:
```tsx title="real-time-streaming.tsx"
function StreamingTextGenerator({ content, title, metadata }) {
return (
```
# Adding Tools
URL: /concepts/tools/adding-tools
Define your tool function and register it with tambo. Tools are asynchronous functions that take in a single argument and return a single value.
```tsx
//define the tool function. This is your own custom function and can perform any logic you want.
const getWeather = async (city: string) => {
try {
const weather = await fetch(
`http://api.weatherapi.com/v1/current.json?key=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}&q=${city}`,
);
return weather.json();
} catch (error) {
throw new Error(`Failed to fetch weather for ${city}`);
}
};
// Create a TamboTool definition including your tool function
export const tools: TamboTool[] = [
{
name: "get_weather",
description: "Fetch current weather information for a specified city",
tool: getWeather,
toolSchema: z
.function()
.args(z.string().describe("The city to fetch weather for"))
.returns(
z.object({
location: z.object({
name: z.string(),
}),
}),
),
},
];
// Register your tools with Tambo
;
```
Now tambo can fetch weather information for a city when responding to a message!
## Returning Rich Content
By default, tool responses are converted to strings and sent back to the AI as text. However, tools can return rich content like images, audio, or mixed media by using the `transformToContent` parameter.
The `transformToContent` function transforms your tool's return value into an array of content parts before sending it back to the AI. This is useful when your tool needs to return images, audio files, or a combination of different content types.
```tsx
import { TamboTool } from "@tambo-ai/react";
import { z } from "zod";
const getProductImage = async (productId: string) => {
const product = await fetchProductData(productId);
return {
name: product.name,
description: product.description,
imageUrl: product.imageUrl,
price: product.price,
};
};
export const tools: TamboTool[] = [
{
name: "get_product_image",
description: "Fetch product information including image",
tool: getProductImage,
toolSchema: z
.function()
.args(z.string().describe("Product ID"))
.returns(
z.object({
name: z.string(),
description: z.string(),
imageUrl: z.string(),
price: z.number(),
}),
),
// Transform the result into content parts
transformToContent: (result) => [
{
type: "text",
text: `${result.name} - $${result.price}\n\n${result.description}`,
},
{
type: "image_url",
image_url: { url: result.imageUrl },
},
],
},
];
```
### Supported Content Types
The `transformToContent` function can return an array of content parts with the following types:
* **Text**: `{ type: "text", text: string }`
* **Image**: `{ type: "image_url", image_url: { url: string } }`
* **Audio**: `{ type: "input_audio", input_audio: { data: string, format: "wav" | "mp3" } }`
You can mix and match these content types to create rich, multimedia responses from your tools.
### When to Use transformToContent
Use `transformToContent` when your tool:
* Returns image URLs that should be displayed inline
* Generates audio that should be played
* Needs to return a combination of text and media
* Integrates with MCP servers (which already return content in this format)
If your tool only returns text or simple data, you don't need `transformToContent` - the default behavior will handle it automatically.
# Tools
URL: /concepts/tools
Tools are normal JavaScript functions that you give Tambo access to. When processing a user message, Tambo may decide to use tools you have registered to retrieve information or to take actions. Tools allow you to extend the capabilities of Tambo.
```tsx
const getWeather = (city: string) => {
return `The weather in ${city} is warm and sunny!`;
};
export const tools: TamboTool[] = [
{
name: "getWeather",
description: "A tool to get the current weather conditions of a city",
tool: getWeather,
toolSchema: z
.function()
.args(z.string().describe("The city name to get weather information for"))
.returns(z.string()),
},
];
;
```
# Auth0
URL: /concepts/user-authentication/auth0
Auth0 is a comprehensive identity and access management platform that provides secure authentication and authorization services. This guide shows how to integrate it with Tambo in a Next.js application.
This guide assumes you've already set up Auth0 in your Next.js application. If
you haven't, follow the [Auth0 Next.js Quick
Start](https://auth0.com/docs/quickstart/webapp/nextjs) first.
## Installation
Install the required packages:
```bash
npm install @auth0/nextjs-auth0 @tambo-ai/react
```
## Integration Options
### Server-Side Token Retrieval (Recommended)
Use this approach for better security and performance, especially when you don't need real-time authentication state changes.
```tsx title="app/layout.tsx"
import { getAccessToken } from "@auth0/nextjs-auth0";
import ClientLayout from "./client-layout";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
let accessToken: string | undefined;
try {
// Get the access token from Auth0
const tokenResponse = await getAccessToken();
accessToken = tokenResponse.accessToken;
} catch (error) {
// User is not authenticated
console.log("User not authenticated");
}
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { UserProvider } from "@auth0/nextjs-auth0/client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
userToken?: string;
}
export default function ClientLayout({
children,
userToken,
}: ClientLayoutProps) {
return (
{children}
);
}
```
### Client-Side Token Retrieval
Use this approach when you need real-time authentication state management or client-side routing with authentication guards.
First, create a token API endpoint:
```tsx title="app/api/auth/token/route.ts"
import { getAccessToken } from "@auth0/nextjs-auth0";
import { NextResponse } from "next/server";
export async function GET() {
try {
const { accessToken } = await getAccessToken();
return NextResponse.json({ accessToken });
} catch (error) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
```
Then use Auth0's `useUser` hook in your client layout:
```tsx title="app/client-layout.tsx"
"use client";
import { UserProvider, useUser } from "@auth0/nextjs-auth0/client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode, useEffect, useState } from "react";
interface ClientLayoutProps {
children: ReactNode;
}
function TamboWrapper({ children }: { children: ReactNode }) {
const { user, isLoading } = useUser();
const [accessToken, setAccessToken] = useState();
useEffect(() => {
if (user && !isLoading) {
const fetchToken = async () => {
try {
const response = await fetch("/api/auth/token");
const data = await response.json();
setAccessToken(data.accessToken);
} catch (error) {
console.error("Error fetching token:", error);
}
};
fetchToken();
}
}, [user, isLoading]);
return {children};
}
export default function ClientLayout({ children }: ClientLayoutProps) {
return (
{children}
);
}
```
## Usage
Once configured, you can use Tambo components throughout your application:
```tsx title="app/dashboard/page.tsx"
import { MessageThreadFull } from "@components/tambo/message-thread-full";
export default function Dashboard() {
return (
Dashboard
);
}
```
## Auth0 Configuration
Make sure your Auth0 application is configured with the appropriate scopes and settings:
### Required Scopes
Ensure your Auth0 application has the necessary scopes configured:
* `openid` - Required for OpenID Connect
* `profile` - Access to user profile information
* `email` - Access to user email address
### Token Configuration
In your Auth0 dashboard, verify that your application is configured to:
* Issue access tokens in JWT format
* Include the user's ID in the `sub` claim
* Have the appropriate audience configured if using APIs
Auth0 access tokens are JWTs that include the user's ID in the `sub` claim,
which is exactly what Tambo needs for user identification. The tokens are
automatically signed by Auth0 and can be verified using Auth0's public keys.
# Better Auth
URL: /concepts/user-authentication/better-auth
Better Auth is a modern authentication library that provides built-in support for multiple providers and plugins. This guide shows how to integrate it with Tambo in a Next.js application.
This guide assumes you've already set up Better Auth in your Next.js
application. If you haven't, follow the [Better Auth Next.js Quick
Start](https://www.better-auth.com/docs/installation) first.
## Installation
Install the required packages:
```bash
npm install better-auth @tambo-ai/react
```
## Integration Options
Choose the approach that best fits your application:
### Server-Side Token Retrieval (Recommended)
Use this approach when you want maximum security and don't need real-time authentication state changes in your UI.
**Benefits:**
* Tokens never appear in client-side JavaScript
* Better for SEO and initial page load performance
* No loading states for authentication
```tsx title="app/layout.tsx"
import { auth } from "./lib/auth"; // Your Better Auth instance
import ClientLayout from "./client-layout";
import { headers } from "next/headers";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth.api.getSession({
headers: await headers(),
});
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
userToken?: string;
}
export default function ClientLayout({
children,
userToken,
}: ClientLayoutProps) {
return {children};
}
```
### Client-Side Token Retrieval
Use this approach when you need real-time authentication state management or client-side routing with authentication guards.
**Benefits:**
* Real-time authentication state updates
* Better for single-page applications with client-side routing
* Allows for authentication state-dependent UI rendering
```tsx title="app/client-layout.tsx"
"use client";
import { authClient } from "./lib/auth-client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
}
export default function ClientLayout({ children }: ClientLayoutProps) {
const { data: session } = authClient.useSession();
return {children};
}
```
## Usage
Once configured, you can use Tambo components throughout your application. The authentication context is automatically handled:
```tsx title="app/dashboard/page.tsx"
import { MessageThreadFull } from "@components/tambo/message-thread-full";
export default function Dashboard() {
return (
Dashboard
);
}
```
## Next Steps
Your Tambo integration is now complete. The `TamboProvider` will automatically:
* Exchange your Better Auth token for a Tambo token
* Refresh the Tambo token when it expires
* Handle authentication state changes
* Provide user isolation for all Tambo API calls
# Clerk
URL: /concepts/user-authentication/clerk
Clerk is a complete authentication and user management solution that handles authentication through middleware without requiring specific API routes. This guide shows how to integrate it with Tambo.
This guide assumes you've already set up Clerk in your Next.js application,
including middleware and sign-in/sign-up pages. If you haven't, follow the
[Clerk Next.js Quick Start](https://clerk.com/docs/quickstarts/nextjs) first.
## Installation
Install the required packages:
```bash
npm install @clerk/nextjs @tambo-ai/react
```
## Integration Options
### Server-Side Token Retrieval (Recommended)
Use this approach for better security and performance, especially when you don't need real-time authentication state changes.
```tsx title="app/layout.tsx"
import { auth } from "@clerk/nextjs/server";
import ClientLayout from "./client-layout";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const { getToken } = await auth();
const token = await getToken();
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { ClerkProvider } from "@clerk/nextjs";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
userToken?: string;
}
export default function ClientLayout({
children,
userToken,
}: ClientLayoutProps) {
return (
{children}
);
}
```
### Client-Side Token Retrieval
Use this approach when you need real-time authentication state management or client-side routing with authentication guards.
```tsx title="app/layout.tsx"
"use client";
import { ClerkProvider } from "@clerk/nextjs";
import ClientLayout from "./client-layout";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { useAuth } from "@clerk/nextjs";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode, useEffect, useState } from "react";
interface ClientLayoutProps {
children: ReactNode;
}
export default function ClientLayout({ children }: ClientLayoutProps) {
const { getToken, isLoaded, isSignedIn } = useAuth();
const [accessToken, setAccessToken] = useState();
useEffect(() => {
async function fetchToken() {
if (isLoaded && isSignedIn) {
try {
const token = await getToken();
setAccessToken(token || undefined);
} catch (error) {
console.error("Error fetching token:", error);
}
}
}
fetchToken();
}, [isLoaded, isSignedIn, getToken]);
return {children};
}
```
## Usage
Once configured, you can use Tambo components throughout your application:
```tsx title="app/dashboard/page.tsx"
import { MessageThreadFull } from "@components/tambo/message-thread-full";
export default function Dashboard() {
return (
Dashboard
);
}
```
## Advanced Configuration
### Custom JWT Templates
For advanced use cases, you can create custom JWT templates in Clerk to include specific claims:
1. Go to your Clerk Dashboard
2. Navigate to "JWT Templates"
3. Create a new template with the claims you need
4. Use the template name when calling `getToken()`:
```tsx
const token = await getToken({ template: "your-template-name" });
```
Clerk provides JWT tokens that include the user's ID in the `sub` claim, which
is exactly what Tambo needs for user identification. The token is
automatically signed and verified by Clerk.
# User Authentication
URL: /concepts/user-authentication
To ensure each of user in your application can see only their own threads and messages, Tambo allows you to bring your own authentication. Generate an auth token and pass it to Tambo, and configure how Tambo should verify the token to get a user ID. Preset configurations for many popular authentication providers are available to choose from.
```tsx
const userToken = useUserToken(); // Get token from your auth provider
...
;
```
# Neon
URL: /concepts/user-authentication/neon
Auth.js (formerly NextAuth.js) is a complete authentication solution that can use various database adapters. This guide shows how to integrate Tambo with Auth.js when using Neon as the database backend for session and user data storage.
This guide assumes you've already set up Auth.js with the Neon database
adapter. If you haven't, follow the [Neon Auth.js Integration
guide](https://neon.tech/docs/guides/auth-authjs) first.
## Installation
Install the required packages:
```bash
npm install next-auth @auth/neon-adapter @neondatabase/serverless @tambo-ai/react
```
## Auth.js Configuration
Configure Auth.js to use the Neon adapter and return access tokens:
```tsx title="app/api/auth/[...nextauth]/route.ts"
import NextAuth from "next-auth";
import { NeonAdapter } from "@auth/neon-adapter";
import { neon } from "@neondatabase/serverless";
import GoogleProvider from "next-auth/providers/google";
const sql = neon(process.env.NEON_DATABASE_URL!);
export const authOptions = {
adapter: NeonAdapter(sql),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
session.accessToken = token.accessToken as string;
return session;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
```
## Integration Options
### Server-Side Token Retrieval (Recommended)
Use this approach for better security and performance, especially when you don't need real-time authentication state changes.
```tsx title="app/layout.tsx"
import { getServerSession } from "next-auth/next";
import { authOptions } from "./api/auth/[...nextauth]/route";
import ClientLayout from "./client-layout";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getServerSession(authOptions);
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
userToken?: string;
}
export default function ClientLayout({
children,
userToken,
}: ClientLayoutProps) {
return {children};
}
```
### Client-Side Token Retrieval
Use this approach when you need real-time authentication state management or client-side routing with authentication guards.
```tsx title="app/layout.tsx"
"use client";
import { SessionProvider } from "next-auth/react";
import ClientLayout from "./client-layout";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { useSession } from "next-auth/react";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
}
export default function ClientLayout({ children }: ClientLayoutProps) {
const { data: session } = useSession();
return (
{children}
);
}
```
## Usage
Once configured, you can use Tambo components throughout your application:
```tsx title="app/dashboard/page.tsx"
import { MessageThreadFull } from "@components/tambo/message-thread-full";
export default function Dashboard() {
return (
Dashboard
);
}
```
# Auth.js
URL: /concepts/user-authentication/nextauth
Auth.js (formerly NextAuth.js) is a complete authentication solution for Next.js applications. This guide demonstrates integration with Tambo using Google as the OAuth provider.
This guide assumes you've already set up Auth.js with Google OAuth in your
Next.js application. If you haven't, follow the [Auth.js Google Provider
documentation](https://authjs.dev/getting-started/providers/google) first.
## Installation
Install the required packages:
```bash
npm install next-auth @auth/core @tambo-ai/react
```
## Auth.js Configuration
First, configure Auth.js to return the access token in your API route:
```tsx title="app/api/auth/[...nextauth]/route.ts"
import NextAuth, { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async jwt({ token, account }) {
// Persist the OAuth access_token to the token right after signin
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
// Send properties to the client
session.accessToken = token.accessToken as string;
return session;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
```
## TypeScript Configuration
Add the access token to your Auth.js session type:
```tsx title="types/next-auth.d.ts"
import "next-auth";
declare module "next-auth" {
interface Session {
accessToken?: string;
}
}
declare module "next-auth/jwt" {
interface JWT {
accessToken?: string;
}
}
```
## Integration Options
### Server-Side Token Retrieval (Recommended)
Use this approach for better security and performance, especially for server-rendered applications.
```tsx title="app/layout.tsx"
import { getServerSession } from "next-auth/next";
import { authOptions } from "./api/auth/[...nextauth]/route";
import ClientLayout from "./client-layout";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getServerSession(authOptions);
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
userToken?: string;
}
export default function ClientLayout({
children,
userToken,
}: ClientLayoutProps) {
return {children};
}
```
### Client-Side Token Retrieval
Use this approach when you need real-time authentication state management or client-side routing.
```tsx title="app/layout.tsx"
"use client";
import { SessionProvider } from "next-auth/react";
import ClientLayout from "./client-layout";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { useSession } from "next-auth/react";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
}
export default function ClientLayout({ children }: ClientLayoutProps) {
const { data: session } = useSession();
return (
{children}
);
}
```
## Usage
Once configured, you can use Tambo components throughout your application:
```tsx title="app/dashboard/page.tsx"
import { MessageThreadFull } from "@components/tambo/message-thread-full";
export default function Dashboard() {
return (
Dashboard
);
}
```
## Important Considerations
Google access tokens expire after 1 hour. For production applications,
consider implementing token refresh logic or using Auth.js's built-in refresh
token capabilities.
Make sure your Google OAuth application has the necessary scopes configured.
The `openid`, `email`, and `profile` scopes are typically sufficient for user
identification.
# Overview
URL: /concepts/user-authentication/overview
In a Tambo application, each user has their own threads and messages, isolated from other users' data. This user isolation is achieved through secure token-based authentication.
## How Tambo Authentication Works
Tambo uses OAuth 2.0 [Token Exchange](https://datatracker.ietf.org/doc/html/rfc8693) to securely identify users. Here's what happens:
1. **Your app authenticates the user** with your chosen OAuth provider (Auth0, Clerk, etc.)
2. **Your app receives a JWT token** from the provider containing user information
3. **Your app exchanges this token with Tambo** via the `/oauth/token` endpoint
4. **Tambo returns a Tambo-specific token** that identifies the user for all subsequent API calls
## Token Requirements
Tambo supports any OAuth 2.0 provider that issues a [JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519) with:
* A `sub` (subject) claim identifying the user
* Proper signature for verification (when JWT verification is enabled)
This includes most popular providers like Google, Microsoft, Auth0, Clerk, and others.
## Implementation Approaches
### Server-Side Token Retrieval (Recommended)
**Best for**: Most applications, especially those requiring server-side rendering or enhanced security.
* Tokens are retrieved on the server during page rendering
* More secure as tokens never appear in client-side JavaScript
* Better for SEO and initial page load performance
* Handles authentication state before the client renders
### Client-Side Token Retrieval
**Best for**: Highly interactive applications that need real-time authentication state changes.
* Tokens are retrieved in the browser after the page loads
* Allows for real-time authentication state management
* Required when using client-side routing with authentication guards
* May show loading states during token retrieval
## Using TamboProvider
The `TamboProvider` component from `@tambo-ai/react` handles the token exchange process automatically:
```tsx title="Basic Setup"
"use client";
// TamboProvider must be in a client component to manage authentication state
import { TamboProvider } from "@tambo-ai/react";
export default function Layout({ children }: { children: React.ReactNode }) {
const userToken = useUserToken(); // Get token from your auth provider
return {children};
}
```
TamboProvider needs to be in a client component because it manages
authentication state, handles token refresh, and provides React context to
child components. Server components cannot manage state or provide React
context.
## Provider-Specific Integration Guides
For detailed integration examples with popular authentication providers, see the following guides:
Learn how to integrate Tambo with Auth.js using Google OAuth as an example.
Step-by-step guide for integrating Tambo with Auth0 authentication.
Complete example of using Tambo with Clerk's authentication system.
Integration guide for Supabase Auth with Tambo in Next.js applications.
How to use Tambo with Auth.js and Neon PostgreSQL database integration.
Enterprise-grade authentication with WorkOS and Tambo integration.
Modern authentication toolkit with built-in support for multiple providers
and plugins.
## JWT Verification Strategies
When your OAuth provider supports OpenID Connect Discovery (most do), Tambo automatically verifies tokens. For providers that don't, you can configure verification in your project dashboard:
* **OpenID Connect Discovery** (Default): Automatic verification using the provider's public keys
* **Asymmetric JWT Verification**: Manual verification using a provided public key
* **Symmetric JWT Verification**: Verification using a shared secret (testing only)
* **None**: No verification (development only)
Supabase Auth doesn't support asymmetric JWT verification. You'll need to
disable JWT verification in your Tambo project settings when using Supabase.
All verification strategies can be configured in your project dashboard under Settings > User Authentication.
# Supabase
URL: /concepts/user-authentication/supabase
Supabase Auth is a complete authentication solution that integrates seamlessly with your Supabase database. This guide shows how to integrate it with Tambo in a Next.js application.
This guide assumes you've already set up Supabase Auth in your Next.js
application, including the auth callback route. If you haven't, follow the
[Supabase Next.js Quick
Start](https://supabase.com/docs/guides/auth/quickstarts/nextjs) first.
Supabase Auth doesn't support asymmetric JWT verification. You **must**
disable JWT verification in your Tambo project settings (Settings > User
Authentication > Verification Strategy > None) when using Supabase Auth.
## Installation
Install the required packages:
```bash
npm install @supabase/supabase-js @tambo-ai/react
```
## Integration Options
### Server-Side Token Retrieval (Recommended)
Use this approach for better security and performance, especially when you don't need real-time authentication state changes.
```tsx title="app/layout.tsx"
import { createServerClient } from "@supabase/supabase-js";
import { cookies } from "next/headers";
import ClientLayout from "./client-layout";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookies().get(name)?.value;
},
},
},
);
const {
data: { session },
} = await supabase.auth.getSession();
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
userToken?: string;
}
export default function ClientLayout({
children,
userToken,
}: ClientLayoutProps) {
return {children};
}
```
### Client-Side Token Retrieval
Use this approach when you need real-time authentication state management or client-side routing with authentication guards.
```tsx title="app/client-layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { createClient } from "@/lib/supabase";
import { ReactNode, useEffect, useState } from "react";
interface ClientLayoutProps {
children: ReactNode;
}
export default function ClientLayout({ children }: ClientLayoutProps) {
const [accessToken, setAccessToken] = useState();
const supabase = createClient();
useEffect(() => {
// Get initial session
const getInitialSession = async () => {
const {
data: { session },
} = await supabase.auth.getSession();
setAccessToken(session?.access_token);
};
getInitialSession();
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setAccessToken(session?.access_token);
});
return () => subscription.unsubscribe();
}, [supabase]);
return {children};
}
```
## Usage
Once configured, you can use Tambo components throughout your application:
```tsx title="app/dashboard/page.tsx"
import { MessageThreadFull } from "@components/tambo/message-thread-full";
export default function Dashboard() {
return (
Dashboard
);
}
```
## Supabase-Specific Features
### Automatic Token Refresh
Supabase automatically handles token refresh in the background. When tokens expire, Supabase will automatically refresh them, and the TamboProvider will receive the updated token through the auth state change listener.
### Session Management
Supabase provides robust session management across tabs and devices. The auth state change listener ensures that authentication state stays synchronized across your application.
Supabase uses symmetric JWT signing (HMAC-SHA256) rather than asymmetric
signing (RS256). Tambo's JWT verification is designed for asymmetric tokens
from OAuth providers. Since Supabase handles authentication security,
disabling JWT verification in Tambo is safe and recommended.
# WorkOS
URL: /concepts/user-authentication/workos
WorkOS is an enterprise-grade authentication and user management platform that provides features like SSO, SCIM provisioning, and directory sync. This guide shows how to integrate it with Tambo in a Next.js application.
This guide assumes you've already set up WorkOS in your Next.js application,
including the callback route for handling authentication responses. If you
haven't, follow the [WorkOS Next.js Quick
Start](https://workos.com/docs/user-management/nextjs) first.
## Installation
Install the required packages:
```bash
npm install @workos-inc/node @tambo-ai/react
```
## Integration Options
### Server-Side Token Retrieval (Recommended)
Use this approach for better security and performance, especially when you don't need real-time authentication state changes.
```tsx title="app/layout.tsx"
import { getSession } from "@workos-inc/node/middleware";
import ClientLayout from "./client-layout";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getSession({
cookiePassword: process.env.WORKOS_COOKIE_PASSWORD!,
});
return (
{children}
);
}
```
```tsx title="app/client-layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
userToken?: string;
}
export default function ClientLayout({
children,
userToken,
}: ClientLayoutProps) {
return {children};
}
```
### Client-Side Token Retrieval
Use this approach when you need real-time authentication state management or client-side routing with authentication guards.
First, create a custom hook for WorkOS authentication:
```tsx title="hooks/use-workos-auth.ts"
import { useEffect, useState } from "react";
interface UseWorkOSAuthReturn {
accessToken: string | null;
user: any | null;
loading: boolean;
}
export function useWorkOSAuth(): UseWorkOSAuthReturn {
const [accessToken, setAccessToken] = useState(null);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchSession = async () => {
try {
const response = await fetch("/api/auth/session");
if (response.ok) {
const data = await response.json();
setAccessToken(data.accessToken);
setUser(data.user);
}
} catch (error) {
console.error("Error fetching session:", error);
} finally {
setLoading(false);
}
};
fetchSession();
}, []);
return { accessToken, user, loading };
}
```
Create a session API endpoint:
```tsx title="app/api/auth/session/route.ts"
import { NextRequest, NextResponse } from "next/server";
import { getSession } from "@workos-inc/node/middleware";
export async function GET(request: NextRequest) {
try {
const session = await getSession({
cookiePassword: process.env.WORKOS_COOKIE_PASSWORD!,
});
if (!session?.accessToken) {
return NextResponse.json({ error: "No access token" }, { status: 401 });
}
return NextResponse.json({
accessToken: session.accessToken,
user: session.user,
});
} catch (error) {
return NextResponse.json({ error: "Invalid session" }, { status: 401 });
}
}
```
Use the hook in your client layout:
```tsx title="app/client-layout.tsx"
"use client";
import { TamboProvider } from "@tambo-ai/react";
import { useWorkOSAuth } from "@/hooks/use-workos-auth";
import { ReactNode } from "react";
interface ClientLayoutProps {
children: ReactNode;
}
export default function ClientLayout({ children }: ClientLayoutProps) {
const { accessToken, loading } = useWorkOSAuth();
if (loading) {
return
Loading...
;
}
return (
{children}
);
}
```
## Usage
Once configured, you can use Tambo components throughout your application:
```tsx title="app/dashboard/page.tsx"
import { MessageThreadFull } from "@components/tambo/message-thread-full";
export default function Dashboard() {
return (
Dashboard
);
}
```
## WorkOS Enterprise Features
WorkOS provides several enterprise-grade features that work seamlessly with Tambo:
### Single Sign-On (SSO)
* **SAML 2.0 & OpenID Connect**: Support for enterprise identity providers
* **Directory Sync**: Automatic user provisioning and deprovisioning
* **Domain Verification**: Restrict access to verified domains
### User Management
* **SCIM Provisioning**: Automated user lifecycle management
* **Multi-factor Authentication**: Built-in MFA support
* **Audit Logs**: Complete authentication and authorization tracking
WorkOS access tokens include enterprise-specific claims and are designed for
high-security environments. The token automatically includes the necessary
user identification and organizational context that Tambo needs.
# Component State
URL: /concepts/components/component-state
Components generated by Tambo may have internal state values that are updated by user interactions after the component is rendered. To allow Tambo to track the current value of a component's internal state, replace `useState` with `useTamboComponentState`.
This allows Tambo to consider the current state values when responding to subsequent user messages, and allows rendering previous thread messages with their latest values.
import { ImageZoom } from "fumadocs-ui/components/image-zoom";
## Tracking State with `useTamboComponentState`
Consider this simple React component that allows a user to update an `emailBody` field, and tracks whether the email has been sent:
```tsx title="Simple email component"
export const EmailSender = () => {
...
const [emailBody, setEmailBody] = useState("") // tracks the message being typed
const [isSent, setIsSent] = useState(false) // tracks whether the 'send' button has been clicked
...
}
```
If Tambo renders this component and the user edits the `emailBody` field, Tambo will not know about the edit. A following user message like "Help me edit what I've typed so far" will not generate a relevant response.
To allow Tambo to see these state values, simply replace `useState` with `useTamboComponentState`, and pass a `keyName` for each value:
```tsx title="Email component with tambo state"
import { useTamboComponentState } from "@tambo-ai/react";
export const EmailSender = () => {
...
const [emailBody, setEmailBody] = useTamboComponentState("emailBody", "");
const [isSent, setIsSent] = useTamboComponentState("isSent", false);
...
}
```
Now tambo will know the current values of `emailBody` and `isSent`.
## Updating editable state from props
Often when we have an editable state value, like the `emailBody` above, we want Tambo to be able to generate and stream in the initial value. If a user sends "Help me generate an email asking about a good time to meet," Tambo should be able to fill in the value with relevant text, and then the user should be able to edit it.
When using `useState` this can be done by adding a `useEffect` that updates the state value with prop value changes:
```tsx title="Simple email component"
export const EmailSender = ({ initialEmailBody }: { initialEmailBody: string }) => {
...
const [emailBody, setEmailBody] = useState("") // tracks the message being typed
const [isSent, setIsSent] = useState(false) // tracks whether the 'send' button has been clicked
useEffect(() => {
setEmailBody(initialEmailBody)
}, [initialEmailBody])
...
}
```
However, when using `useTamboComponentState`, this pattern will cause the initial prop value to overwrite the latest stored state value when re-rendering a previously generated component.
Instead, use the `setFromProp` parameter of `useTamboComponentState` to specify a prop value that should be used to set the initial state value:
```tsx title="Simple email component"
export const EmailSender = ({ initialEmailBody }: { initialEmailBody: string }) => {
...
const [emailBody, setEmailBody] = useTamboComponentState("emailBody", "", initialEmailBody) // tracks the message being typed, and sets initial value from the prop
const [isSent, setIsSent] = useTamboComponentState("isSent", false) // tracks whether the 'send' button has been clicked
...
}
```
# Defining Tambo Components
URL: /concepts/components/defining-tambo-components
Tell Tambo which components it can use in responses, what they are for, and what props they expect by defining a `TamboComponent` for each.
```tsx
import { z } from "zod";
import { DataChart } from "@/components/DataChart";
import { TamboProvider } from "@tambo-ai/react";
// Define the schema with Zod
export const DataChartProps = z.object({
data: z.object({
labels: z.array(z.string()),
values: z.array(z.number()),
}),
type: z.enum(["bar", "line", "pie"]),
});
const tamboComponents: TamboComponent[] = [
{
component: DataChart,
name: "DataChart",
description: "Displays data as a chart",
propsSchema: DataChartProps,
},
];
;
```
Note that when using zod's `.optional()` on a field, tambo may not attempt to generate any value for the prop.
You can also use `z.describe()` to provide extra guidance to the AI:
```tsx
import { z } from "zod";
import { DataChart } from "@/components/DataChart";
import { TamboProvider } from "@tambo-ai/react";
// Define schema with descriptions for AI
export const DataChartProps = z
.object({
data: z
.object({
labels: z
.array(z.string())
.describe("Use single words or short phrases."),
values: z.array(z.number()).describe("Use whole numbers."),
})
.describe("A component for displaying data in various chart formats"),
type: z
.enum(["bar", "line", "pie"])
.describe(
"Use a chart type that is appropriate for the data. Only use pie charts when less than 5 values.",
),
})
.describe("A component for displaying data in various chart formats");
const tamboComponents: TamboComponent[] = [
{
component: DataChart,
name: "DataChart",
description: "Displays data as a chart",
propsSchema: DataChartProps,
},
];
;
```
This registration approach is for components that Tambo generates from scratch. If you want to pre-place components on your page and let Tambo modify them, use [Interactable Components](./interactable-components) instead—they register themselves automatically when mounted.
If you want Tambo to both modify pre-placed interactable components AND be able to generate new instances inline, register the component in the `TamboProvider` components array as well.
Now when a user sends a message asking about something related to your components, Tambo can respond with the appropriate component filled with relevant data!
# Registering Components
URL: /concepts/components
Register your React components as UI tools that Tambo can intelligently choose and populate with data. Tell Tambo which components are available and what props they expect. This enables conversational control of your app where users can ask for specific functionality and Tambo responds with the appropriate component filled with relevant data.
```tsx
const components: TamboComponent[] = [
{
name: "WeatherDisplay",
description: "A display of the weather in a city",
component: WeatherDisplayComponent,
propsSchema: WeatherDisplayPropsSchema,
},
{
name: "UserProfile",
description: "A user profile card with avatar and basic information",
component: UserProfileComponent,
propsSchema: UserProfilePropsSchema,
},
];
;
```
# Interactable Components
URL: /concepts/components/interactable-components
When you want to place specific components on screen rather than letting Tambo choose which to show, but still want to allow your users to interact with them using natural language, use Tambo's Interactable components.
Unlike regular registered components that Tambo generates and renders from scratch when responding to messages, interactable components are pre-placed by you while still allowing Tambo to modify their props.
## Creating Interactable Components
The easiest way to make a component interactable to Tambo is by using `withInteractable`. Pass in your component, a name, a description, and the props schema, and get an interactable version of your component that Tambo knows about.
1. **Build the presentational component.** Use `useEffect` to keep any local UI state aligned with incoming props from Tambo.
```tsx
import { useEffect, useState } from "react";
type NoteProps = {
title: string;
content: string;
color?: "white" | "yellow" | "blue" | "green";
};
export function Note({ title, content, color = "yellow" }: NoteProps) {
const [draftContent, setDraftContent] = useState(content);
useEffect(() => {
setDraftContent(content);
}, [content]);
return (
{title}
);
}
```
2. **Register it with `withInteractable`.** This tells Tambo the component name, description, and which props it can safely edit.
```tsx
import { withInteractable } from "@tambo-ai/react";
import { z } from "zod";
import { Note } from "./note";
export const InteractableNote = withInteractable(Note, {
componentName: "Note",
description:
"A simple note that can change title, content, and background color",
propsSchema: z.object({
title: z.string(),
content: z.string(),
color: z.enum(["white", "yellow", "blue", "green"]).optional(),
}),
});
```
3. **Render the interactable version in your app.** Tambo now sees the note and can change its props in place.
```tsx
export function Page() {
return (
);
}
```
Show full example
```tsx
import { useEffect, useState } from "react";
import { withInteractable } from "@tambo-ai/react";
import { z } from "zod";
type NoteProps = {
title: string;
content: string;
color?: "white" | "yellow" | "blue" | "green";
};
function Note({ title, content, color = "yellow" }: NoteProps) {
const [draftContent, setDraftContent] = useState(content);
useEffect(() => {
setDraftContent(content);
}, [content]);
return (
{title}
);
}
const InteractableNote = withInteractable(Note, {
componentName: "Note",
description:
"A simple note that can change title, content, and background color",
propsSchema: z.object({
title: z.string(),
content: z.string(),
color: z.enum(["white", "yellow", "blue", "green"]).optional(),
}),
});
export default function Page() {
return (
);
}
```
Unlike regular components that need to be registered in the `TamboProvider` components array, interactable components are automatically registered when they mount. You don't need to add them to your components array.
If you want Tambo to be able to both modify pre-placed instances AND generate new instances inline, you'll need to register the component normally in the `TamboProvider` as well.
This baseline shows the key workflow:
* Build presentational components that accept props.
* Use `useEffect` inside the component to mirror incoming prop changes onto any local UI state (like the textarea draft).
* Register them with `withInteractable`, providing a schema so Tambo knows which props it can mutate.
* Render the interactable version in your app; Tambo can now update those props in place.
The textarea edits are local to the component, which keeps the example lightweight.
Want the user's edits to flow back to Tambo? Follow the step-by-step guide in
[Two-way state syncing for interactables](./two-way-state-syncing).
Now Tambo is able to read and update the note in-place when responding to messages. Users can ask things like:
* "Change the note title to 'Important Reminder'"
* "Update the note content to 'Don't forget the meeting at 3pm'"
* "Make the note blue"
* "Summarize the note I pinned earlier"
### InteractableConfig
When using `withInteractable`, you provide a configuration object describing the component to Tambo:
```tsx
interface InteractableConfig {
componentName: string; // Name for Tambo to reference
description: string; // Description of what the component does
propsSchema: z.ZodTypeAny; // Schema of props for Tambo to generate
}
```
### How it Works
For each component marked as interactable using `withInteractable`, behind the scenes Tambo stores a state object representing the props of the component, and registers a tool to update that object.
When Tambo decides to update an interactable component while responding to a message, it uses that component's 'update' tool, which updates the state and triggers a re-render of your wrapped component.
## Integration with Tambo Provider
Make sure your app is wrapped with the ``.
```tsx
import { TamboProvider } from "@tambo-ai/react";
function App() {
return (
{/* Your app with interactable components */}
);
}
```
This creates a truly conversational interface where users can modify your UI through natural language, making your applications more accessible and user-friendly.
## Automatic Context Awareness
When you use `TamboInteractableProvider`, your interactable components are automatically included in the AI's context. This means:
* The AI knows what components are currently on the page
* Users can ask "What's on this page?" and get a comprehensive answer
* The AI can see the current state (props) of all interactable components
* Component changes are reflected in real-time
**No additional setup required** - this context is provided automatically and can be customized or disabled if needed.
### Example Interactions
With interactable components on the page, users can ask:
* "What components are available?"
* "Change the note title to 'Important Reminder'"
* "Show me the current state of all my components"
* "Summarize the note I pinned earlier"
## Customizing Automatic Context
The automatic context can be disabled, enabled selectively, or customized to show only specific information.
### Disable Globally
To disable interactables context across your entire app:
```tsx
null,
}}
>
{/* Interactables context is disabled, but components still work */}
```
### Enable Locally (Override Global Disable)
If you've disabled it globally but want to enable it for a specific page or section:
```tsx
function SpecificPage() {
const { addContextHelper } = useTamboContextHelpers();
const snapshot = useCurrentInteractablesSnapshot();
React.useEffect(() => {
// Re-enable interactables context for this page only
const helper = () => {
if (snapshot.length === 0) return null;
return {
description: "Interactable components on this page that you can modify",
components: snapshot.map((component) => ({
id: component.id,
componentName: component.name,
description: component.description,
props: component.props,
propsSchema: component.propsSchema ? "Available" : "Not specified",
})),
};
};
addContextHelper("interactables", helper);
}, [addContextHelper, snapshot]);
return (
{/* Context is now enabled for this page */}
);
}
```
### Custom Context (IDs Only Example)
Sometimes you might want to share summary information and have the AI request the full context when needed.
This is an example of how to only IDs and names with every message:
```tsx
import {
useCurrentInteractablesSnapshot,
useTamboContextHelpers,
} from "@tambo-ai/react";
function IdsOnlyInteractables() {
const { addContextHelper } = useTamboContextHelpers();
const snapshot = useCurrentInteractablesSnapshot();
React.useEffect(() => {
const idsOnlyHelper = () => {
if (snapshot.length === 0) return null;
return {
description: "Available interactable component ids.",
components: snapshot.map((component) => ({
id: component.id,
componentName: component.name,
// Deliberately omit props.
})),
};
};
// Override the default helper with our ids only version
addContextHelper("interactables", idsOnlyHelper);
}, [addContextHelper, snapshot]);
return null; // This component just sets up the context helper
}
// Usage
;
```
### Filter by Component Type
Maybe you only want to show certain types of components.
Here is an example of how you could filter by component type:
```tsx
function FilteredInteractablesContext() {
const { addContextHelper } = useTamboContextHelpers();
const snapshot = useCurrentInteractablesSnapshot();
React.useEffect(() => {
const filteredHelper = () => {
// Only show Notes, hide other component types
const allowedTypes = ["Note"];
const filteredComponents = snapshot.filter((component) =>
allowedTypes.includes(component.name),
);
if (filteredComponents.length === 0) return null;
return {
description: "Available interactable components (filtered)",
components: filteredComponents.map((component) => ({
id: component.id,
componentName: component.name,
props: component.props,
})),
};
};
addContextHelper("interactables", filteredHelper);
}, [addContextHelper, snapshot]);
return null;
}
```
## Partial Updates (Property Replacement)
Interactable component props are updated via partial updates. When an update occurs, only the provided top-level props are replaced in the component's existing props. This uses property replacement behavior:
* Providing `{ count: 5 }` only updates `count`, leaving other props unchanged.
* Providing nested objects replaces that nested object entirely, potentially losing other properties within that object.
Example property replacement behavior:
```tsx
// Original props
{
title: "Original Title",
config: {
theme: "light",
language: "en",
features: { notifications: true, analytics: false },
},
}
// Update with a nested object that omits some keys
{
config: { theme: "dark" }
}
// Resulting props (config object is completely replaced)
{
title: "Original Title",
config: {
theme: "dark",
// language and features are now undefined because the entire config object was replaced
},
}
```
Best practice for nested updates: Since nested objects are completely replaced, if you need to update a deeply nested value but keep the rest, provide the full nested object for that branch.
```tsx
// Proper nested update (preserves other nested keys)
{
config: {
theme: "light",
language: "en",
features: {
notifications: true,
analytics: false,
},
},
}
```
### Update Results and Errors
Updates return a string status:
* `"Updated successfully"` for successful updates when props actually change
* `"No changes needed - all provided props are identical to current values"` when no props change
* `"Error: Component with ID not found"` when the target does not exist
* `"Warning: No props provided for component with ID ."` when the update object is empty/null/undefined
## Auto-registered Tools for Interactables
When there are interactable components present, the following tools are registered automatically to help the AI reason about and modify your UI:
* `get_all_interactable_components` — Returns all interactable components with their current props.
* `get_interactable_component_by_id` — Returns a specific interactable component by id.
* `remove_interactable_component` — Removes a component from the interactables list.
* `update_interactable_component_` — Updates the props for a specific component id using partial props. The argument schema is derived from the component's `propsSchema` and accepts partials.
These tools enable the AI to discover what's on the page and perform targeted updates.
## Snapshot Hook: useCurrentInteractablesSnapshot
Use this hook to read the current interactables without risking accidental mutation of internal state. It returns a cloned snapshot of each item and its props.
```tsx
import {
useCurrentInteractablesSnapshot,
useTamboContextHelpers,
} from "@tambo-ai/react";
function InteractablesContextSummary() {
const { addContextHelper } = useTamboContextHelpers();
const snapshot = useCurrentInteractablesSnapshot();
React.useEffect(() => {
const helper = () => {
if (snapshot.length === 0) return null;
return {
description: "Interactable components currently on screen",
components: snapshot.map((c) => ({
id: c.id,
componentName: c.name,
props: c.props,
})),
};
};
addContextHelper("interactables", helper);
}, [addContextHelper, snapshot]);
return null;
}
```
## Practical Tips
* For nested updates, provide the complete nested object to avoid unintended `undefined` values, since nested objects are completely replaced.
* Arrays are replaced entirely when provided in partial updates.
* If you need fine-grained nested updates, structure your props to keep critical nested branches small and independent.
* The property replacement behavior is predictable and explicit - you always know exactly what will be updated.
With these tools and behaviors, you can confidently let Tambo adjust parts of your UI through natural language while retaining predictable update semantics.
# Dynamic Registration
URL: /concepts/components/registering-components-dynamically
For runtime registration of components, you can use the `registerComponent` function from the `useTamboRegistry()` hook. This approach allows you to register components based on conditions, user interactions, or other dynamic factors.
```tsx title="page.tsx"
import { useEffect } from "react";
import { useTamboRegistry } from "@tambo-ai/react";
import { z } from "zod";
import { WeatherDisplay } from "@/components/WeatherDisplay";
// Define simple Zod schemas for component props
const WeatherDisplayProps = z.object({
city: z.string(),
temperature: z.number(),
condition: z.string(),
});
export default function Page() {
const { registerComponent } = useTamboRegistry();
useEffect(() => {
if(someCondition) {
registerComponent({
name: "WeatherDisplay",
description: "A display of the weather in a city.",
component: WeatherDisplay,
propsSchema: WeatherDisplayProps,
})
}
}, [registerComponent]);
return (
// Your page content
);
}
```
# Registering Components with TamboProvider
URL: /concepts/components/registering-with-tambo-provider
The `@tambo-ai/react` package provides hooks to help with component registration. One of the main approaches is using the `TamboProvider` component for static registration.
## Using the TamboProvider
You can pass a list of components directly to the `TamboProvider` component. All components will be visible to Tambo hooks and components rendered inside the provider.
```tsx title="layout.tsx"
import { TamboProvider } from "@tambo-ai/react";
import { z } from "zod";
import { WeatherDisplay } from "@/components/WeatherDisplay";
import { UserProfile } from "@/components/UserProfile";
// Define simple Zod schemas for component props
const WeatherDisplayProps = z.object({
city: z.string(),
temperature: z.number(),
condition: z.string(),
});
const UserProfileProps = z.object({
name: z.string(),
email: z.string(),
avatar: z.string().optional(),
});
const components = [
{
name: "WeatherDisplay",
description: "A display of the weather in a city",
component: WeatherDisplay,
propsSchema: WeatherDisplayProps,
},
{
name: "UserProfile",
description: "A user profile card with avatar and basic information",
component: UserProfile,
propsSchema: UserProfileProps,
},
];
;
```
# Syncing editable interactables
URL: /concepts/components/syncing-editable-interactables
When you want a user-editable component to stay aligned with the props that Tambo controls, you only need a thin wrapper: keep the edited props in state, pass them straight into the interactable, and adopt any remote changes with a `useEffect`.
## Why props fall out of sync
* The component stores user edits in local state, so Tambo never sees the new props.
* Tambo updates the registry, but the component keeps rendering the stale value.
Fix both directions by treating the wrapper state as the single source of truth.
## Step by step
### 1. Presentational component
```tsx
type NoteProps = {
title: string;
content: string;
color?: "white" | "yellow" | "blue" | "green";
};
function Note({ title, content, color = "yellow" }: NoteProps) {
return (
{title}
{content}
);
}
```
* No state, just props. Tambo controls whatever you pass in.
### 2. Register it with `withInteractable`
```tsx
import { withInteractable } from "@tambo-ai/react";
import { z } from "zod";
const InteractableNote = withInteractable(Note, {
componentName: "Note",
description: "A note that both users and Tambo can edit",
propsSchema: z.object({
title: z.string(),
content: z.string(),
color: z.enum(["white", "yellow", "blue", "green"]).optional(),
}),
});
```
* The schema tells Tambo exactly which props it can update.
### 3. Wrap it with local state + a sync effect
```tsx
import { useEffect, useState } from "react";
import { useCurrentInteractablesSnapshot } from "@tambo-ai/react";
function EditableNote(initial: NoteProps) {
const [note, setNote] = useState(initial);
const [interactableId, setInteractableId] = useState(null);
const snapshot = useCurrentInteractablesSnapshot();
// Pull in any updates that Tambo makes to this interactable.
useEffect(() => {
if (!interactableId) return;
const match = snapshot.find((item) => item.id === interactableId);
if (!match) return;
const next = match.props as Partial;
setNote((prev) => ({ ...prev, ...next }));
}, [snapshot, interactableId]);
return (
);
}
```
* `useState` keeps the canonical props that both the UI and Tambo share.
* Updating state immediately rerenders the interactable with the new props, so the registry stays current.
* The `useEffect` mirrors any Tambo-driven updates back into the textarea.
### 4. Use it anywhere
```tsx
export function Page() {
return (
);
}
```
## Full example
Show complete code
```tsx
import { useEffect, useState } from "react";
import {
useCurrentInteractablesSnapshot,
withInteractable,
} from "@tambo-ai/react";
import { z } from "zod";
type NoteProps = {
title: string;
content: string;
color?: "white" | "yellow" | "blue" | "green";
};
function Note({ title, content, color = "yellow" }: NoteProps) {
return (
{title}
{content}
);
}
const InteractableNote = withInteractable(Note, {
componentName: "Note",
description: "A note that both users and Tambo can edit",
propsSchema: z.object({
title: z.string(),
content: z.string(),
color: z.enum(["white", "yellow", "blue", "green"]).optional(),
}),
});
function EditableNote(initial: NoteProps) {
const [note, setNote] = useState(initial);
const [interactableId, setInteractableId] = useState(null);
const snapshot = useCurrentInteractablesSnapshot();
useEffect(() => {
if (!interactableId) return;
const match = snapshot.find((item) => item.id === interactableId);
if (!match) return;
const next = match.props as Partial;
setNote((prev) => ({ ...prev, ...next }));
}, [snapshot, interactableId]);
return (
);
}
export function Page() {
return (
);
}
```
## Quick checklist
* Update the wrapper state whenever the user edits; the interactable sees the new props on the same render.
* Use `useEffect` to adopt registry updates so the UI reflects Tambo’s changes.
* Only pass serializable props into the interactable—avoid callbacks or refs.
# Elicitations
URL: /concepts/model-context-protocol/features/elicitation
MCP Elicitation allows MCP servers to request additional information from users during tool execution. When an MCP server needs user input, Tambo automatically displays an interactive form in the message input area, making it seamless for users to provide the requested information.
For more details on the elicitation specification, see the [MCP Elicitation documentation](https://modelcontextprotocol.io/docs/learn/client-concepts#elicitation).
## What is Elicitation?
Elicitation is a protocol feature that allows MCP servers to pause execution and request structured input from the user. This is useful when:
* A tool needs additional information that wasn't provided in the initial request
* An MCP server requires user confirmation before proceeding with an action
* Multiple pieces of information are needed to complete a task
## Built-in Support
The `message-input` component automatically handles elicitation requests from MCP servers. When an elicitation request is received, the message input area is replaced with a dynamic form that matches the server's requested schema.
```tsx
import { MessageInput } from "@/components/ui/message-input";
// Elicitation is automatically handled - no additional setup needed
;
```
The elicitation UI automatically:
* Renders appropriate input fields based on the requested schema (text, number, boolean, enum)
* Validates user input according to the schema constraints
* Provides Accept, Decline, and Cancel actions
* Returns the user's response to the MCP server
## How It Works
Here's the typical flow when an MCP server requests elicitation:
## Example Use Cases
### Confirmation Dialogs
An MCP server can request confirmation before performing a destructive action:
### Multi-field Forms
MCP servers can request multiple fields at once:
### Enum Selection
When the server needs the user to choose from a predefined set of options:
```tsx
import { TamboProvider } from "@tambo-ai/react";
import { TamboMcpProvider } from "@tambo-ai/react/mcp";
function App() {
return (
{/* Your app - elicitation from server-side MCP servers will work */}
);
}
```
Without the `TamboMcpProvider`, server-side MCP servers cannot send elicitation requests to your application, as there's no connection to Tambo's internal MCP server.
## Advanced: Custom Elicitation Handlers
While the built-in elicitation UI handles most use cases, you can implement custom elicitation handling if needed.
### Custom Elicitation UI
You can access the current elicitation request and respond to it using the `useTamboElicitationContext` hook:
```tsx
import { useTamboElicitationContext } from "@tambo-ai/react/mcp";
function CustomElicitationUI() {
const { elicitation, resolveElicitation } = useTamboElicitationContext();
if (!elicitation) return null;
const handleAccept = () => {
resolveElicitation?.({
action: "accept",
content: {
/* user's input */
},
});
};
const handleDecline = () => {
resolveElicitation?.({ action: "decline" });
};
const handleCancel = () => {
resolveElicitation?.({ action: "cancel" });
};
return (
{elicitation.message}
{/* Render form fields based on elicitation.requestedSchema */}
);
}
```
### Provider-level Handler
For advanced scenarios where you need programmatic control over elicitation flow—such as conditionally approving requests, implementing custom validation logic, or integrating with external systems—you can provide a handler function that intercepts elicitation requests before they reach the UI.
The handler is an async callback that receives the elicitation request and returns a promise with the response. This allows you to implement custom decision logic, show your own UI, or even automatically respond based on the request content.
Apply a custom handler to all MCP servers:
```tsx
import { TamboMcpProvider } from "@tambo-ai/react/mcp";
function App() {
const handleElicitation = async (request, extra, serverInfo) => {
// request: { params: { message, requestedSchema } }
// extra: { signal } - AbortSignal for cancellation
// serverInfo: { name, url, description, ... } - MCP server config
console.log(`Elicitation from ${serverInfo.name}: ${request.params.message}`);
// Show custom UI and collect user input
const userInput = await showCustomElicitationUI(request.params);
return {
action: "accept",
content: userInput,
};
};
return (
{/* Your app */}
);
}
```
### Per-server Handler
You can also override the handler for specific MCP servers, which is useful when different servers require different approval workflows or UI patterns:
```tsx
import { TamboMcpProvider, MCPTransport } from "@tambo-ai/react/mcp";
function App() {
const handleGitHubElicitation = async (request, extra) => {
// Custom handling for GitHub MCP server only
return {
action: "accept",
content: { confirmed: true },
};
};
return (
{/* Your app */}
);
}
```
Per-server handlers take precedence over provider-level handlers.
### Handler Parameters
#### Elicitation Request
```typescript
{
params: {
message: string; // Human-readable message from server
requestedSchema: {
type: "object";
properties: Record;
required?: string[];
};
}
}
```
#### Extra Parameters
```typescript
{
signal: AbortSignal; // Fires if server cancels the request
}
```
#### Response
```typescript
{
action: "accept" | "decline" | "cancel";
content?: Record; // User's input (required if action is "accept")
}
```
* **accept**: User provided input and wants to continue
* **decline**: User chose not to provide input but tool should continue
* **cancel**: User wants to abort the entire operation
# Features
URL: /concepts/model-context-protocol/features
Tambo supports various Model Context Protocol features that enable rich interactions between your application and MCP servers. Below is a summary of currently supported features.
## Supported Features
| Feature | Support Status | Description | Documentation |
| ---------------- | -------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **Tools** | ✅ Supported | Call functions exposed by MCP servers to perform actions and retrieve data | Coming soon |
| **Prompts** | ✅ Supported | Use predefined prompt templates from MCP servers | [View docs](/concepts/model-context-protocol/features/prompts) |
| **Elicitations** | ✅ Supported | Request additional input from users during tool execution with dynamic forms | [View docs](/concepts/model-context-protocol/features/elicitation) |
| **Sampling** | ✅ Supported | Allow MCP servers to request LLM completions through your application | [View docs](/concepts/model-context-protocol/features/sampling) |
| **Resources** | ⏳ Planned | Access data and content exposed by MCP servers | Coming soon |
## Feature Details
### Tools
MCP tools are automatically discovered and made available to Tambo when you connect to an MCP server (either client-side or server-side). The AI can call these tools to perform actions like creating issues, fetching data, or interacting with external services.
Tools support rich content responses, including text, images, and other media types that are seamlessly passed to the AI.
### Prompts
Prompts allow MCP servers to expose reusable prompt templates that can be inserted into your message input. Users can select from available prompts via a dropdown menu in the message input area.
**Note:** Prompt parameters are not yet supported. All prompts are inserted as-is without customization.
[Learn more about prompts](/concepts/model-context-protocol/features/prompts)
### Elicitations
Elicitations allow MCP servers to pause during tool execution and request additional information from users. Tambo provides built-in UI components that automatically render dynamic forms based on the requested schema, handle validation, and return responses to the MCP server.
[Learn more about elicitations](/concepts/model-context-protocol/features/elicitation)
### Sampling
Sampling enables MCP servers to request LLM completions through your application. This allows MCP tools to leverage AI capabilities for tasks like generating text, analyzing content, or making decisions based on context.
**Note:** Sampling currently only works with server-side MCP connections.
[Learn more about sampling](/concepts/model-context-protocol/features/sampling)
### Future Features
**Resources** will enable MCP servers to expose data sources and content that can be dynamically accessed and used by the AI.
## Connection Types
MCP features work with both connection types:
* **[Server-side MCP](/concepts/model-context-protocol/providers/serverside-mcp-connection)** - Configured through the Tambo dashboard
* **[Client-side MCP](/concepts/model-context-protocol/providers/clientside-mcp-connection)** - Connected directly from the browser
Different features may have different capabilities or requirements depending on the connection type. Check the specific documentation for each feature for details.
# Prompts
URL: /concepts/model-context-protocol/features/prompts
Prompts allow MCP servers to expose reusable prompt templates that can be easily inserted into your application. This enables standardized workflows, consistent prompt patterns, and quick access to commonly used prompts provided by MCP servers.
For more details on the prompts specification, see the [MCP Prompts documentation](https://modelcontextprotocol.io/docs/concepts/prompts).
## What are Prompts?
Prompts are predefined text templates exposed by MCP servers that users can insert into their message input. This is useful when:
* An MCP server provides helpful prompt templates for common tasks
* You want to standardize how users interact with specific tools or workflows
* Users need quick access to complex prompts without memorizing syntax
For example, a GitHub MCP server might provide prompts like "Create a detailed issue report" or "Review this pull request", which expand into well-structured templates when selected.
## Built-in Support
The `message-input` component automatically displays available prompts from all connected MCP servers. When any MCP server exposes prompts, a document button (📄) appears in the message input area:
```tsx
import { MessageInput } from "@/components/ui/message-input";
// Prompts are automatically displayed - no additional setup needed
;
```
The prompt picker automatically:
* Discovers prompts from all connected MCP servers
* Displays them in a dropdown menu
* Inserts the selected prompt into the message input
* Only appears when at least one MCP server has prompts available
## How It Works
Here's the typical flow when a user selects an MCP prompt:
## User Experience
When prompts are available:
1. A document icon (📄) appears in the message input area
2. Clicking it opens a dropdown showing all available prompts
3. Prompts are organized by MCP server
4. Selecting a prompt inserts its content into the message input
5. The user can then edit the prompt before sending
## Current Limitations
**Parameters are not yet supported.** While the MCP specification allows prompts to accept parameters for customization, Tambo's current implementation does not expose parameter input fields. All prompts are inserted as-is without user customization.
This means:
* Prompts with parameters will use their default values
* Users cannot customize prompt templates at insertion time
* Parameter support is planned for a future release
## Programmatic Access
You can access Prompts programmatically using the provided hooks:
### List All Prompts
Use `useTamboMcpPromptList` to get all available prompts from connected servers:
```tsx
import { useTamboMcpPromptList } from "@tambo-ai/react/mcp";
function PromptList() {
const { data: prompts, isLoading, error } = useTamboMcpPromptList();
if (isLoading) return
Loading prompts...
;
if (error) return
Error loading prompts
;
return (
{prompts?.map((entry) => (
{entry.prompt.name}
{entry.prompt.description}
From: {entry.server.name}
))}
);
}
```
The hook returns an array of `ListPromptEntry` objects, each containing:
* `server` - The connected MCP server providing this prompt
* `prompt` - The prompt metadata (name, description)
### Get Specific Prompt
Use `useTamboMcpPrompt` to fetch a specific prompt's content:
```tsx
import { useTamboMcpPrompt } from "@tambo-ai/react/mcp";
function PromptContent({ promptName }: { promptName: string }) {
const { data: prompt, isLoading, error } = useTamboMcpPrompt(promptName);
if (isLoading) return
Loading...
;
if (error) return
Error loading prompt
;
if (!prompt) return null;
return (
{prompt.description}
{prompt.messages.map((msg, idx) => (
{msg.role}:
{msg.content.type === "text" &&
{msg.content.text}
}
))}
);
}
```
The hook accepts:
* `promptName` - The name of the prompt to fetch
* `args` - Optional parameters (currently not exposed in UI)
## Connection Type Support
Prompts work with both connection types:
* **[Server-side MCP](/concepts/model-context-protocol/providers/serverside-mcp-connection)** - ✅ Fully supported
* **[Client-side MCP](/concepts/model-context-protocol/providers/clientside-mcp-connection)** - ✅ Fully supported
Both connection types can expose prompts that will appear in the prompt picker.
## Example Use Cases
### Documentation Templates
A documentation MCP server provides prompts for common documentation patterns:
* "API endpoint documentation"
* "Component usage guide"
* "Troubleshooting section"
### Code Review Prompts
A code review MCP server offers structured review templates:
* "Security-focused code review"
* "Performance optimization review"
* "Accessibility audit"
### Task Management
A task tracking MCP server provides prompts for common workflows:
* "Create bug report"
* "Plan feature implementation"
* "Write release notes"
## Future Capabilities
Planned enhancements for MCP prompts include:
* **Inline prompts**: Allow users to edit prompts in the message input, with a `/` hotkey.
* **Parameter support**: Allow users to customize prompts with dynamic values
* **Custom UI**: Build custom prompt pickers with different layouts
* **Prompt history**: Track frequently used prompts
## Related Features
import LearnMore from "@/components/learn-more";
# Sampling
URL: /concepts/model-context-protocol/features/sampling
Sampling enables MCP servers to leverage your application's language model to process information and generate content. Instead of requiring their own AI infrastructure, MCP servers can request LLM completions from your application, creating powerful AI-augmented tools.
For more details on the sampling specification, see the [MCP Sampling documentation](https://modelcontextprotocol.io/docs/concepts/sampling).
## What is Sampling?
Sampling allows MCP servers to request that your application's LLM generate text completions. This is useful when:
* An MCP server needs to analyze or summarize data before presenting it to users
* A tool wants to leverage AI capabilities without implementing its own LLM integration
* An MCP server needs to make decisions or generate content based on context
For example, a data retrieval tool might fetch raw information from an API, then use sampling to have your application's LLM summarize and format that data in a user-friendly way.
## Server-side Support Only
**Important:** Sampling currently only works with **[server-side MCP connections](/concepts/model-context-protocol/providers/serverside-mcp-connection)**. Client-side MCP servers cannot make sampling requests at this time.
This limitation exists because sampling requests need to be processed through Tambo's backend infrastructure to ensure proper security, rate limiting, and LLM access control.
## How It Works
When an MCP server makes a sampling request, it creates a "sub-conversation" between the tool and your application's LLM:
Tambo automatically displays sampling interactions in the UI as expandable dropdowns within tool information panels, showing the messages exchanged between the MCP server and the LLM.
## Built-in Support
Sampling is automatically handled when you connect to server-side MCP servers through the Tambo dashboard. No additional configuration is required in your React application:
```tsx
import { TamboProvider } from "@tambo-ai/react";
import { TamboMcpProvider } from "@tambo-ai/react/mcp";
function App() {
return (
{/* Sampling from server-side MCP servers works automatically */}
);
}
```
The MCP servers you configure in the Tambo dashboard can make sampling requests, and Tambo will handle routing these to your configured LLM provider.
## Example Use Cases
### Data Summarization
An MCP server fetches detailed analytics data and uses sampling to generate a concise summary:
* **Without sampling**: The tool returns raw JSON data that overwhelms the user
* **With sampling**: The tool requests the LLM to create a natural language summary of key insights
### Content Generation
A documentation tool uses sampling to generate code examples based on API specifications:
* The server retrieves API endpoint definitions
* It uses sampling to generate working code examples in the user's preferred language
* The formatted examples are returned to the user
### Decision Making
A workflow automation tool uses sampling to analyze context and suggest next actions:
* The server gathers information about the current workflow state
* It uses sampling to have the LLM recommend appropriate next steps
* The recommendations are presented to the user for confirmation
## Technical Details
### Message Tracking
Messages generated through sampling include a `parentMessageId` field that references the original tool call. This allows Tambo to properly track the conversation hierarchy and display sampling interactions in context.
### Requirements
To use MCP sampling features, ensure you have:
* `@tambo-ai/react@0.57.0` or newer installed
* Run `npx tambo@latest upgrade message` to update your message components
* Server-side MCP connections configured in the Tambo dashboard
## Future Capabilities
Planned enhancements for sampling include:
* **Client-side sampling**: Allow client-side MCP servers to make sampling requests
* **User confirmation**: Let users approve or reject sampling requests before they're processed
* **Custom sampling handlers**: Provide programmatic control over sampling requests
## Related Features
import LearnMore from "@/components/learn-more";
# Client-side MCP Connection
URL: /concepts/model-context-protocol/providers/clientside-mcp-connection
### Client-side Support
Client-side MCP integration allows your application to connect to MCP servers
that are accessible from the end user's browser, for instance when using a local
MCP server.
This is useful for:
* Local MCP servers running on the user's machine
* MCP servers where the user's browser is already authenticated
* Private or internal services behind a firewall, that are only visible from the user's browser
**Note:** *There is currently no support for authenticated MCP servers when using client-side MCP connections*.
To implement client-side MCP support, use the `TamboMcpProvider` component inside your `TamboProvider`:
```tsx
import { TamboProvider } from "@tambo-ai/react";
import { TamboMcpProvider, MCPTransport } from "@tambo-ai/react/mcp";
function MyApp() {
return (
{/* Your application components */}
);
}
```
The `TamboMcpProvider` establishes connections to the specified MCP servers and makes their tools available to Tambo agents in your application.
## Control Flow
# Providers
URL: /concepts/model-context-protocol/providers
Tambo supports two ways to connect to MCP servers, each with different characteristics and use cases.
## Connection Types
### Server-side (Recommended)
Server-side MCP connections are configured through the Tambo dashboard and run on Tambo's backend infrastructure. This approach provides the most efficient communication since tools execute via direct server-to-server connections.
**Key characteristics:**
* **Authentication:** Requires OAuth-based authentication (or custom API key headers)
* **Performance:** More efficient due to direct server-to-server communication
* **Sharing:** MCP servers are shared across all users of your project
* **Ideal for:** Production applications, shared services, and scenarios requiring OAuth
[Learn more about Server-side MCP →](/concepts/model-context-protocol/providers/serverside-mcp-connection)
### Client-side
Client-side MCP connections run directly in the user's browser, allowing you to leverage the browser's existing authentication state and access to local services.
**Key characteristics:**
* **Authentication:** Leverages the browser's authentication state (no OAuth setup needed)
* **Performance:** More chatty due to browser-to-Tambo-to-MCP communication
* **Local access:** Can connect to local MCP servers (e.g., localhost)
* **Ideal for:** Local development, user-specific services, and services behind firewalls
[Learn more about Client-side MCP →](/concepts/model-context-protocol/providers/clientside-mcp-connection)
## Rich Content Support
MCP tools automatically support rich content responses. When MCP servers return content arrays (which can include text, images, and other media types), Tambo automatically passes them through to the AI without converting them to plain text. This means MCP tools can seamlessly return images, formatted content, and other rich media.
If you're building custom tools that need similar capabilities, you can use the `transformToContent` parameter when registering tools. [Learn more about returning rich content from tools](/concepts/tools/adding-tools#returning-rich-content).
# Server-side MCP Connection
URL: /concepts/model-context-protocol/providers/serverside-mcp-connection
### Server-side Support (recommended)
Server-side MCP integration allows you to configure MCP servers at the project
level through the Tambo dashboard. This generally gives the best end-user performance and
allows you to use OAuth-based authentication.
This approach is beneficial when:
* MCP servers need to be shared across all users of your application
* The MCP server is accessible from your Tambo backend
* You need to use an MCP server that requires authentication
To configure server-side MCP:
1. Navigate to [your project dashboard](https://tambo.co/dashboard)
2. Click on your project
3. Find the "MCP Servers" section
4. Click "Add MCP Server"
5. Enter the server URL and server type (StreamableHTTP or SSE), and click the save button.
6. Tambo will automatically detect if an MCP server requires authentication. If so, the "Begin Authentication" button will be shown. Click it to begin the authentication process.
Once configured, the MCP servers will be available to all users of your project without any additional client-side setup.
**Note:** *When an MCP server is authenticated, the authenticated user is currently shared across **all** users of the project. In the future Tambo will support per-user authentication.*
If your server does not support OAuth-based authentication, you can add a custom header to the MCP server configuration. (e.g. `X-Api-Key`)
## Control Flow