A small Vue 3 composable for progressive enhancement with the browser's
document.modelContext WebMCP surface. Unsupported browsers keep working
normally and do not throw.
webmcp-tool-core: framework-neutral lifecycle, detection, normalization, abort cleanup, and callback freshness.vue-webmcp-tool: the Vue 3useWebMCPcomposable.nuxt-webmcp-tool: Nuxt module with auto-imports. Requires Nuxt 4.0 or later.
Vue:
pnpm add webmcp-tool-core vue-webmcp-toolNuxt:
pnpm add nuxt-webmcp-tool// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-webmcp-tool'],
})import { useWebMCP } from 'vue-webmcp-tool'
const { supported, registered, error } = useWebMCP({
name: 'search-products',
description: 'Search the product catalog.',
inputSchema: {
type: 'object',
properties: { query: { type: 'string' } },
required: ['query'],
},
execute: async (args, { signal }) => {
if (!args || typeof args !== 'object' || typeof args.query !== 'string') {
throw new TypeError('query is required')
}
const response = await fetch(`/api/products?q=${encodeURIComponent(args.query)}`, { signal })
if (!response.ok) throw new Error('Product search failed')
return response.json()
},
})useWebMCP starts after mount, watches reactive tool options, and stops on
unmount. The returned state is { supported, registered, error }.
import { createToolController } from 'webmcp-tool-core'
const controller = createToolController({
name: 'lookup-order',
description: 'Look up an order by its public identifier.',
execute: async (args, { signal }) => {
if (!args || typeof args !== 'object' || typeof args.id !== 'string') {
throw new TypeError('id must be a string')
}
const response = await fetch(`/api/orders/${encodeURIComponent(args.id)}`, { signal })
if (!response.ok) throw new Error('Order lookup failed')
return response.json()
},
})
controller.start()
controller.stop()The core accepts the current execute(args, { signal }) form and the
one-argument form used by the original React reference. inputSchema is
descriptive; callbacks must validate their own input and authorization.
Successful values are normalized to WebMCP content responses. Thrown and
rejected values call the latest onError callback and become error content.
Metadata changes are compared by value, while callback changes stay fresh
without forcing a new browser registration.
Detection is based only on document.modelContext; this package does not
guess support from browser versions. Registration uses
registerTool(tool, { signal }) and aborts on component cleanup. The
controller also uses unregisterTool if available for cleanup; otherwise,
the registration AbortSignal is the baseline cleanup mechanism.
- Run the docs with
pnpm docs:dev. - Run the demo with
pnpm example:coffee-shop:dev. - Build everything with
pnpm docs:buildandpnpm example:coffee-shop:build. - Read the Vue coffee-shop guide.
- See the upstream demo catalog for browser and framework references.
Treat every exposed tool as an agent-facing API. Validate arguments and
authorization inside the callback, keep outputs concise, mark untrusted data
with untrustedContentHint, and never expose secrets or privileged operations
merely because a browser agent can call a tool.