vlt / affordances
  • Quickstart
  • Concepts
  • Guides
  • Reference
  • For agents
  • Why Affordances
  • Quickstart
  • Benchmarks

Concepts
  • Overview
  • Actions
  • State
  • Expose
  • Guards and confirm
  • Deltas
  • Plugins

Guides
  • Overview
  • React and WebMCP
  • MCP
  • Lint
  • Security

Plugins
  • Overview

Reference
  • Overview
  • Protocol
  • Options
  • CLI
  • AffordancesProvider
  • Props
  • useAffordances
  • Affordance
  • Devtools
  1. Guides
  2. /
  3. React and WebMCP

React and WebMCP

AffordancesProvider turns the current page's actions into WebMCP tools for browser agents. Affordance marks the controls in the DOM.

@affordances/react serves agents that run inside the browser, next to the person.

AffordancesProvider

TSX
<AffordancesProvider path={pathname} onDelta={() => router.refresh()}>
  {children}
</AffordancesProvider>

On mount, and whenever path changes, the provider fetches the page's graph from its own URL. When the browser supports WebMCP, it registers each available action as a tool:

Tool fieldFrom the descriptor
namename
descriptiondescription
inputSchemainput
annotations.readOnlyHintreadOnly
annotations.consequentialHintexpose === 'confirm'

A tool's execute doesn't call the server action directly. It POSTs to the page's Affordances endpoint, the same way a headless agent would, so browser agents get the same expose, Origin, guard, and confirm checks as everyone else. The browser sends the person's cookies and the page's Origin, so session actions work.

After each call, the provider calls onDelta, reloads the graph, and registers the tools again, so tools that became available appear and ones that didn't go away. Unmounting unregisters everything.

WebMCP is read from document.modelContext, falling back to the earlier navigator.modelContext. Without either, the provider still loads the graph and does nothing else.

Props

PropTypeDescription
pathstringThe current page's path and query. Change it on navigation.
onDelta(delta: Delta) => voidCalled after every call. In Next, router.refresh() re-renders server components.
confirm(message: string) => boolean | Promise<boolean>Asked before a confirm action runs. Defaults to window.confirm.

useAffordances

Inside the provider, useAffordances() returns { graph, call, lastDelta }. graph is the page's graph, or null while it loads or when the page has none. call(action, input) calls an action the way a tool would, including the confirm step, and resolves to the delta. lastDelta is the most recent delta from any call, or null.

TSX
'use client'

import { useAffordances } from '@affordances/react'

export const AgentStatus = () => {
  const { graph } = useAffordances()
  if (!graph) return null
  const available = graph.actions.filter((action) => action.available)
  return <p>{available.length} actions available to agents on this page</p>
}

Affordance

<Affordance> marks the element a person would use for an action, for agents that read the DOM:

TSX
<Affordance action={addToCart} input={{ sku: variant.sku }}>
  <button formAction={addToCart}>Add to cart</button>
</Affordance>

It renders a display: contents wrapper with data-affordances-action="addToCart" and, when you pass input, data-affordances-input with the input as JSON. It doesn't register anything or change behavior.

In a server component, action is the real function from defineAction, and <Affordance> reads the name from it. In a client component, a server action arrives as a bare reference without its metadata, so pass the name instead:

TSX
<Affordance name="addToCart">…</Affordance>

Devtools

<AffordancesDevtools /> from @affordances/react/dev shows what agents see, on top of the page. Render it inside the provider, in development only:

TSX
import { AffordancesDevtools } from '@affordances/react/dev'

{
  process.env.NODE_ENV === 'development' && <AffordancesDevtools />
}

Press ⌥A (Alt+A) to toggle it. It outlines every [data-affordances-action] control with its action's name: solid when the action is available, dashed with the reason when it isn't. It also shows the last delta.

@affordances/lint warns when a control calls a server action outside an <Affordance>.


PreviousSessionsNextMCP

On this page

  • AffordancesProvider
  • Props
  • useAffordances
  • Affordance
  • Devtools
Edit this page

Make your app agent-operable

Give every page a graph and every server action an agent path: HTTP, WebMCP, and MCP, with the same session and the same checks. One command sets it up.

Read the quickstart