fastwebmcp
← Home

API Reference

Every export, in detail.

Seven functions, all of it. Signatures, parameters, return values, what throws and when, and a real example for each — several of them the exact code verified against a real browser and the real mcpwasm sandbox.

supportsWebMcp()

function supportsWebMcp(): boolean

Pure feature detection. Returns true only when document.modelContext exists and is a non-null object. Every other function here that touches the browser calls this first — you'll rarely need to call it yourself unless you're branching your own UI on WebMCP support.

Parameters

None.

Returns

booleantrue if this browser supports WebMCP.

Invariants
  • Never throws, regardless of document's shape — missing, null, not an object, or anything else.
Example
if (supportsWebMcp()) {
  console.log('This browser can register WebMCP tools.');
}

defineTool(spec)

function defineTool<TSchema extends ZodType>(spec: ToolSpec<TSchema>): DefinedTool

Validates and normalizes a tool definition. Derives the JSON Schema from your Zod inputSchema, and wraps execute so every call is parsed against that schema before your handler runs. Pure — it never touches document.modelContext itself.

Parameters
NameTypeDescription
spec.namestringNon-empty, 1-128 characters, letters/numbers/"_"/"-"/"." only (the WebMCP spec's own charset).
spec.descriptionstringNon-empty description shown to the agent.
spec.inputSchemaZodTypeA Zod schema describing the tool's input.
spec.execute(input, ctx) => unknownYour handler. Receives the parsed, typed input and { signal: AbortSignal }.
spec.annotations?{ readOnlyHint?, untrustedContentHint? }Optional. Forwarded as-is to document.modelContext.registerTool() — omitted from the result entirely when not passed.
spec.title?stringOptional human-readable label. Forwarded as-is — omitted from the result entirely when not passed. No format restriction, unlike name.
Returns

DefinedTool{ name, description, inputSchema, execute, annotations?, title? }, with inputSchema as JSON Schema and execute wrapped and async.

Throws
  • Synchronously, before doing anything else, if name or description is empty or not a string, or execute is not a function.
  • If name doesn't match the WebMCP spec's charset/length ([A-Za-z0-9_.-]{1,128}) — fails here with a clear message instead of the browser rejecting it later with a less useful one.
Warns
  • Via console.warn (never throws) if name exceeds 30 characters or description exceeds 500 — the limits Chrome's tool security guide recommends for reliable agent results.
Example
const tool = defineTool({
  name: 'add_todo',
  description: 'Add a todo item to the list.',
  inputSchema: z.object({ text: z.string().min(1) }),
  execute: async ({ text }) => `Added: ${text}`,
  annotations: { readOnlyHint: false },
});

registerTool(spec, options?)

function registerTool<TSchema extends ZodType>(spec: ToolSpec<TSchema>, options?: RegisterToolOptions): boolean

The entry point most code calls. Always validates via defineTool first — a broken spec throws immediately, whether or not the browser supports WebMCP, because a bug in your spec isn't something the fallback should hide. Then checks supportsWebMcp(): if unsupported, logs a console.warn naming the tool and returns false without touching the browser; if supported, calls document.modelContext.registerTool(tool, options) and returns true.

Parameters
NameTypeDescription
specToolSpec<TSchema>Same shape as defineTool's parameter.
options.signalAbortSignal?Lets the browser unregister the tool later.
options.exposedTostring[]?Secure origins allowed cross-origin access.
Returns

booleantrue if actually registered with the browser, false if it no-opped.

Throws
  • Same as defineTool — an invalid spec throws regardless of browser support.
Example
const registered = registerTool({
  name: 'add_todo',
  description: 'Add a todo item to the list.',
  inputSchema: z.object({ text: z.string().min(1) }),
  execute: async ({ text }) => `Added: ${text}`,
});
// registered === false on a browser without WebMCP -- the page still works

defineDeclarativeTool(form, spec)

function defineDeclarativeTool(form: DeclarativeFormElementLike, spec: DeclarativeToolSpec): void

Sets the four attributes the WebMCP Declarative API explainer defines directly on a <form>: toolname, tooldescription, toolautosubmit (presence-only), and toolparamdescription on the matching field elements.

Parameters
NameTypeDescription
formDeclarativeFormElementLikeA form element (or anything with setAttribute and an elements collection).
spec.name / descriptionstringSame rules as defineTool.
spec.autoSubmitboolean?Sets toolautosubmit when true.
spec.fields{name,description}[]?Matched against form.elements by name.
Returns

void — it mutates the form in place.

Throws
  • Same fail-fast as defineTool for name/description.
  • If a field name in spec.fields has no matching control in form.elements.
Example
defineDeclarativeTool(form, {
  name: 'submit_support_request',
  description: 'Submit a request for support.',
  fields: [{ name: 'topic', description: 'Determines what team this routes to.' }],
});

respondToAgentSubmit(event, handler)

function respondToAgentSubmit(event: AgentSubmitEventLike, handler: (event) => unknown): boolean

The Declarative-side counterpart to defineTool's wrapped execute. Checks event.agentInvoked; if false (a human submitted the form), it does nothing and your normal submit handling runs. If true, it calls your handler and forwards the result — or a synchronous throw, turned into a rejection — to event.respondWith().

Parameters
NameTypeDescription
eventAgentSubmitEventLikeThe SubmitEvent, with agentInvoked/respondWith per the explainer.
handler(event) => unknownReceives the event, runs sync or async.
Returns

booleantrue if the submit was agent-invoked and handled, false otherwise.

Example
form.addEventListener('submit', (event) => {
  event.preventDefault();
  const handled = respondToAgentSubmit(event, () => ({ status: 'submitted' }));
  if (!handled) {
    // a human submitted -- handle it however you normally would
  }
});

createWebMcpMock()

function createWebMcpMock(): WebMcpMock

A testing harness. Returns a document-shaped object you can assign to globalThis.document, plus invokeTool() to call a registered tool the same way a real agent would — running the exact execute your tool was registered with, not a reimplementation.

Parameters

None.

Returns

WebMcpMock{ document, registeredTools, invokeTool(name, input, context?) }.

Invariants
  • invokeTool rejects with a clear error if no tool is registered under that name.
  • Defaults context.signal to a fresh AbortSignal if you don't provide one.
Example
const mock = createWebMcpMock();
globalThis.document = mock.document;

registerYourTools();

const result = await mock.invokeTool('add_todo', { text: 'Buy milk' });

toMcpwasmSkillSource(tool, options?)

function toMcpwasmSkillSource(tool: DefinedTool, options?: McpwasmSkillOptions): string

Reuses the schema a DefinedTool already carries to emit the registerTool({...}) source a real mcpwasm tool.js expects — verified against the real mcpwasm sandbox, not just checked for valid syntax. Schema-only: it never tries to translate execute, which exists to touch a DOM the sandbox doesn't have.

Parameters
NameTypeDescription
toolDefinedToolThe output of defineTool().
options.handlerBodystring?Raw JS text for the sandboxed handler's body; defaults to a TODO stub naming the sandbox's constraints.
Returns

string — the tool.js source text.

Example

The exact code verified end-to-end against the real mcpwasm CLI — see the status section.

const tool = defineTool({
  name: 'e2e_sum',
  description: 'Sum two numbers a and b.',
  inputSchema: z.object({ a: z.number(), b: z.number() }),
  execute: async ({ a, b }) => a + b, // browser-only, never auto-translated
});

toMcpwasmSkillSource(tool, {
  handlerBody: 'return Number(args.a) + Number(args.b);',
});