Skip to content

Experimental · Unofficial

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

Nuxt

No adapter needed. A .client.ts plugin is the idiomatic home for this — it never runs during SSR, so there is no document guard to think about.

Client plugin

typescript
// plugins/webmcp.client.ts
import contentstack from "@contentstack/delivery-sdk";
import { createContentstackWebMcp, isWebMcpSupported } from "@timbenniks/contentstack-webmcp";
import { contentTypes } from "~/content-types";

export default defineNuxtPlugin(async () => {
  if (!isWebMcpSupported()) return;

  const config = useRuntimeConfig();
  const router = useRouter();

  const stack = contentstack.stack({
    apiKey: config.public.contentstackApiKey,
    deliveryToken: config.public.contentstackDeliveryToken,
    environment: config.public.contentstackEnvironment,
  });

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

  await webmcp.register({
    signal: controller.signal,
    navigate: (path) => router.push(path),
  });

  window.addEventListener("pagehide", () => controller.abort());
});

The .client suffix is what keeps this out of the server bundle. A plugin runs once per page load and survives client-side navigation, so tools register a single time.

Runtime config

Nuxt splits public and private for you, and that split is the mode boundary.

typescript
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // Proxy mode — server only, never sent to the browser
    contentstackApiKey: process.env.CONTENTSTACK_API_KEY,
    contentstackDeliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
    contentstackEnvironment: process.env.CONTENTSTACK_ENVIRONMENT,

    public: {
      // Direct mode — the browser needs these
      contentstackApiKey: process.env.NUXT_PUBLIC_CONTENTSTACK_API_KEY,
      contentstackDeliveryToken: process.env.NUXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN,
      contentstackEnvironment: process.env.NUXT_PUBLIC_CONTENTSTACK_ENVIRONMENT,
    },
  },
});

WARNING

Use public only for a read-only delivery token, and only in direct mode. Anything under public is serialised into the page payload.

Proxy mode

Nitro server routes need one adapter call — toWebRequest converts the H3 event to a Web Request.

typescript
// server/api/contentstack/[...path].ts
import contentstack from "@contentstack/delivery-sdk";
import { createContentstackWebMcpProxyHandler } from "@timbenniks/contentstack-webmcp/server";
import { contentTypes } from "~/content-types";

const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY!,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN!,
  environment: process.env.CONTENTSTACK_ENVIRONMENT!,
});

const handler = createContentstackWebMcpProxyHandler({
  stack,
  contentTypes,
  basePath: "/api/contentstack",
});

export default defineEventHandler((event) => handler(toWebRequest(event)));

Then in the plugin, drop the stack and switch to { mode: "proxy", proxyBasePath: "/api/contentstack", contentTypes } — no public runtime config needed at all.

Permissions-Policy

typescript
// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/**": { headers: { "Permissions-Policy": "tools=(self)" } },
  },
});

With nuxt generate (static output) there is no server, so set it at your host instead — see Server runtimes.

Released under the MIT License.