Skip to content

Experimental · Unofficial

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

Vue 3

No adapter needed. A composable with onMounted / onUnmounted is the whole integration.

Composable

typescript
// composables/useContentstackWebMcp.ts
import { onMounted, onUnmounted, ref } from "vue";
import { createContentstackWebMcp, isWebMcpSupported } from "@timbenniks/contentstack-webmcp";
import type {
  ContentstackWebMcpConfig,
  WebMcpNavigateCallback,
} from "@timbenniks/contentstack-webmcp";

export function useContentstackWebMcp(
  config: ContentstackWebMcpConfig,
  navigate?: WebMcpNavigateCallback
) {
  const registeredTools = ref<string[]>([]);
  let cleanup = () => {};

  onMounted(async () => {
    if (!isWebMcpSupported()) return;

    const webmcp = createContentstackWebMcp(config);
    const controller = new AbortController();
    cleanup = () => controller.abort();

    registeredTools.value = await webmcp.register({
      signal: controller.signal,
      navigate: navigate ?? ((path) => window.location.assign(path)),
    });
  });

  onUnmounted(() => cleanup());

  return { registeredTools };
}

cleanup is assigned before the await, so unmounting mid-registration still aborts.

Usage

vue
<!-- App.vue — mount once, above <RouterView /> -->
<script setup lang="ts">
import contentstack from "@contentstack/delivery-sdk";
import { useRouter } from "vue-router";
import { useContentstackWebMcp } from "@/composables/useContentstackWebMcp";
import { contentTypes } from "@/content-types";

const router = useRouter();

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 { registeredTools } = useContentstackWebMcp({ mode: "direct", stack, contentTypes }, (path) =>
  router.push(path)
);
</script>

<template>
  <RouterView />
</template>

Mounting in App.vue rather than a page component means tools register once instead of on every route change.

TIP

router.push keeps navigate_to a client-side transition, so guards and scroll behaviour still apply. window.location.assign would do a full reload and re-register everything.

Proxy mode

Vue has no server of its own, so host the handler wherever you serve the app — see Server runtimes for Express, Hono, and the Vite dev-server middleware, which is handy locally.

typescript
useContentstackWebMcp({ mode: "proxy", proxyBasePath: "/api/contentstack", contentTypes }, (path) =>
  router.push(path)
);

Env vars

Vite exposes only VITE_-prefixed variables to the client, which is exactly the mode boundary: direct mode needs VITE_ vars, proxy mode keeps them unprefixed and server-side.

Nuxt

Nuxt has its own lifecycle and server routes — see Nuxt.

Released under the MIT License.