koh / webmcp
WebMCP (Web Model Context Protocol) integration for TYPO3 — exposes structured tools (search, navigation, content) to AI agents via navigator.modelContext.
Requires
- php: ^8.5.11
- typo3/cms-backend: ^13.4 || ^14.0
- typo3/cms-core: ^13.4 || ^14.0
- typo3/cms-frontend: ^13.4 || ^14.0
Requires (Dev)
- koh/dev-typo3: ^1.0
- typo3/cms-form: ^13.4 || ^14.0
Suggests
- typo3/cms-form: Required for the Form Tool integration (submitting TYPO3 forms via WebMCP)
Provides
None
Conflicts
None
Replaces
None
README
Web Model Context Protocol (WebMCP) integration for TYPO3.
Allows AI agents to interact with TYPO3 websites in a structured way via document.modelContext.
Features
- Automatic tool registration — Built-in tools for search, navigation and page content
- Extensible — Add custom tools via
ToolProviderInterface+ToolHandlerInterface - Secure — CSRF nonce, SecureContext check, input validation, frontend user group access control
- Per-site configurable — Site Sets with individual tool toggles
- Cache-friendly — JS injection via
AssetCollector, works with full-page cache
Installation
composer require koh/webmcp
Configuration
Enable the Site Set
Include the site set koh/webmcp in your site configuration.
Settings
In config/sites/<site>/settings.yaml:
webmcp: enabled: true apiBasePath: '/_webmcp/api' bridge: enabled: true widget: # 'always' (default) shows the connection trigger to every visitor. # 'optin' keeps the bridge loaded and connectable but hides the trigger # until ?webmcp is in the URL or window.webmcp.showWidget() is called — # what a customer-facing site wants: the capability without the blue # square in the corner. The choice is remembered for the session. visibility: always # Chrome Origin Trial token — see "Browser Requirements" below originTrialToken: '' # Log registration failures to the browser console (development only) debug: false tools: search: enabled: true maxResults: 20 navigation: enabled: true maxDepth: 4 page-content: enabled: true
Browser Requirements
The native WebMCP API (document.modelContext) is gated on several conditions. When any
of them is not met, tool registration silently does nothing — the bridge widget keeps
working, so the failure is easy to miss.
-
Secure context. The API is
[SecureContext]; it is unavailable over plain HTTP (localhostcounts as secure). -
Policy-controlled feature
tools. The default allowlist is['self']. A restrictivePermissions-Policyheader that omitstoolsdisables the API for the whole document, and in an iframe the embedding page must opt in explicitly:<iframe src="https://example.org/" allow="tools"></iframe>
-
Origin Trial (Chrome 149–156). Outside the trial
document.modelContextisundefinedon real sites. Register the origin at Chrome Origin Trials and put the token intowebmcp.originTrialToken; the extension then renders<meta http-equiv="origin-trial" content="…">into the<head>.
Implementation status at the time of writing (spec draft 2026-07-21): Chrome 149+ behind the origin trial, Edge behind a flag, Firefox and Safari observing.
Entry point
The 2026-07-21 Community Draft moved the entry point from navigator.modelContext to
document.modelContext and dropped ModelContext.provideContext(). The extension reads
document.modelContext and falls back to navigator.modelContext for Chrome 149, which
shipped the old location.
Consent changes at runtime
Tools registered with a consent requirement are re-evaluated when consent changes:
Klaro via klaro.getManager().watch(), Cookiebot via its CookiebotOnConsentReady /
CookiebotOnAccept / CookiebotOnDecline events. With the native provider, update the
consent object and dispatch the event yourself:
window.__webmcp_consent = { functional: true }; window.dispatchEvent(new Event('webmcp:consent-changed'));
Withdrawing consent aborts the tool's AbortSignal, which unregisters it from the agent.
Built-in Tools
| Tool | Description | Input |
|---|---|---|
search |
Full-text search across pages and content elements | { query: string, limit?: int } |
navigation |
Page tree as structured JSON | { rootPageId?: int, maxDepth?: int } |
page-content |
Structured content of a specific page | { pageId?: int, slug?: string } |
In addition, every registered context is exposed as a read-only tool named
get-<context> (e.g. get-page-meta, get-site-info) taking no input. Since
provideContext() was removed from the spec, this is the only way context data still
reaches native agents. These tools appear in the JS config payload and in the
/.well-known discovery endpoints like any other tool.
In the browser they resolve from the context data already contained in the config
payload — no API round-trip. Page-scoped contexts such as page-meta are therefore
only available to browser agents; the API middleware runs before TYPO3's page resolver
and cannot rebuild them, so they are not advertised in the discovery endpoints either.
All tools respect TYPO3 access restrictions (fe_group, starttime, endtime, hidden). Only content visible to the current visitor (anonymous or logged-in) is returned.
Creating Custom Tools
1. Implement a ToolProvider
<?php use Koh\WebMcp\Domain\Contract\ToolProviderInterface; use Koh\WebMcp\Domain\Model\ToolAnnotations; use Koh\WebMcp\Domain\Model\ToolDefinition; final class ShopToolProvider implements ToolProviderInterface { public function getIdentifier(): string { return 'shop'; } public function getTools(): array { return [ new ToolDefinition( name: 'product-search', description: 'Search products in the shop', inputSchema: [ 'type' => 'object', 'properties' => [ 'query' => ['type' => 'string'], 'category' => ['type' => 'string'], ], 'required' => ['query'], ], annotations: new ToolAnnotations(readOnlyHint: true), ), ]; } }
2. Implement a ToolHandler
<?php use Koh\WebMcp\Domain\Contract\ToolHandlerInterface; use Koh\WebMcp\Domain\Model\ToolResult; use Psr\Http\Message\ServerRequestInterface; final class ProductSearchHandler implements ToolHandlerInterface { public function getToolName(): string { return 'product-search'; } public function execute(array $input, ServerRequestInterface $request): ToolResult { // Implement product search... return ToolResult::success(['products' => $results]); } }
Both classes are automatically discovered via _instanceof tags in Services.yaml — no additional configuration required.
How It Works
- When a page is rendered, the extension injects JavaScript via
AssetCollector - The JavaScript registers all enabled tools via
document.modelContext.registerTool(tool, { signal }) - When an AI agent calls a tool, the browser sends a
POSTrequest to/_webmcp/api/{toolName} - The PSR-15 middleware validates the request (CSRF nonce, input) and delegates to the appropriate ToolHandler
- The result is returned as JSON
Requirements
- TYPO3 13.4 or 14.0
- PHP 8.2 or higher
- HTTPS (SecureContext required)
License
MIT — Copyright (c) Moselwal Digitalagentur GmbH