{"id":50658696,"url":"https://github.com/asouqi/webmcp-adapter","last_synced_at":"2026-06-08T01:05:55.854Z","repository":{"id":354698236,"uuid":"1210296338","full_name":"asouqi/webmcp-adapter","owner":"asouqi","description":"Lightweight adapter for WebMCP — define, register, and manage browser tools with built-in schema validation and error handling","archived":false,"fork":false,"pushed_at":"2026-05-29T22:02:51.000Z","size":128,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-29T23:12:50.967Z","etag":null,"topics":["ai","ai-agent","browser","function-calling","llm","mcp","model-context-protocol","standard-schema","tool-calling","type-safe","typescript","validation","webmcp","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/asouqi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-14T09:18:12.000Z","updated_at":"2026-05-29T22:02:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/asouqi/webmcp-adapter","commit_stats":null,"previous_names":["asouqi/webmcp-adapter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/asouqi/webmcp-adapter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asouqi%2Fwebmcp-adapter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asouqi%2Fwebmcp-adapter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asouqi%2Fwebmcp-adapter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asouqi%2Fwebmcp-adapter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/asouqi","download_url":"https://codeload.github.com/asouqi/webmcp-adapter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asouqi%2Fwebmcp-adapter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34043826,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-agent","browser","function-calling","llm","mcp","model-context-protocol","standard-schema","tool-calling","type-safe","typescript","validation","webmcp","zod"],"created_at":"2026-06-08T01:05:54.768Z","updated_at":"2026-06-08T01:05:55.849Z","avatar_url":"https://github.com/asouqi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WebMCP Adapter\n\nA 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](https://github.com/mcp-b/webmcp-types), it provides a type-safe, framework-agnostic way to expose browser-side tools to AI models.\n\n## 🧠 What is WebMCP?\n\nThe [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that enables AI models to securely connect to local and remote tools. [WebMCP](https://webmcp.dev/) brings this protocol directly into the browser — letting web applications expose tools to AI models via `document.modelContext`.\n\n## 🚀 Installation\n\n```bash\nnpm install webmcp-adapter\n# or\nyarn add webmcp-adapter\n```\n\n---\n\n## 🛠 API Reference\n\n### `defineTool(config)`\n\nA 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`.\n\n```typescript\nimport { defineTool } from 'webmcp-adapter'\n\nconst calculateTool = defineTool({\n  name: 'calculate_area',\n  description: 'Calculates the area of a rectangle',\n  inputSchema: {\n    type: 'object',\n    properties: {\n      width: { type: 'number' },\n      height: { type: 'number' }\n    },\n    required: ['width', 'height']\n  },\n  execute: ({ width, height }) =\u003e {\n    // 'width' and 'height' are fully typed as number\n    return {\n      content: [{ type: 'text', text: `The area is ${width * height}` }]\n    }\n  }\n})\n```\n\n---\n\n### `registerTool(tool)`\n\nRegisters a single tool with (`document.modelContext` or `navigator.modelContext`). The `execute` function is automatically wrapped with validation and error handling.\n\n- **Returns**: A cleanup function `() =\u003e void` to unregister that specific tool.\n- **Silent no-op** if WebMCP is not supported in the current environment.\n\n```typescript\nimport { defineTool, registerTool } from 'webmcp-adapter'\n\nconst tool = defineTool({ ... })\nconst unregister = registerTool(tool)\n\n// Later, to clean up:\nunregister()\n```\n\n---\n\n### `registerBatch(tools)`\n\nRegisters an array of tools simultaneously and returns a single cleanup function.\n\n- **Returns**: A single `() =\u003e void` that unregisters all tools in the batch.\n\n```typescript\nimport { defineTool, registerBatch } from 'webmcp-adapter'\n\nconst searchTool = defineTool({ name: 'search_products', ... })\nconst cartTool = defineTool({ name: 'add_to_cart', ... })\nconst checkoutTool = defineTool({ name: 'checkout', ... })\n\nconst unregisterAll = registerBatch([searchTool, cartTool, checkoutTool])\n\n// Later, to clean up all three:\nunregisterAll()\n```\n\n```typescript\n// React usage\nuseEffect(() =\u003e {\n  const unregister = registerBatch([searchTool, cartTool, checkoutTool])\n  return unregister // Cleanup on unmount\n}, [])\n```\n\n---\n\n### `unregisterTool(name)`\n\nUnregisters a specific tool by its string name.\n\n- **Returns**: `true` if the tool was found and removed, `false` otherwise.\n\n```typescript\nimport { unregisterTool } from 'webmcp-adapter'\n\nunregisterTool('calculate_area') // true\nunregisterTool('unknown_tool')   // false\n```\n\n---\n\n### `unregisterAllTools()`\n\nA global reset that removes every tool registered through this adapter. Useful for logout or page-transition scenarios.\n\n```typescript\nimport { unregisterAllTools } from 'webmcp-adapter'\n\n// On page unload\nwindow.addEventListener('beforeunload', () =\u003e {\n  unregisterAllTools()\n})\n\n// On user logout\nfunction handleLogout() {\n  unregisterAllTools()\n  // ... other cleanup\n}\n```\n\n---\n\n### `hasTool(name)`\n\nChecks whether a tool with the given name is currently registered.\n\n- **Returns**: `true` if the tool is registered, `false` otherwise.\n- Useful for **conditional registration** — avoids re-registering a tool that is already active, e.g. when user state or cart state changes.\n\n```typescript\nimport { hasTool, registerTool } from 'webmcp-adapter'\n\nif (!hasTool('checkout_form')) {\n  registerTool(checkoutTool)\n}\n```\n\n---\n\n### `getRegisteredTools()`\n\nReturns the names of all tools currently registered through this adapter as a `string[]`.\n\nUseful for debugging, logging, or verifying which tools are active at any point in the application lifecycle.\n\n```typescript\nimport { getRegisteredTools } from 'webmcp-adapter'\n\nconsole.log(getRegisteredTools())\n// ['search_products', 'add_to_cart', 'checkout_form']\n```\n\n```typescript\nimport { registerBatch, getRegisteredTools } from 'webmcp-adapter'\n\n// Verify tools registered after a batch\nregisterBatch([searchTool, cartTool, checkoutTool])\nconsole.log(getRegisteredTools().length) // 3\n```\n\n---\n\n### `isWebMCPSupported()`\n\nReturns `true` if the current environment supports the (`document.modelContext` or `navigator.modelContext`) API.\n\n```typescript\nimport { isWebMCPSupported } from 'webmcp-adapter'\n\nif (isWebMCPSupported()) {\n  registerTool(myTool)\n} else {\n  console.warn('WebMCP is not available in this environment')\n}\n```\n\n---\n\n## 🛡️ Runtime Validation\n\nThe 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.\n\nWhen 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.\n\nAdditionally, the adapter natively supports [Standard Schema](https://github.com/standard-schema/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.\n\n### 1. Built-in JSON Schema validation (default)\n\nIf no `validator` is provided, the adapter validates input against the `inputSchema` using a lightweight built-in validator. It covers:\n\n- Type checking (`string`, `number`, `boolean`, `array`, `object`, `null`)\n- String constraints: `minLength`, `maxLength`, `pattern`, `enum`\n- Number constraints: `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`\n- Array constraints: `minItems`, `maxItems`, `items`\n- Object constraints: `required`, `properties`\n- Composites: `oneOf`, `anyOf`, `allOf`, `if/then/else`\n\n```typescript\nimport { defineTool, registerTool } from 'webmcp-adapter'\n\nconst calculateTool = defineTool({\n  name: 'calculate_area',\n  description: 'Calculates the area of a rectangle',\n  inputSchema: {\n    type: 'object',\n    properties: {\n      width: { type: 'number', minimum: 0 },\n      height: { type: 'number', minimum: 0 }\n    },\n    required: ['width', 'height']\n  },\n  // No validator provided — built-in JSON Schema validation runs automatically\n  execute: ({ width, height }) =\u003e {\n    return {\n      content: [{ type: 'text', text: `The area is ${width * height}` }]\n    }\n  }\n})\n\nregisterTool(calculateTool)\n```\n\n### 2. Standard Schema validation (Zod, Valibot, ArkType)\n\nPass any [Standard Schema](https://github.com/standard-schema/standard-schema)-compatible library via the `validator` option for strict runtime validation. When provided, this **replaces** the built-in JSON Schema validation.\n\nUse this when you need constraints that JSON Schema cannot express — such as regex patterns, email formats, cross-field rules, or custom `refine` logic.\n\n```typescript\nimport { defineTool, registerTool } from 'webmcp-adapter'\nimport { z } from 'zod'\n\nconst schema = z.object({\n  width: z.number().positive('Width must be a positive number'),\n  height: z.number().positive('Height must be a positive number')\n})\n\nconst calculateTool = defineTool({\n  name: 'calculate_area',\n  description: 'Calculates the area of a rectangle',\n  inputSchema: {\n    type: 'object',\n    properties: {\n      width: { type: 'number' },\n      height: { type: 'number' }\n    },\n    required: ['width', 'height']\n  },\n  validator: schema,  // ← Zod schema replaces built-in validation\n  execute: ({ width, height }) =\u003e {\n    // Reaches here only if Zod validation passed\n    return {\n      content: [{ type: 'text', text: `The area is ${width * height}` }]\n    }\n  }\n})\n\nregisterTool(calculateTool)\n```\n\n### Validation error response\n\nWhen validation fails the adapter returns a structured response back to the AI model so it can correct its input and retry:\n\n```typescript\n// Validation failure — AI should fix input and retry\n{\n  content: [{ type: 'text', text: 'Validation error: width: Expected number, got string' }],\n  isError: true,\n  structuredContent: {\n    success: false,\n    validationFailed: true,                          // ← input was invalid, not a crash\n    error: 'width: Expected number, got string',     // top-level summary\n    errors: {\n      width: 'Expected number, got string'           // field-keyed for targeted correction\n    }\n  }\n}\n```\n\nCompare this to a **runtime error** (an exception thrown inside `execute`), which has `validationFailed: false`:\n\n```typescript\n// Runtime crash — retrying with the same input won't help\n{\n  content: [{ type: 'text', text: 'Error: Something went wrong' }],\n  isError: true,\n  structuredContent: {\n    success: false,\n    validationFailed: false,     // ← something crashed, not a validation issue\n    error: 'Something went wrong',\n    errors: {}\n  }\n}\n```\n\nThe `validationFailed` flag lets the model distinguish between **\"fix your input and retry\"** vs **\"something crashed on the server side\"**.\n\n---\n\n## 🔧 Validation Utilities\n\nThese are exported for use in higher-level libraries (e.g. `webmcp-forms`) that need to run validation outside of the tool registration flow.\n\n### `validateJsonSchema(schema, input)`\n\nValidates `input` against a JSON Schema object. Returns a `ValidationResult`.\n\n```typescript\nimport { validateJsonSchema } from 'webmcp-adapter'\n\nconst result = validateJsonSchema(\n  { type: 'string', minLength: 3 },\n  'hi'\n)\n// { valid: false, error: 'String must be at least 3 characters, got 2' }\n```\n\n### `validateWithStandardSchema(schema, input)`\n\nValidates `input` against a Standard Schema (Zod, Valibot, etc.). Returns a `Promise\u003cValidationResult\u003e` with a full field-keyed `errors` map.\n\n- Issues with a `path` are keyed by `path.join('.')` (e.g. `\"address.zip\"`)\n- Issues with no path (top-level `refine`, cross-field rules) are keyed under `_form`\n\n```typescript\nimport { validateWithStandardSchema } from 'webmcp-adapter'\nimport { z } from 'zod'\n\nconst schema = z.object({\n  email: z.string().email(),\n  age: z.number().min(18)\n})\n\nconst result = await validateWithStandardSchema(schema, { email: 'bad', age: 16 })\n// {\n//   valid: false,\n//   error: 'email: Invalid email, age: Number must be greater than or equal to 18',\n//   errors: {\n//     email: 'Invalid email',\n//     age: 'Number must be greater than or equal to 18'\n//   }\n// }\n```\n\n### `isStandardSchema(value)`\n\nType guard that returns `true` if `value` implements the Standard Schema interface. Used internally to detect Zod, Valibot, and ArkType validators.\n\n```typescript\nimport { isStandardSchema } from 'webmcp-adapter'\nimport { z } from 'zod'\n\nisStandardSchema(z.object({ name: z.string() })) // true\nisStandardSchema({ type: 'object' })              // false — plain JSON Schema\n```\n\n---\n\n## 📦 Exported Types\n\n```typescript\n// From @mcp-b/webmcp-types\nexport type { InputSchema, JsonValue, ToolResponse }\n\n// From webmcp-adapter\nexport type { ToolDefinition, ToolConfig, UnregisterFn, ValidationResult, StandardSchema }\n```\n\n### `ValidationResult`\n\n```typescript\ninterface ValidationResult {\n  valid: boolean\n  error?: string                   // top-level summary string\n  errors?: Record\u003cstring, string\u003e  // field-keyed errors map\n}\n```\n\n### `ToolConfig`\n\n```typescript\ninterface ToolConfig\u003cTSchema extends InputSchema = InputSchema\u003e {\n  name: string\n  description: string\n  inputSchema: InputSchema\n  annotations?: ToolAnnotations\n  validator?: StandardSchema       // optional Zod/Valibot/ArkType schema\n  execute: (input: InferArgsFromInputSchema\u003cTSchema\u003e) =\u003e Promise\u003cToolResponse\u003e | ToolResponse\n}\n```\n\n### `ToolDefinition`\n\nThe normalized, readonly shape returned by `defineTool()` and consumed by `registerTool()` / `registerBatch()`.\n\n```typescript\ninterface ToolDefinition\u003cTSchema extends InputSchema = InputSchema\u003e {\n  readonly name: string\n  readonly description: string\n  readonly inputSchema: TSchema\n  readonly annotations: ToolAnnotations\n  readonly validator?: StandardSchema\n  readonly execute: (input: InferArgsFromInputSchema\u003cTSchema\u003e) =\u003e Promise\u003cToolResponse\u003e | ToolResponse\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasouqi%2Fwebmcp-adapter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasouqi%2Fwebmcp-adapter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasouqi%2Fwebmcp-adapter/lists"}