n8n/packages/@n8n/mcp-apps
n8n-assistant[bot] 662a23c202
🚀 Release 2.32.0 (#34600)
Co-authored-by: n8n-release-helper[bot] <265227495+n8n-release-helper[bot]@users.noreply.github.com>
2026-07-21 07:14:52 +00:00
..
src feat(ai-builder): Capture compiled workflow JSON as a trace event for eval seed reconstruction (no-changelog) (#33302) 2026-07-06 15:03:38 +00:00
.gitignore feat(core): Introduce mcp-apps package (no-changelog) (#31298) 2026-06-01 15:39:41 +00:00
eslint.config.mjs feat(core): Introduce mcp-apps package (no-changelog) (#31298) 2026-06-01 15:39:41 +00:00
package.json 🚀 Release 2.32.0 (#34600) 2026-07-21 07:14:52 +00:00
README.md fix(core): Narrow mcp apps frame domains (no-changelog) (#33123) 2026-06-30 13:30:10 +00:00
tsconfig.build.json chore: Migrate the rest of packages/@n8n to typescript 7 (#34332) 2026-07-17 10:45:58 +03:00
tsconfig.json chore: Migrate the rest of packages/@n8n to typescript 7 (#34332) 2026-07-17 10:45:58 +03:00
vite.config.mts feat(core): Implement preview-workflow app (#31647) 2026-06-08 07:35:30 +00:00
vitest.config.mts ci: Include vue plugin to mcp-apps (#33267) 2026-06-30 12:31:09 +03:00

@n8n/mcp-apps

UI resources and server helpers that let the n8n MCP server return MCP Apps — small, sandboxed HTML/Vue experiences rendered inside MCP clients that support the @modelcontextprotocol/ext-apps extension. The package owns both the runtime UI bundles and the small server helpers used by packages/cli to register them as MCP resources and tools.

What it provides

  • Server helpers (@n8n/mcp-apps/server) for registering MCP App tools and the static HTML resources that back them.
  • Vue UI apps under src/apps/*, built with Vite as standalone, fully inlined HTML files (CSS + JS in a single document) so they can be served directly as MCP resources.
  • i18n plumbing powered by vue-i18n, with locale negotiation driven by the host context the MCP client provides at runtime.

Today the package ships a single app, workflow-preview, which is rendered after the create_workflow_from_code MCP tool returns. It loads the sanitized workflow graph through the existing get_workflow_details MCP tool, renders the existing n8n demo canvas in an iframe, and keeps a button to open the freshly created workflow in n8n. New apps can be added alongside it (see Adding a new app).

Package layout

src/
  apps-manifest.ts          # single source of truth for the apps registry
  apps/                     # Vue UI apps, each built into a standalone HTML
    workflow-preview/
      App.vue               # root component
      main.ts               # mounts App with i18n
      index.html            # entry HTML (built into dist/apps/<app>.html)
      tokens.scss           # design tokens / global styles
      types.ts              # workflow preview data types
      type-guards.ts        # workflow preview data guards
      composables/
        use-workflow-preview.ts # workflow preview state and host tool handling
      utils/
        url.ts              # defense-in-depth URL validation
  components/               # reusable MCP app Vue components
    workflow-preview/       # workflow-preview-specific reusable components
  composables/              # reusable MCP host/runtime composables
  i18n/                     # vue-i18n setup + host locale resolution
  locales/                  # flat-key locale files (en.json, …)
  server/                   # consumed by packages/cli
    apps/                   # MCP resource registrations for each UI app
    constants.ts            # shared URIs, MIME type, _meta keys
    register-mcp-app-tool.ts
    resource-loader.ts      # lazy reads built HTML from dist/apps
    index.ts                # public entry: @n8n/mcp-apps/server
  utils/                    # framework-agnostic client helpers

apps-manifest.ts is the canonical registry of MCP apps. Both the Vite build (entry directory + output HTML filename per --mode) and the server-side resource loader (compile-time union + runtime allow-list of loadable HTML files) derive from it, so the build and runtime stay in lockstep and there is no separate list to maintain.

The Vite build (pnpm build:ui) emits one inlined HTML file per app into dist/apps/<app>.html. The TypeScript build (pnpm build:server) emits the server helpers into dist/server/. Both run as part of pnpm build.

UI runtime

Each app:

  • Connects to the host via @modelcontextprotocol/ext-apps's App class.
  • Receives a McpUiHostContext (theme, style variables, host fonts, locale) through onhostcontextchanged and reflects it on the document.
  • Reads the originating tool's structuredContent via ontoolresult to populate its own state.
  • Calls app.callServerTool(...) when it needs fresh n8n data from the MCP server. The workflow preview uses this to call get_workflow_details with the created workflow ID.
  • Calls app.openLink({ url }) to ask the host to navigate — never opens links itself.

URL handling is locked down by isAllowedWorkflowUrl in src/apps/workflow-preview/utils/url.ts: only http(s):// URLs with a non-empty host are accepted, both when reading the tool result and right before calling openLink. This is defense in depth on top of the host's own validation.

The workflow preview iframe loads the shared n8n preview service (WORKFLOW_PREVIEW_ORIGIN). The preview is instance-agnostic: the workflow graph is pushed into the iframe via postMessage rather than fetched from the instance, so a single origin renders both cloud and self-hosted workflows. The resource metadata therefore declares exactly one frameDomains entry — the preview-service origin. Keep this list narrow: MCP hosts (e.g. the ChatGPT connector review) reject broad or wildcard frame domains. The framed server's own frame policy still applies, so the app falls back to the open-workflow button when the preview cannot load.

Internationalization

Locale files live under src/locales/ and use flat, namespaced keys (workflowPreview.openButton, workflowPreview.ariaLabel.ready, …). The host's BCP 47 locale is resolved to a shipped locale via resolveLocale, applied to the vue-i18n instance, and mirrored to <html lang> for assistive tech. See src/i18n/index.ts for the full contract.

To add a new locale:

  1. Drop <code>.json next to en.json in src/locales/.
  2. Import it in src/i18n/index.ts and add the code to SUPPORTED_LOCALES.

The schema is derived from en.json, so other locales are type-checked against the same key set.

Adding a new app

  1. Create src/apps/<app-name>/ with index.html, main.ts, and an App.vue root component. Mount it through the shared i18n instance.

  2. Add an entry to MCP_APPS in src/apps-manifest.ts:

    '<app-name>': {
      entry: '<app-name>',           // directory under src/apps/
      htmlFile: '<app-name>.html',   // output under dist/apps/
    },
    

    This single entry teaches Vite about the new --mode, expands the McpAppHtmlFileName type union, and adds the file to the loadAppHtml runtime allow-list. pnpm build:ui --mode <app-name> will then produce dist/apps/<app-name>.html.

  3. Add the app's URI constant to src/server/constants.ts and a register<App>App helper in src/server/apps/ that calls server.resource(...) with loadAppHtml('<app-name>.html').

  4. Re-export the helper and URI constant from src/server/index.ts.

  5. Add any UI strings to src/locales/en.json under a new app-scoped key prefix.

SDK version compatibility

src/server/sdk-version.test.ts asserts that the @modelcontextprotocol/sdk version installed via the pnpm catalog satisfies the peer range declared by @modelcontextprotocol/ext-apps. CI fails the moment those two pins drift, so bumping one without the other is caught immediately.

Scripts

Command Description
pnpm build Build UI apps and server helpers
pnpm build:ui Build the Vue apps to inlined HTML in dist/apps/
pnpm build:server Build the server entry to dist/server/
pnpm typecheck Run vue-tsc over the UI and tsc over the server
pnpm lint Lint with the shared ESLint config
pnpm test Run unit tests with Vitest
pnpm test:dev Run Vitest in watch mode