Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clients
- Write a client in any language
- Access and tenants
- Run in the terminal
- Deploy a harness
- Send inputs safely
- Run a tool without a model
- Durable sessions
- Run background work on workers
- List, rename, and fork sessions
- Lifecycle and retention
- Run side effects once
- Wait for a person
- Build on the session log
- Control how a turn ends
- Store settings per thread
- Compact a long session
- Fork and reset a thread
- One log for a session tree
- Write a plugin
- Track usage and cost
- Ask before risky tool calls
- Give your agent coding tools
- Sandbox and extend the coding tools
- Run the coding tools for many threads
- Undo a turn
- Build a coding agent
- Delegate to coding agents
- Switch between agent profiles
- Auth and connectors
- Share a thread between people
- Connect model providers
- Build your own UI
- Code mode in a harness
- Work until a goal is met
- Run agents from a harness
- Hand off a conversation
- Add skills
- Use MCP servers
- Give your agent a computer
- Use from any MCP client
- Use a harness from an editor (ACP)
- Overview
- Quick Start
- ProvidersUpdated
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files API
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-Shaking
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
Page WebMCP Tools in Chat
Read the WebMCP tools already registered on document.modelContext and pass those same tools to the in-page chat.
The chat gets them as client tools. When the model calls one, the chat runs it through WebMCP.
Warning
WebMCP is experimental. During SSR, in an insecure context, or in a browser without WebMCP, the tool list stays empty.
Set it up#
You need a working chat first. If you do not have one, start with the Quick Start.
1. Accept client tools on the server#
The server does not know which tools the page has. Let the client send them, and merge them with mergeAgentTools:
// api/chat.ts
import {
chat,
chatParamsFromRequest,
mergeAgentTools,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const stream = chat({
adapter: openaiText('gpt-6-astra'),
messages: params.messages,
threadId: params.threadId,
tools: mergeAgentTools([], params.tools),
})
return toServerSentEventsResponse(stream)
}Warning
params.tools comes from the browser. The model can call these tools, but they run in the browser, not on the server. Keep sensitive work behind your own server checks.
2. Give the page tools to the chat#
Call the page tools API for your framework. Pass the result to the chat as tools.
usePageWebMCPTools returns an array. The array updates when the page adds or removes a tool.
import {
fetchServerSentEvents,
useChat,
usePageWebMCPTools,
} from '@tanstack/ai-react'
const connection = fetchServerSentEvents('/api/chat')
export function Chat() {
const pageTools = usePageWebMCPTools()
const { messages, sendMessage } = useChat({ connection, tools: pageTools, threadId: 'support-chat' })
return (
<button type="button" onClick={() => sendMessage('Open the help panel')}>
Ask ({messages.length} messages)
</button>
)
}That is all. Your chat can now call every WebMCP tool on the page. Ask it to "Open the help panel", and the model calls the page tool that does it.
How it works#
- The page tools API reads the tools from document.modelContext.
- It turns each tool into a client tool, with the same name, description, and input schema.
- The chat sends these tool definitions to your server. mergeAgentTools gives them to the model.
- When the model calls a tool, the chat runs it in the browser through WebMCP.
- When the page adds or removes a tool, the list updates.
To send your own client tools to WebMCP, see WebMCP Tools.
Advanced#
Skip tools with a filter#
Pass filter to skip a tool. Return false, and the chat does not get that tool. The filter gets the WebMCP tool, so you can read these fields:
- name
- origin: the origin of the document that registered the tool.
- annotations
This filter keeps only read-only tools from your own origin:
import { usePageWebMCPTools } from '@tanstack/ai-react'
export function useReadOnlyPageTools() {
return usePageWebMCPTools({
filter: (tool) =>
tool.origin === location.origin &&
tool.annotations?.readOnlyHint === true,
})
}Some providers reject a tool name with a period, such as help.open. WebMCP allows periods. Use filter to skip those tools.
Handle read errors#
Every framework API takes the same two options: filter and onError.
onError gets a failed WebMCP read. An example is a NotAllowedError from the tools permissions policy. When a read fails, the last good list stays in place.
Duplicate tool names#
Tool names must be unique after the filter runs. This includes tools from different frames.
If two tools have the same name:
- getWebMCPTools() rejects.
- A subscription sends the error to onError and keeps the last good list.
Give the tools unique names, or use filter to keep only one of them.
Tool results#
WebMCP returns each tool result as a string. If the string is valid JSON, the chat parses it. If not, the chat gets the string as it is.
Read page tools without a framework#
Use getWebMCPTools from @tanstack/ai-client to read the tools one time:
import { ChatClient, fetchServerSentEvents, getWebMCPTools } from '@tanstack/ai-client'
const client = new ChatClient({
connection: fetchServerSentEvents('/api/chat'),
threadId: 'support-chat',
tools: await getWebMCPTools(),
})To keep the list current, use subscribeWebMCPTools. It calls your listener now, and again after each WebMCP toolchange event. Abort the signal to stop:
import {
ChatClient,
fetchServerSentEvents,
subscribeWebMCPTools,
} from '@tanstack/ai-client'
const client = new ChatClient({
connection: fetchServerSentEvents('/api/chat'),
threadId: 'support-chat',
})
const subscription = new AbortController()
subscribeWebMCPTools((tools) => client.updateOptions({ tools }), {
signal: subscription.signal,
filter: (tool) => tool.origin === location.origin,
})
export function stopPageTools() {
subscription.abort()
}If WebMCP is not available, subscribeWebMCPTools calls your listener one time with an empty array.
Every framework package also exports getWebMCPTools and subscribeWebMCPTools.