webmcp-adapter
v0.0.11
Published
Lightweight adapter for WebMCP — define, register, and manage AI agent tools with built-in schema validation and error handling.
Maintainers
Readme
WebMCP Adapter
A lightweight utility designed to register, validate, and manage tool lifecycles within the document.modelContext (or navigator.modelContext for backward compatibility). By leveraging the official @mcp-b/webmcp-types, it provides a type-safe, framework-agnostic way to expose browser-side tools to AI models.
🧠 What is WebMCP?
The Model Context Protocol (MCP) is an open standard that enables AI models to securely connect to local and remote tools. WebMCP brings this protocol directly into the browser — letting web applications expose tools to AI models via document.modelContext.
🚀 Installation
npm install webmcp-adapter
# or
yarn add webmcp-adapter🛠 API Reference
defineTool(config)
A type-safe helper to create a tool definition. Does not register the tool — it just ensures the execute handler receives the correct types based on inputSchema.
import { defineTool } from 'webmcp-adapter'
const calculateTool = defineTool({
name: 'calculate_area',
description: 'Calculates the area of a rectangle',
inputSchema: {
type: 'object',
properties: {
width: { type: 'number' },
height: { type: 'number' }
},
required: ['width', 'height']
},
execute: ({ width, height }) => {
// 'width' and 'height' are fully typed as number
return {
content: [{ type: 'text', text: `The area is ${width * height}` }]
}
}
})registerTool(tool)
Registers a single tool with (document.modelContext or navigator.modelContext). The execute function is automatically wrapped with validation and error handling.
- Returns: A cleanup function
() => voidto unregister that specific tool. - Silent no-op if WebMCP is not supported in the current environment.
import { defineTool, registerTool } from 'webmcp-adapter'
const tool = defineTool({ ... })
const unregister = registerTool(tool)
// Later, to clean up:
unregister()registerBatch(tools)
Registers an array of tools simultaneously and returns a single cleanup function.
- Returns: A single
() => voidthat unregisters all tools in the batch.
import { defineTool, registerBatch } from 'webmcp-adapter'
const searchTool = defineTool({ name: 'search_products', ... })
const cartTool = defineTool({ name: 'add_to_cart', ... })
const checkoutTool = defineTool({ name: 'checkout', ... })
const unregisterAll = registerBatch([searchTool, cartTool, checkoutTool])
// Later, to clean up all three:
unregisterAll()// React usage
useEffect(() => {
const unregister = registerBatch([searchTool, cartTool, checkoutTool])
return unregister // Cleanup on unmount
}, [])unregisterTool(name)
Unregisters a specific tool by its string name.
- Returns:
trueif the tool was found and removed,falseotherwise.
import { unregisterTool } from 'webmcp-adapter'
unregisterTool('calculate_area') // true
unregisterTool('unknown_tool') // falseunregisterAllTools()
A global reset that removes every tool registered through this adapter. Useful for logout or page-transition scenarios.
import { unregisterAllTools } from 'webmcp-adapter'
// On page unload
window.addEventListener('beforeunload', () => {
unregisterAllTools()
})
// On user logout
function handleLogout() {
unregisterAllTools()
// ... other cleanup
}hasTool(name)
Checks whether a tool with the given name is currently registered.
- Returns:
trueif the tool is registered,falseotherwise. - Useful for conditional registration — avoids re-registering a tool that is already active, e.g. when user state or cart state changes.
import { hasTool, registerTool } from 'webmcp-adapter'
if (!hasTool('checkout_form')) {
registerTool(checkoutTool)
}getRegisteredTools()
Returns the names of all tools currently registered through this adapter as a string[].
Useful for debugging, logging, or verifying which tools are active at any point in the application lifecycle.
import { getRegisteredTools } from 'webmcp-adapter'
console.log(getRegisteredTools())
// ['search_products', 'add_to_cart', 'checkout_form']import { registerBatch, getRegisteredTools } from 'webmcp-adapter'
// Verify tools registered after a batch
registerBatch([searchTool, cartTool, checkoutTool])
console.log(getRegisteredTools().length) // 3isWebMCPSupported()
Returns true if the current environment supports the (document.modelContext or navigator.modelContext) API.
import { isWebMCPSupported } from 'webmcp-adapter'
if (isWebMCPSupported()) {
registerTool(myTool)
} else {
console.warn('WebMCP is not available in this environment')
}🛡️ Runtime Validation
The browser's native WebMCP API (document.modelContext or navigator.modelContext) performs only basic JSON Schema validation. webmcp-adapter adds an additional validation layer on top — wrapping every tool's execute function so that input is validated more thoroughly before your code ever runs.
When validation fails, instead of the model receiving a generic error, it gets a structured isError: true response with field-level details. This allows the AI to see exactly which fields failed and why, correct its input, and retry — rather than simply crashing.
Additionally, the adapter natively supports Standard Schema, meaning you can use libraries like Zod or Valibot to enforce strict constraints (like regex, emails, or custom cross-field logic) that basic JSON Schema cannot handle.
1. Built-in JSON Schema validation (default)
If no validator is provided, the adapter validates input against the inputSchema using a lightweight built-in validator. It covers:
- Type checking (
string,number,boolean,array,object,null) - String constraints:
minLength,maxLength,pattern,enum - Number constraints:
minimum,maximum,exclusiveMinimum,exclusiveMaximum - Array constraints:
minItems,maxItems,items - Object constraints:
required,properties - Composites:
oneOf,anyOf,allOf,if/then/else
import { defineTool, registerTool } from 'webmcp-adapter'
const calculateTool = defineTool({
name: 'calculate_area',
description: 'Calculates the area of a rectangle',
inputSchema: {
type: 'object',
properties: {
width: { type: 'number', minimum: 0 },
height: { type: 'number', minimum: 0 }
},
required: ['width', 'height']
},
// No validator provided — built-in JSON Schema validation runs automatically
execute: ({ width, height }) => {
return {
content: [{ type: 'text', text: `The area is ${width * height}` }]
}
}
})
registerTool(calculateTool)2. Standard Schema validation (Zod, Valibot, ArkType)
Pass any Standard Schema-compatible library via the validator option for strict runtime validation. When provided, this replaces the built-in JSON Schema validation.
Use this when you need constraints that JSON Schema cannot express — such as regex patterns, email formats, cross-field rules, or custom refine logic.
import { defineTool, registerTool } from 'webmcp-adapter'
import { z } from 'zod'
const schema = z.object({
width: z.number().positive('Width must be a positive number'),
height: z.number().positive('Height must be a positive number')
})
const calculateTool = defineTool({
name: 'calculate_area',
description: 'Calculates the area of a rectangle',
inputSchema: {
type: 'object',
properties: {
width: { type: 'number' },
height: { type: 'number' }
},
required: ['width', 'height']
},
validator: schema, // ← Zod schema replaces built-in validation
execute: ({ width, height }) => {
// Reaches here only if Zod validation passed
return {
content: [{ type: 'text', text: `The area is ${width * height}` }]
}
}
})
registerTool(calculateTool)Validation error response
When validation fails the adapter returns a structured response back to the AI model so it can correct its input and retry:
// Validation failure — AI should fix input and retry
{
content: [{ type: 'text', text: 'Validation error: width: Expected number, got string' }],
isError: true,
structuredContent: {
success: false,
validationFailed: true, // ← input was invalid, not a crash
error: 'width: Expected number, got string', // top-level summary
errors: {
width: 'Expected number, got string' // field-keyed for targeted correction
}
}
}Compare this to a runtime error (an exception thrown inside execute), which has validationFailed: false:
// Runtime crash — retrying with the same input won't help
{
content: [{ type: 'text', text: 'Error: Something went wrong' }],
isError: true,
structuredContent: {
success: false,
validationFailed: false, // ← something crashed, not a validation issue
error: 'Something went wrong',
errors: {}
}
}The validationFailed flag lets the model distinguish between "fix your input and retry" vs "something crashed on the server side".
🔧 Validation Utilities
These are exported for use in higher-level libraries (e.g. webmcp-forms) that need to run validation outside of the tool registration flow.
validateJsonSchema(schema, input)
Validates input against a JSON Schema object. Returns a ValidationResult.
import { validateJsonSchema } from 'webmcp-adapter'
const result = validateJsonSchema(
{ type: 'string', minLength: 3 },
'hi'
)
// { valid: false, error: 'String must be at least 3 characters, got 2' }validateWithStandardSchema(schema, input)
Validates input against a Standard Schema (Zod, Valibot, etc.). Returns a Promise<ValidationResult> with a full field-keyed errors map.
- Issues with a
pathare keyed bypath.join('.')(e.g."address.zip") - Issues with no path (top-level
refine, cross-field rules) are keyed under_form
import { validateWithStandardSchema } from 'webmcp-adapter'
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
age: z.number().min(18)
})
const result = await validateWithStandardSchema(schema, { email: 'bad', age: 16 })
// {
// valid: false,
// error: 'email: Invalid email, age: Number must be greater than or equal to 18',
// errors: {
// email: 'Invalid email',
// age: 'Number must be greater than or equal to 18'
// }
// }isStandardSchema(value)
Type guard that returns true if value implements the Standard Schema interface. Used internally to detect Zod, Valibot, and ArkType validators.
import { isStandardSchema } from 'webmcp-adapter'
import { z } from 'zod'
isStandardSchema(z.object({ name: z.string() })) // true
isStandardSchema({ type: 'object' }) // false — plain JSON Schema📦 Exported Types
// From @mcp-b/webmcp-types
export type { InputSchema, JsonValue, ToolResponse }
// From webmcp-adapter
export type { ToolDefinition, ToolConfig, UnregisterFn, ValidationResult, StandardSchema }ValidationResult
interface ValidationResult {
valid: boolean
error?: string // top-level summary string
errors?: Record<string, string> // field-keyed errors map
}ToolConfig
interface ToolConfig<TSchema extends InputSchema = InputSchema> {
name: string
description: string
inputSchema: InputSchema
annotations?: ToolAnnotations
validator?: StandardSchema // optional Zod/Valibot/ArkType schema
execute: (input: InferArgsFromInputSchema<TSchema>) => Promise<ToolResponse> | ToolResponse
}ToolDefinition
The normalized, readonly shape returned by defineTool() and consumed by registerTool() / registerBatch().
interface ToolDefinition<TSchema extends InputSchema = InputSchema> {
readonly name: string
readonly description: string
readonly inputSchema: TSchema
readonly annotations: ToolAnnotations
readonly validator?: StandardSchema
readonly execute: (input: InferArgsFromInputSchema<TSchema>) => Promise<ToolResponse> | ToolResponse
}