Agent / foldkit-agent-webmcp

Sixty seconds: publish a bound runtime

Start with the Agent contract and host guide. The integration below assumes AssistantAgent is your declared contract and AgentBuilder is its application-specialized builder. mounted below is an application host (for example, the value returned by Sync.mount), not an arbitrary Foldkit runtime handle.

Bind that contract to the running application, then register its currently available capabilities with WebMCP:

ts
import { AgentWebMcp } from 'foldkit-agent-webmcp'

const agentRuntime = AgentBuilder.bind({
  definition: AssistantAgent,
  host: {
    model: mounted.model,
    dispatch: message => mounted.dispatch(message),
    subscribe: mounted.subscribe,
    observe: mounted.observe, // Message-based completion
    principal: () => currentPrincipal, // your authenticated application principal
  },
})

const modelContext = AgentWebMcp.pageModelContext()

if (modelContext !== undefined) {
  const registration = AgentWebMcp.register({
    agent: agentRuntime,
    modelContext,
  })

  await registration.refresh()
  window.addEventListener('pagehide', () => registration.unregister())
}

Also call registration.unregister() when the owning component or runtime is disposed; a page unload listener alone does not handle in-page teardown. currentPrincipal above comes from your application, not tool arguments.

That is the whole architecture: the contract remains protocol-neutral, the runtime remains the one authority for availability/authorization/dispatch, and this package translates it to the browser API.

pageModelContext() is document.modelContext, where the spec and Chromium 150 put it, or else navigator.modelContext, the older name that earlier Chromium and agent extensions providing WebMCP still expose. It is safe during SSR and returns undefined when neither exists. Calling register() without a model context throws rather than silently pretending registration succeeded.

Registration starts immediately, but browser registration is asynchronous. register() returns after reconciliation is scheduled; use await registration.refresh() when the caller needs to know that the browser has accepted the current tool set, and onError for background reconcile failures.