Architecture
Overview
flowchart TB
subgraph browser [Browser tab — any framework]
Agent[Browser agent]
Core["@timbenniks/contentstack-webmcp core"]
Adapter["Optional /react, /next"]
end
subgraph site [Customer site]
ProxyAPI["Proxy routes /server"]
DirectSDK["Delivery SDK in the browser tab"]
end
subgraph cs [Contentstack]
CDA[Content Delivery API]
end
Agent --> Core
Adapter -.->|thin wrapper| Core
Core -.->|"proxy mode"| ProxyAPI
Core -->|"direct mode (default)"| DirectSDK
ProxyAPI --> CDA
DirectSDK --> CDAPackage layering
| Layer | Export path | Depends on | Purpose |
|---|---|---|---|
| Core | @timbenniks/contentstack-webmcp | delivery-sdk (peer) | Tool factory, registration, browser detection |
| Server | @timbenniks/contentstack-webmcp/server | delivery-sdk | Framework-agnostic proxy handlers |
| Serializers | @timbenniks/contentstack-webmcp/serializers | none | Entry to markdown/summary transforms |
| React | @timbenniks/contentstack-webmcp/react | react (peer) | Hook and provider wrapper |
| Next.js | @timbenniks/contentstack-webmcp/next | next (peer) | App Router route re-exports over /server |
Why a companion package?
The Delivery SDK plugin API intercepts CDA HTTP requests. WebMCP operates at a different layer:
typescript
// Delivery SDK plugin — shapes HTTP traffic
class CrossStackPlugin {
onRequest(request) {
return request;
}
async onResponse(request, response, data) {
return response;
}
}
// WebMCP — shapes browser capabilities
document.modelContext.registerTool({
name: "contentstack_search_entries",
description: "...",
inputSchema: {/* JSON Schema */},
execute: async (input) => {
/* structured result */
},
});Plugins cannot register browser tools, define JSON schemas for agents, or manage browser lifecycle. A companion package is the correct abstraction.
Request flow (proxy mode)
1. Browser agent calls contentstack_search_entries
2. Tool execute handler → fetch("/api/contentstack/search?query=...")
3. Server proxy handler → Delivery SDK → CDA
4. JSON response → MCP text content → agentShared CDA layer
Both the browser direct client and server proxy handlers use src/cda/queries.ts:
searchEntriesgetEntryByUrlgetEntryByUidlistContentTypes
Custom queries extend this via createCdaClient() or customRoutes.
Progressive enhancement
isWebMcpEnabled() → env kill switch
isWebMcpSupported() → feature detect document.modelContext
registerToolSafely() → try/catch per tool
AbortSignal → cleanup on route change / pagehideUnsupported browsers: complete no-op, zero console errors.