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
  • Sessions
  • Confirm
  1. Concepts
  2. /
  3. Expose

Expose

Nothing reaches agents until an action says who may call it. expose is public, session, or confirm.

expose decides whether an action is part of the protocol and who may call it. Without it, an action is an ordinary server action: it doesn't appear in graphs, the handler answers not_found for it, and defineAction warns in development.

exposeWho may call it
publicAnyone
sessionA caller with a session, by cookie or bearer token
confirmA caller with a session, and only with confirm: true after the person approved it

Sessions

A session is whatever your auth resolver returns for a request. Define it once with createAffordances, and both the UI path and the agent path use it:

lib/affordances.ts
import { createAffordances } from '@affordances/next'
import { sessionFrom } from '@/lib/auth'

export const { defineAction, defineState } = createAffordances({
  auth: (request) => sessionFrom(request),
})

The resolver gets a Request. For agent calls, it's the real request, with its cookies and Authorization header. For server actions called from your UI, @affordances/next builds one from headers().

Actions that need a session are still listed for callers without one, as available: false with the reason Requires a session. Calling one answers unauthorized.

Confirm

Use confirm for anything a person should approve: placing an order, sending a message, deleting data. The first call doesn't run anything. It answers confirm_required with a summary:

JSON
{
  "ok": false,
  "error": {
    "code": "confirm_required",
    "message": "Order 2 items for $96.00. Resend with confirm: true once the user approves."
  }
}

The summary comes from the action's summary option, and the agent should show it to the person before resending the same call with "confirm": true. @affordances/react does this for WebMCP tools, with window.confirm unless you pass your own confirm. @affordances/mcp tells the model to do the same.

TypeScript
export const placeOrder = defineAction({
  name: 'placeOrder',
  description: 'Place the order for everything in the cart',
  input: z.object({}),
  state: checkout,
  expose: 'confirm',
  guard: ({ state }) => state.cart.count > 0 || 'Your cart is empty',
  summary: ({ state }) => `Order ${state.cart.count} items for ${format(state.cart.total)}`,
  run: (_, ctx) => orders.place(ctx.session!.id),
})

confirm is a protocol rule, not a promise about the agent. A client can send confirm: true without asking anyone. It stops agents from running consequential actions by accident; it doesn't replace authorization.


PreviousStateNextGuards and confirm

On this page

  • Sessions
  • Confirm
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