---
title: "Patterns & limits"
description: "UI patterns, unsupported Nuxt features in the iframe, and API reference."
canonical_url: "https://mcp-toolkit.nuxt.dev/apps/patterns-reference"
---
# Patterns & limits

> UI patterns, unsupported Nuxt features in the iframe, and API reference.

## Patterns

### Skeletons during `callTool`

`pending` flips to `true` only during in-flight `callTool` calls — perfect for partial UI updates without losing the previous data:

```vue
<button :disabled="pending" @click="callTool('color-picker', { base: nextBase })">
  {{ pending ? 'Mixing…' : 'Try this colour' }}
</button>
```

### Adapting to fullscreen vs inline

Hosts can mount your app inline (small) or expand it fullscreen, on their own or after `requestDisplayMode()`. `hostContext.displayMode` follows every change the host reports, so switch layout primitives on it:

```vue
<section :class="['rail', isFullscreen && 'rail-grid']">
  <!-- horizontal scroll inline, CSS grid in fullscreen -->
</section>
```

### Reading vs writing through tools

In Claude (as of September 2026), a tool call from the iframe only goes through when the tool is read-only (`annotations: { readOnlyHint: true }`); calls to tools that write are dropped, whether the tool is model-visible or app-only, and time out. Read through `callTool` / `useToolCall`, and route writes through the model with `sendPrompt`.

### App-to-app workflows

Use `sendPrompt` from app A and let the LLM dispatch to app B. The user perceives a smooth transition; the LLM stays in the loop and can carry parameters through:

```vue
<button @click="sendPrompt(`Generate a typography scale for ${swatch.hex}.`)">
  Pair with type
</button>
```

The LLM picks the right tool from the prompt — keep names predictable.

### Co-locating CSS

Use scoped styles or any CSS-in-JS solution that emits inline styles. The bundler inlines all styles into the HTML; **no external CSS request fires from the iframe**.

## What You Cannot Do (Yet)

MCP Apps run inside an isolated `iframe` with no access to your Nuxt runtime context. That means:

- ❌ **Nuxt UI / Nuxt Image / NuxtLink** — they need the parent Nuxt app's runtime.
- ❌ **Auto-imported Nuxt composables** (`useFetch`, `useState`, `useRoute`, `useNuxtApp`, …).
- ❌ **Pinia stores or app-level plugins** — different module graph.
- ❌ **Cookies, headers, session from the parent page** — sandboxed origin.

What you can use:

- ✅ Vue 3 + Composition API (auto-imported: `ref`, `computed`, `watch`, `onMounted`, …).
- ✅ `useMcpApp()` — the only toolkit composable in the iframe.
- ✅ `$fetch` inside `handler` (server side) — call any Nuxt API.
- ✅ Co-located `.ts`/`.css` helpers next to the SFC (the bundler inlines them).
- ✅ Headless UI libraries (Reka UI, Headless UI Vue, Floating UI) — they're framework-agnostic.

<callout color="info" icon="i-lucide-info">

Need rich components? Build a small co-located design system in your apps directory, such as `app/mcp/_components/` — it gets bundled into every app that imports it. Or pull in a headless library that doesn't depend on the host runtime.

</callout>

## Reference

<table>
<thead>
  <tr>
    <th>
      API
    </th>
    
    <th>
      Where
    </th>
    
    <th>
      Purpose
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        defineMcpApp()
      </code>
    </td>
    
    <td>
      <code>
        <script setup>
      </code>
    </td>
    
    <td>
      Declare the tool, schema, handler, CSP. Stripped from the browser bundle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useMcpApp<T>()
      </code>
    </td>
    
    <td>
      SFC body
    </td>
    
    <td>
      Reactive <code>
        initialData
      </code>
      
       / <code>
        data
      </code>
      
       / <code>
        meta
      </code>
      
       / <code>
        loading
      </code>
      
       / <code>
        hostContext
      </code>
      
       / <code>
        hostCapabilities
      </code>
      
       + <code>
        callTool
      </code>
      
      , <code>
        sendPrompt
      </code>
      
      , <code>
        openLink
      </code>
      
      , <code>
        requestDisplayMode
      </code>
      
      , <code>
        updateModelContext
      </code>
      
      , <code>
        downloadFile
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useToolCall<T>()
      </code>
    </td>
    
    <td>
      SFC body, imported from <code>
        @nuxtjs/mcp-toolkit/app
      </code>
    </td>
    
    <td>
      Call a tool without writing its result into <code>
        data
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        csp.resourceDomains
      </code>
    </td>
    
    <td>
      <code>
        defineMcpApp
      </code>
    </td>
    
    <td>
      Allow-list image / font / style origins.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        csp.connectDomains
      </code>
    </td>
    
    <td>
      <code>
        defineMcpApp
      </code>
    </td>
    
    <td>
      Allow-list <code>
        fetch
      </code>
      
       / <code>
        XHR
      </code>
      
       / <code>
        WebSocket
      </code>
      
       origins.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        _meta
      </code>
    </td>
    
    <td>
      <code>
        defineMcpApp
      </code>
    </td>
    
    <td>
      Extra <code>
        _meta
      </code>
      
       fields surfaced to the host alongside <code>
        ui.resourceUri
      </code>
      
       and <code>
        ui.csp
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

Looking for the underlying tool / resource APIs? See [Tools](https://mcp-toolkit.nuxt.dev/tools/overview) and [Resources](https://mcp-toolkit.nuxt.dev/resources/overview).


## Sitemap

See the full [sitemap](https://mcp-toolkit.nuxt.dev/sitemap.md) for all pages.
