Skip to content

Experimental · Unofficial

Built by Tim Benniks. This is not an official Contentstack product and is not supported by Contentstack.

Execution modes ​

Two ways for a tool handler to reach the CDA. Direct mode is the default: no backend, the Delivery SDK runs in the tab. Proxy mode puts a same-origin route in between when you need the delivery token off the client.

Direct mode (default) ​

Tool handlers call the Delivery SDK in the browser tab. No API routes, no server.

typescript
import contentstack from "@contentstack/delivery-sdk";
import { createContentstackWebMcp } from "@timbenniks/contentstack-webmcp";

const stack = contentstack.stack({
  apiKey: import.meta.env.VITE_CONTENTSTACK_API_KEY,
  deliveryToken: import.meta.env.VITE_CONTENTSTACK_DELIVERY_TOKEN,
  environment: import.meta.env.VITE_CONTENTSTACK_ENVIRONMENT,
});

const webmcp = createContentstackWebMcp({ mode: "direct", stack, contentTypes });

That's the whole integration. Works in static HTML, Astro islands, Vue, Svelte, Angular, and any bundler, with no server-side component at all.

What you are accepting ​

The delivery token ships in the JS bundle. It is read-only and scoped to one environment, and the content it reads is content the page already serves to every visitor — so for a public site the token itself is not much of a secret.

The consequence that does matter:

WARNING

A delivery token grants read access to every content type in that environment, not just the ones in your contentTypes config. Your allowlist is a tool-layer convenience, not a security boundary — anyone can lift the token from the bundle and query content types you never exposed.

So the prerequisite for direct mode is: everything published in that environment is content you are happy to have public. If the environment holds published-but-unlinked entries, staging pages, or internal records, use proxy mode.

Secondary considerations:

  • CDA calls are metered. A token in a public bundle can be scraped and hammered.
  • No shared cache — every tab fetches for itself.
  • Your CSP must allow requests to the Contentstack CDA host.

Proxy mode ​

Tool handlers call same-origin routes; the routes use the Delivery SDK server-side.

typescript
// Server — credentials live here
import { createContentstackWebMcpProxyHandler } from "@timbenniks/contentstack-webmcp/server";

export default createContentstackWebMcpProxyHandler({ stack, contentTypes });

// Browser — no stack
createContentstackWebMcp({ mode: "proxy", proxyBasePath: "/api/contentstack", contentTypes });

Reach for it when you need any of:

  • The contentTypes allowlist actually enforced — the token never leaves your server, so it is the only path to the CDA
  • Delivery token kept out of the bundle
  • Shared caching, rate limiting, or error normalization in one place
  • Custom server-side queries via customRoutes
  • No CDA host in your CSP

Works with Node, Deno, Cloudflare Workers, and any Web Request/Response runtime. See Proxy endpoints for the route reference.

Comparison ​

DirectProxy
Server requiredNoYes
Token in browserYes (read-only, environment-scoped)No
contentTypes allowlist enforcedTool layer onlyYes, server-side
Shared caching / rate limitingNoYes
Custom server routesN/AYes (customRoutes)
CDA host in CSPRequiredNot required
Suitable for productionYes, when the environment is fully publicYes

Choosing a mode ​

Public site, environment holds only public content  → direct
Environment holds unlisted or internal entries       → proxy
Need caching, quotas, or metering control            → proxy
Need custom server-side CDA queries                  → proxy + customRoutes
No backend available at all                          → direct

Both modes register identical tools and return identical response shapes, so switching later is a one-line config change plus a route.

Released under the MIT License.