Build browser agents with tools and UI
Agent SDK is a browser-first JavaScript and TypeScript SDK for connecting an LLM, application tools, MCP servers, skills, structured responses, and a chat interface. It uses one lifecycle:
When a browser provides native document.modelContext, Agent SDK connects native Tools with its Runtime Tool
registry. When native WebMCP is unavailable, runtime.attachModelContext() installs a compatible fallback at
document.modelContext. Application code can therefore register, discover, and execute Tools through one integration
path instead of maintaining separate WebMCP and non-WebMCP implementations.
Agent SDK is not merely a general-purpose WebMCP polyfill. It uses the WebMCP ModelContext and Tool contracts as a compatibility boundary, then provides a broader Agent application framework around them.
| WebMCP-compatible foundation | Additional Agent SDK capabilities |
|---|---|
Native document.modelContext integration | Provider-independent Runtime, Agent, Session, and Turn lifecycle |
| Fallback ModelContext when native WebMCP is unavailable | Direct Mode and credential-protecting Relay Mode |
| Tool registration, discovery, and direct execution | Tool approval, Tool calling loops, ToolGroup, and remote MCP servers |
| A shared Tool implementation for SDK and browser Agents | Skills, structured output, attachments, Transcript, and Session events |
| Native and Runtime Tool discovery from one surface | Agent Chat UI, custom interactions, iframe/popup bridge, and Remote Session |
Runtime → Agent → Session → Turn
Use it when an application needs more than a single model request—for example, a support assistant that calls product APIs, an editor assistant that invokes document tools, or an agent whose chat UI and tool providers live in different iframes.
One Tool works with and without native WebMCP
Call attachModelContext() regardless of browser support. The SDK selects the native or fallback path and exposes the
selected mode through runtime.getModelContextMode().
const modelContext = runtime.attachModelContext();
modelContext.registerTool({
name: 'get_workspace_status',
title: 'Get workspace status',
description: 'Returns the readiness state of the current workspace.',
inputSchema: {type: 'object', additionalProperties: false, properties: {}},
annotations: {readOnlyHint: true},
execute: () => ({ready: true}),
});
console.log(runtime.getModelContextMode()); // "native" or "fallback"
const tools = await document.modelContext.getTools();
const status = await document.modelContext.executeTool('get_workspace_status', {});
The same registered Tool can be invoked by an SDK Agent through session.send(...) or discovered by a browser Agent
through document.modelContext. The fallback covers the Runtime-managed ModelContext Tool surface; it does not attempt
to reproduce browser-owned permission UI, navigation behavior, or arbitrary cross-origin Tool discovery.
Compatibility with the current WebMCP draft
This comparison uses the WebMCP Draft Community Group Report dated 21 July 2026. WebMCP remains a Community Group draft, not a W3C Standard, so applications should pin the SDK version and review this table when the draft changes.
Legend: O = compatible, △ = usable with a documented difference or only on the native path, X = not implemented by the SDK fallback.
| Current WebMCP surface | Native WebMCP path | SDK fallback path | Overall | Compatibility note |
|---|---|---|---|---|
document.modelContext | O | O | O | attachModelContext() uses native support or installs the fallback at the same property. |
ModelContext as EventTarget | O | X | △ | The fallback is a plain compatibility object, not an EventTarget. |
registerTool(tool) basic registration | O | O | O | Name, title, description, schema, annotations, and executor are retained. |
registerTool() returns Promise<undefined> | X | X | X | The SDK wrapper currently returns synchronously. Do not depend on await registerTool() matching the draft lifecycle. |
| Tool name grammar and 128-character limit | O | O | O | ASCII letters, digits, _, -, and . are accepted; invalid names are rejected. |
Draft DOMException type and asynchronous rejection timing | △ | △ | △ | Invalid definitions fail, but SDK error class and timing are not guaranteed to match Web IDL exactly. |
ModelContextRegisterToolOptions.signal | X | X | X | The SDK uses an internal AbortSignal when mirroring into native WebMCP, but does not accept the caller's registration signal. |
ModelContextRegisterToolOptions.exposedTo | X | X | X | Cross-origin exposure policy is not implemented by the SDK registration wrapper. |
getTools() local discovery | O | O | O | SDK Runtime Tools are returned alphabetically with stringified inputSchema, window, and origin. |
getTools({fromOrigins}) descendant/cross-origin discovery | O | X | △ | Options are delegated to a native implementation. Fallback discovery remains inside the attached Runtime. |
RegisteredTool fields | O | O | O | name, title, description, stringified inputSchema, window, origin, and annotations are exposed. |
readOnlyHint | O | O | O | Preserved through registration and discovery. |
untrustedContentHint | O | O | O | Preserved through registration and discovery. |
toolchange / ontoolchange | O | X | △ | Native browser events remain available; the fallback does not dispatch the draft event. |
| Secure Context and fully-active Document enforcement | O | X | △ | Native enforcement belongs to the browser. The fallback does not reproduce the Web IDL security gate. |
Permissions Policy tools with default 'self' | O | X | △ | Native enforcement belongs to the browser. Use Tool Provider/bridge allowlists for SDK-managed frames. |
| Declarative WebMCP | — | — | — | The current draft section is still TODO; there is no stable normative surface to claim compatibility with. |
What the SDK intentionally adds beyond WebMCP
These capabilities are Agent SDK extensions. They are not claims of WebMCP standard compatibility.
| Agent SDK extension | WebMCP draft | Agent SDK |
|---|---|---|
Execute a discovered Tool from page JavaScript with executeTool() | X | O |
| Accept a Tool name or discovered Tool object for direct execution | X | O |
| Accept JSON string or JavaScript object Tool input | X | O |
| Runtime → Agent → Session → Turn lifecycle | X | O |
| Provider adapters and multi-step Tool calling loop | X | O |
| Per-Turn allowlist and Tool approval | X | O |
| ToolGroup and remote MCP server discovery | X | O |
| Skills and structured model output | X | O |
| Transcript, attachments, Property Bag, snapshot/fork/merge | X | O |
| Agent Chat UI and structured user interactions | X | O |
| iframe/popup Tool Provider and Remote Session bridge | X | O |
| Direct Mode and Backend Relay Mode | X | O |
executeTool() is documented by the
Chrome WebMCP Imperative API, but it is not present in the
21 July 2026 Community Group Draft ModelContext IDL. Treat it as a useful browser/SDK extension until the draft adopts
or replaces it.
document.modelContext.executeTool(...) directly runs a Tool. It does not create a Session Turn and does not apply
allowedToolNames, Tool approval, provider orchestration, or Transcript recording. Use session.send(...) when those
Agent policies and records are required.
What you receive
| Entry point | Use it for |
|---|---|
tf-agent-sdk/runtime | Headless Runtime, Agent, Session, Tool, Provider, MCP, Skill, and bridge APIs |
tf-agent-sdk/ui | The standard Agent Chat UI |
sdk/tf-agent-runtime.js | Browser global without a module bundler |
sdk/tf-agent-chat-ui.js | Browser global including the chat UI |
The package is currently distributed through a private GitHub Release Bundle rather than a public npm registry. Obtain
an approved release bundle, unpack it under vendor/tf-agent-sdk, and add it as a local dependency:
{
"dependencies": {
"tf-agent-sdk": "file:./vendor/tf-agent-sdk"
}
}
Then run your package manager's normal install command.
Five-minute example
import {
AiProviderKeys,
ConnectionModes,
LlmModelNames,
createRuntime,
} from 'tf-agent-sdk/runtime';
const runtime = createRuntime({appName: 'Order workspace'});
const agent = runtime.createAgent({
agentId: 'order-assistant',
systemPrompt: 'Help the user check an order. Never invent an order state.',
aiProviderConfig: {
connectionMode: ConnectionModes.DIRECT,
aiProvider: AiProviderKeys.GOOGLE,
model: LlmModelNames.GEMINI_3_5_FLASH,
auth: {kind: 'apiKey', apiKey: import.meta.env.VITE_GEMINI_API_KEY},
},
});
const modelContext = runtime.attachModelContext();
modelContext.registerTool({
name: 'get_order',
title: 'Get order',
description: 'Returns the current state of one order.',
inputSchema: {
type: 'object',
additionalProperties: false,
required: ['orderId'],
properties: {orderId: {type: 'string'}},
},
annotations: {readOnlyHint: true},
async execute({orderId}) {
const response = await fetch(`/api/orders/${encodeURIComponent(orderId)}`);
if (!response.ok) throw new Error(`Order lookup failed: ${response.status}`);
return response.json();
},
});
const session = agent.createSession({sessionId: crypto.randomUUID()});
const result = await session.send('Check order A-1042 and explain its status.');
console.log(result.text);
The important order is createRuntime() → runtime.createAgent() → agent.createSession() → session.send().
A Runtime does not create Sessions directly.
Direct Mode and Relay Mode
Direct Mode is convenient for a personal development environment, but its provider credential is visible to browser developer tools. Do not ship a shared application with a secret embedded in JavaScript.
Use Relay Mode in production. The browser sends the model request to your Backend, and the Backend attaches the provider credential. Apply authentication, per-user quotas, timeouts, request-size limits, and audit logging at that boundary.
Tool design checklist
- Give every Tool one narrow responsibility and an explicit JSON Schema.
- Set
additionalProperties: falseunless unknown inputs are intentional. - Mark read-only Tools and require approval for destructive Tools.
- Return structured data; let the Agent turn it into prose.
- Treat Tool input as untrusted and enforce authorization inside the Tool implementation.
- Keep Tool errors actionable without exposing credentials or internal stack traces.
Choosing the next feature
Use Local Tools for functions owned by the current page. Use an MCP server when Tool discovery and execution belong to a remote service. Use a Skill for instructions and Tools that should become available only for relevant Turns. Use bridge and Remote Session APIs when Runtime, UI, or Tool providers must run in separate windows or frames.
Troubleshooting
| Symptom | Check |
|---|---|
| The model answers without calling a Tool | Make the Tool description specific and ensure it is registered before send() |
| Provider request is rejected | Check provider, model, connection mode, credential, and relay response shape together |
| A Session loses context | Reuse the same Session instead of creating one per message |
| Browser CORS error | Use Relay Mode or explicitly allow the application origin at the target service |
| A Tool can access another user's data | Enforce identity and resource authorization in the Tool or Backend, not in the prompt |
The source project contains a catalog of 29 focused examples covering Tools, UI placement, bridge configurations, interactions, attachments, and structured output. These examples will be exposed through the Open the Agent SDK Playground to edit and run the examples with the connected browser artifact.