- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- Build your first harnessUpdated
- Host optionsNew
- Send and show mediaUpdated
- Connect clientsUpdated
- Write a client in any languageNew
- Access and tenantsNew
- Run in the terminalUpdated
- Deploy a harnessUpdated
- Send inputs safelyUpdated
- Run a tool without a model
- Durable sessionsUpdated
- Run background work on workersNew
- List, rename, and fork sessionsUpdated
- Lifecycle and retentionNew
- Run side effects onceUpdated
- Wait for a personNew
- Build on the session logUpdated
- Control how a turn endsUpdated
- Store settings per threadUpdated
- Compact a long sessionUpdated
- Fork and reset a threadUpdated
- One log for a session treeUpdated
- Write a pluginUpdated
- Track usage and costUpdated
- Ask before risky tool callsUpdated
- Give your agent coding toolsUpdated
- Sandbox and extend the coding toolsUpdated
- Run the coding tools for many threadsNew
- Undo a turnUpdated
- Build a coding agentUpdated
- Delegate to coding agentsUpdated
- Switch between agent profilesUpdated
- Auth and connectorsUpdated
- Share a thread between peopleUpdated
- Connect model providersUpdated
- Build your own UIUpdated
- Code mode in a harnessUpdated
- Work until a goal is metUpdated
- Run agents from a harnessUpdated
- Hand off a conversationNew
- Add skillsUpdated
- Use MCP serversUpdated
- Give your agent a computerNew
- Use from any MCP clientUpdated
- Use a harness from an editor (ACP)Updated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Keep Keys on the ServerUpdated
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Send the current user to the server
Pass forwardedProps to useAppChat(). Every request from that chat carries the value, and the model does not see it.
Client#
Put forwardedProps on the screen, not on chatOptions, because the value changes for each user:
import { fetchServerSentEvents } from '@tanstack/ai-react'
import { createChatHook } from '@tanstack/ai-react/ui'
const chatOptions = {
connection: fetchServerSentEvents('/api/chat'),
}
const { useAppChat, useChatContext } = createChatHook({
options: chatOptions,
components: {
input: () => {
const chat = useChatContext()
return (
<button onClick={() => void chat.sendMessage('What are my invoices?')}>
Ask
</button>
)
},
layout: ({ Messages, Input }) => (
<main>
<Messages />
<Input />
</main>
),
message: ({ Parts }) => <article><Parts /></article>,
},
partsComponents: {
text: ({ part }) => <p>{part.content}</p>,
fallback: () => null,
},
})
export function TenantChat({ tenantId }: { tenantId: string }) {
const chat = useAppChat({
threadId: `tenant-${tenantId}`,
forwardedProps: { tenantId },
})
return <chat.AppChat />
}Server#
Read the value with chatParamsFromRequest. Then put it in context, where tools and middleware read it:
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const tenantId =
typeof params.forwardedProps.tenantId === 'string'
? params.forwardedProps.tenantId
: undefined
if (!tenantId) return new Response('Missing tenant', { status: 400 })
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages: params.messages,
threadId: params.threadId,
context: { tenantId },
})
return toServerSentEventsResponse(stream)
}Click Ask. The route gets tenantId from the request, and the model never sees it.
How it works#
- useAppChat() sends forwardedProps with every request from that instance.
- chatParamsFromRequest returns it as params.forwardedProps.
- context passes it to your tools and middleware.
Validate it on the server#
forwardedProps comes from the browser, so a user can change it. Do not trust it for identity or authorization:
- Get the user id from the auth of the request (your session or token), not from the client.
- Check each forwarded field against a type or a schema.
- If a required field is missing, reject the request.
The example above makes sure that tenantId is a string, and rejects the request without it. A real app also makes sure that the signed-in user can use that tenant.
One chat for each user#
If a screen can show more than one chat, give each instance its own threadId. Two instances with different thread ids keep separate histories.
Next#
- Typed server values for tools and middleware: Runtime context.
- Browser values that stay on the client: Client tools.