npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

foldkit-agent-webmcp

v0.6.0

Published

WebMCP adapter for foldkit-agent. Projects exposed Foldkit Messages into document.modelContext tools.

Readme

foldkit-agent-webmcp

Publishes a foldkit-agent runtime as browser-native WebMCP tools.

Use it when an agent runs inside the page and should operate the same live Foldkit application the person is using. This package does not define new agent capabilities and does not automate the DOM. It translates the capabilities your agent contract already exposes into document.modelContext tools.

Foldkit application
       |
       v
AssistantAgent              protocol-neutral contract
       |
       | AgentBuilder.bind(...)
       v
AgentRuntime                live Model + dispatch + policy
       |
       v
foldkit-agent-webmcp        adapter only
       |
       v
document.modelContext      browser tools
       |
       v
Message -> update -> Model + Commands

The authority boundary stays in foldkit-agent: the contract decides what the agent may observe and which Messages it may cause. WebMCP only makes those capabilities available to a browser-resident agent.

The adapter uses the WebMCP producer API when the host exposes it. Feature detect document.modelContext at runtime; tool registration is optional for browsers that do not provide it.

Install

pnpm add foldkit-agent foldkit-agent-webmcp

foldkit, effect, and foldkit-agent are peer dependencies.

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:

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.

What a capability becomes

Each currently available capability becomes one WebMCP tool:

foldkit-agent                    WebMCP

capability name          ->      tool name
variant description      ->      description
encoded input Schema     ->      inputSchema
AgentRuntime dispatch    ->      execute
completion contract      ->      resolved tool outcome

No input schema or handler is written again. execute delegates to the bound runtime, which performs the same decode, availability check, authorization, dispatch, cancellation, and completion handling as every other adapter.

A capability with a completion contract resolves when its correlated success or failure Message is observed. Without one, validated dispatch is the completion boundary.

Tools follow the Model

Availability is Model-dependent, so the advertised WebMCP tool set may change as the application changes:

RequestedDeleteTodo: {
  name: 'delete_todo',
  description: 'Delete the selected todo',
  available: model => Option.isSome(model.selectedTodoId),
}
no selection       tools: create_todo
       |
user selects todo
       v
selected todo      tools: create_todo, delete_todo

With followModel: true (the default) and a host with subscribe, a Model change reconciles registrations. A capability that disappears is unregistered; a newly available capability is registered.

Availability is stronger than discoverability. A capability absent from the tool list is also refused if a caller somehow invokes its known name.

Reconciliation is serialized, so overlapping Model changes cannot register the same capability twice. A failed browser registration is not recorded as live and is retried on a later reconcile.

Forms as declarative tools

WebMCP also proposes a declarative API: a form carrying toolname and tooldescription, with fields named by name and described by toolparamdescription, is a tool the browser derives from the markup. The browser fills the fields and submits; the person can see the form as it is filled. formTool draws a capability that way, from the same contract, so the form and the registered tool cannot name or describe it differently:

const CreateTodoForm = AgentWebMcp.formTool(AppAgent, 'requested_create_todo')

// In the view: the form and each field the input names. `DraftSubmitted` and
// `model.draft` are the application's own submit and draft, as before.
h.form([...CreateTodoForm.form(h), h.OnSubmit(Message.DraftSubmitted())], [
  h.input([...CreateTodoForm.field('title', h), h.Value(model.draft)]),
])

// Beside `register`, which then answers the form's agent submissions.
AgentWebMcp.register({ agent, forms: [CreateTodoForm] })

formTool only writes attributes; it registers nothing and listens to nothing. register({ forms }) listens for submit on the document in the capture phase, ahead of the application's own handler. A submission the browser marks agentInvoked is answered as the tool call would be: the fields the input names are read from the form as text, decoded, authorized and dispatched as the capability, and the result goes back through respondWith. The application's OnSubmit does not run for it, so an agent's submission never depends on the Model having seen the browser fill the fields. A person's submission is the application's as before.

In a browser whose SubmitEvent has agentInvoked (AgentWebMcp.declarativeTools()), a form's capability is not also registered imperatively, since the form is the tool. Elsewhere it is registered as usual. A field's value arrives as text, as every form submits it: give such a capability text fields, or a transforming Schema (Schema.NumberFromString). The declarative API is an early proposal whose form-to-schema rules are still open, so treat it as a second way in, not the only one.

formTool(definition, name, { autosubmit: true }) adds toolautosubmit, which lets the agent submit without the person's review. It throws for a name the contract does not expose and for an input that is not an object of fields; field throws for a key the input lacks, and its type takes only the input's keys.

Registration lifecycle

AgentWebMcp.register returns:

registration.registered()  // capability names currently registered
await registration.refresh() // reconcile against the current Model
registration.unregister()  // stop following and abort every registration

The main options are:

| Option | Default | Purpose | | --- | --- | --- | | agent | — | The bound AgentRuntime. | | modelContext | pageModelContext() | Producer surface to register against; pass a stand-in in tests. | | followModel | true | Reconcile tools as capability availability changes. | | signal | — | Unregister everything when aborted. | | invocationId | crypto.randomUUID() | Supplies protocol invocation ids. | | onError | — | Receives failures from background reconciliation. | | forms | — | Capabilities drawn as forms (formTool): agent submissions are answered, and declarative browsers get no second, imperative tool. |

Each registered tool has its own registration AbortController. That signal is for the WebMCP registration itself. It is distinct from a tool invocation's execution signal, which is forwarded to AgentRuntime as Invocation.signal.

Errors

Application refusals become tool errors the calling agent can reason about:

No such capability: delete_todo
Capability "delete_todo" is not available right now
Not authorized to invoke "delete_todo"
Invalid input for "create_todo"
Capability "create_todo" failed unexpectedly

execute does not expose underlying application errors to the caller. A host or adapter defect is flattened to a generic failure; the detailed error stays in the application.

Testing without a browser

modelContext is the only browser-specific seam this package needs. Pass an object with registerTool to capture descriptors and invoke their execute functions directly:

const registration = AgentWebMcp.register({
  agent: agentRuntime,
  modelContext: fakeModelContext,
})

await registration.refresh()

That is how the package test suite and examples/todo exercise the real adapter path without requiring browser support.

examples/todo-app shows the same adapter attached to a real local-first Foldkit application.

Choosing an adapter

All four adapters serve the same AssistantAgent contract through a bound runtime. Serving more than one does not duplicate capability declarations.

| Where the agent runs | Adapter | | --- | --- | | In the page, beside the user | foldkit-agent-webmcp | | An external MCP client, over stdio or HTTP | foldkit-agent-mcp | | Another agent, over A2A | foldkit-agent-a2a | | An Agent Native host | foldkit-agent-native |

Why this stays an adapter

WebMCP is experimental and may change independently of Foldkit Plus. Its browser shape is intentionally isolated here. If the proposal changes, this adapter can change without changing the protocol-neutral foldkit-agent contract or the application's Messages.

See also

  • Agents guide — the full builder → contract → runtime mental model.
  • foldkit-agent — declares and binds the contract this package serves.
  • WebMCP — the browser proposal.