The Declarative API is great for annotating existing forms. But when you need custom logic — fetching live data, handling complex inputs, or exposing dynamic capabilities — the Imperative API is where WebMCP gets powerful.
This guide covers the full Imperative API: tool registration, input schema design, execution functions, read-only vs. write tools, error handling, and advanced patterns.
Prerequisites
- HTTPS — required for all WebMCP APIs
- Chrome 146+ with WebMCP flag enabled for testing
- Intermediate JavaScript — async/await, fetch, JSON
- Familiarity with the Declarative API is helpful but not required
The Core API: navigator.modelContext.registerTool()
The Imperative API lives at navigator.modelContext. Always check for its existence before calling it — browsers without WebMCP support will not have it.
// Always guard with feature detection
if ("modelContext" in navigator) {
navigator.modelContext.registerTool({
name: "toolName", // required: camelCase string
description: "...", // required: agent-readable description
inputSchema: { ... }, // required: JSON Schema object
readOnly: true, // optional: true = no confirmation needed
async execute(params) { // required: async function
return {
content: [{ type: "text", text: "result" }]
};
}
});
}
Designing Input Schemas
The inputSchema follows JSON Schema draft-07 format. Getting this right is the most important part of Imperative API implementation — a well-designed schema means agents call your tool correctly every time.
Basic Schema Structure
inputSchema: {
type: "object",
properties: {
paramName: {
type: "string", // string, number, boolean, array
description: "...", // required: explain to the agent
// Optional constraints:
enum: ["a", "b", "c"], // restrict to specific values
format: "email", // email, uri, date, date-time
minLength: 1,
maxLength: 200,
pattern: "^[A-Z]{3}$" // regex pattern
}
},
required: ["paramName"] // array of required param names
}
Full Real-World Example: Service Search Tool
navigator.modelContext.registerTool({
name: "searchSEOServices",
description: "Search Salam Experts SEO service packages.
Returns packages matching the requested service type
and optionally filtered by industry or budget tier.
Read-only — does not submit any data.",
readOnly: true,
inputSchema: {
type: "object",
properties: {
serviceType: {
type: "string",
enum: ["seo", "technical-seo", "shopify-seo",
"local-seo", "ecommerce-seo"],
description: "Type of SEO service"
},
industry: {
type: "string",
description: "Industry to filter by (optional).
E.g.: healthcare, logistics, ecommerce"
},
budgetTier: {
type: "string",
enum: ["starter", "growth", "enterprise"],
description: "Budget tier (optional)"
}
},
required: ["serviceType"]
},
async execute({ serviceType, industry, budgetTier }) {
try {
const params = new URLSearchParams({ serviceType });
if (industry) params.set("industry", industry);
if (budgetTier) params.set("tier", budgetTier);
const response = await fetch(`/api/services?${params}`);
if (!response.ok) throw new Error("API error");
const packages = await response.json();
return {
content: [{
type: "text",
text: JSON.stringify(packages, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error.message}. Try visiting
salamexperts.com/services/ directly.`
}]
};
}
}
});
Read-Only vs. Write Tools
This is a critical design decision. The readOnly flag controls whether the browser asks the user for confirmation before the agent executes the tool.
| readOnly: true (No confirmation) | readOnly: false (Requires confirmation) |
|---|---|
| Agent calls the tool silently | Browser shows user a confirmation prompt |
| Best for: search, get pricing, list services, fetch info | Best for: form submission, booking, purchase, data entry |
| User does not see a prompt | User must approve before agent proceeds |
| Lower friction — agents prefer these | Keeps human in the loop for sensitive actions |
| Examples: searchProducts, getPricing, listCaseStudies | Examples: bookAudit, submitQuote, placeOrder |
Design principle
Default to read-only for informational tools. Require confirmation for anything that creates a record, sends an email, or costs money. This mirrors how users expect AI agents to behave — ask before acting.
Error Handling Patterns
Always wrap execute() in try/catch. Always return a human-readable message in the error case — agents use this to tell users what went wrong.
Standard Error Pattern
async execute(params) {
try {
// Your logic here
const result = await someAsyncOperation(params);
return {
content: [{ type: "text", text: JSON.stringify(result) }]
};
} catch (error) {
// Return readable error — agent will relay this to user
return {
content: [{
type: "text",
text: `Unable to complete request: ${error.message}.
Please visit our site directly or contact us at
salamexperts.com/contact/`
}]
};
}
}
Validation Before Execution
async execute({ email, website }) {
// Validate on top of schema for business rules
if (!website.startsWith("https://") &&
!website.startsWith("http://")) {
return {
content: [{
type: "text",
text: "Please provide a full URL including
https:// (e.g., https://yoursite.com)"
}]
};
}
// Proceed with validated data...
}
Advanced Pattern: Multiple Tools on One Page
You can register multiple tools on a single page. Register them all after the DOM is ready, wrapped in a single feature check.
document.addEventListener("DOMContentLoaded", () => {
if (!("modelContext" in navigator)) return;
// Tool 1: Search
navigator.modelContext.registerTool({
name: "searchServices",
readOnly: true,
description: "...",
inputSchema: { ... },
async execute(p) { ... }
});
// Tool 2: Get case study
navigator.modelContext.registerTool({
name: "getCaseStudy",
readOnly: true,
description: "Get a specific case study by industry.
Returns: client background, results achieved,
and services used.",
inputSchema: {
type: "object",
properties: {
industry: {
type: "string",
enum: ["ecommerce", "logistics", "healthcare"],
description: "Industry for the case study"
}
},
required: ["industry"]
},
async execute({ industry }) {
const studies = {
ecommerce: {
client: "CNH Pillow",
result: "283% organic traffic growth",
service: "E-commerce SEO + technical SEO"
}
};
const study = studies[industry];
return {
content: [{
type: "text",
text: study
? JSON.stringify(study)
: `No case study available for ${industry}`
}]
};
}
});
// Tool 3: Book audit (write)
navigator.modelContext.registerTool({
name: "bookFreeAudit",
readOnly: false,
description: "...",
inputSchema: { ... },
async execute(p) { ... }
});
});
Testing Checklist
| Test | Pass? |
|---|---|
| Tool appears in Model Context Tool Inspector | ☐ |
| Description is complete and clear in Inspector | ☐ |
| Required params enforced (omit one, see error) | ☐ |
| Enum values work correctly | ☐ |
| Successful execution returns structured JSON | ☐ |
| Error case returns readable message | ☐ |
| Write tools show browser confirmation prompt | ☐ |
| Read-only tools call without prompt | ☐ |
| No errors in browser DevTools console | ☐ |
Ready to implement WebMCP?
Building complex tools and want an expert review? We audit implementations, review schema design, and ensure your tools work reliably across agent scenarios.
Get WebMCP Readiness AuditRelated Reading
- WebMCP Declarative API Guide (start here)
- WebMCP Security Best Practices
- WebMCP for E-Commerce
- WebMCP Implementation Services