From a57b400bd044f92fae20fd35e8b774efcbdac816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romuald=20Cz=C5=82onkowski?= <56956555+czlonkowski@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:25:27 +0100 Subject: [PATCH] fix: use official ext-apps useApp hook to fix blank MCP App rendering (#578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The custom useToolData hook had lifecycle issues that prevented the UI from rendering in Claude Desktop/web: no appInfo in App constructor, unhandled connect() Promise, app.close() on unmount conflicting with React Strict Mode. Switched to the official useApp hook from @modelcontextprotocol/ext-apps/react which handles initialization handshake, handler registration, and cleanup correctly. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-authored-by: Claude Opus 4.6 --- CHANGELOG.md | 12 +++++++ package.json | 2 +- ui-apps/src/apps/operation-result/App.tsx | 12 +++++-- ui-apps/src/apps/validation-summary/App.tsx | 12 +++++-- ui-apps/src/shared/hooks/useToolData.ts | 36 ++++++++++++--------- 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24635df..f54b2a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.34.4] - 2026-02-07 + +### Fixed + +- **MCP Apps: Fix blank UI rendering in Claude**: Rewrote `useToolData` hook to use the official `useApp` hook from `@modelcontextprotocol/ext-apps/react` instead of manually managing `App` lifecycle + - Proper initialization handshake with host via `appInfo` and `capabilities` + - Handlers registered via `onAppCreated` callback (before `connect()`) to avoid race conditions + - Removed `app.close()` on unmount which caused issues with React Strict Mode + - Added visible error and connection states with inline colors for debugging + +Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en + ## [2.34.3] - 2026-02-07 ### Fixed diff --git a/package.json b/package.json index 4dde40a..5dce88b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp", - "version": "2.34.3", + "version": "2.34.4", "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/ui-apps/src/apps/operation-result/App.tsx b/ui-apps/src/apps/operation-result/App.tsx index 37cba97..231103e 100644 --- a/ui-apps/src/apps/operation-result/App.tsx +++ b/ui-apps/src/apps/operation-result/App.tsx @@ -5,10 +5,18 @@ import { useToolData } from '@shared/hooks/useToolData'; import type { OperationResultData } from '@shared/types'; export default function App() { - const data = useToolData(); + const { data, error, isConnected } = useToolData(); + + if (error) { + return
Error: {error}
; + } + + if (!isConnected) { + return
Connecting...
; + } if (!data) { - return
Loading...
; + return
Waiting for data...
; } const isSuccess = data.status === 'success'; diff --git a/ui-apps/src/apps/validation-summary/App.tsx b/ui-apps/src/apps/validation-summary/App.tsx index 05b88ab..08f98a7 100644 --- a/ui-apps/src/apps/validation-summary/App.tsx +++ b/ui-apps/src/apps/validation-summary/App.tsx @@ -5,10 +5,18 @@ import { useToolData } from '@shared/hooks/useToolData'; import type { ValidationSummaryData } from '@shared/types'; export default function App() { - const data = useToolData(); + const { data, error, isConnected } = useToolData(); + + if (error) { + return
Error: {error}
; + } + + if (!isConnected) { + return
Connecting...
; + } if (!data) { - return
Loading...
; + return
Waiting for data...
; } return ( diff --git a/ui-apps/src/shared/hooks/useToolData.ts b/ui-apps/src/shared/hooks/useToolData.ts index d86332d..ef9acb8 100644 --- a/ui-apps/src/shared/hooks/useToolData.ts +++ b/ui-apps/src/shared/hooks/useToolData.ts @@ -1,14 +1,17 @@ -import { useState, useEffect } from 'react'; -import { App } from '@modelcontextprotocol/ext-apps'; +import { useState, useCallback } from 'react'; +import { useApp } from '@modelcontextprotocol/ext-apps/react'; -export function useToolData(): T | null { +interface UseToolDataResult { + data: T | null; + error: string | null; + isConnected: boolean; +} + +export function useToolData(): UseToolDataResult { const [data, setData] = useState(null); - useEffect(() => { - const app = new App(); - + const onAppCreated = useCallback((app: any) => { app.ontoolresult = (result: any) => { - // The host pushes tool result content; parse the first text item as JSON if (result?.content) { const textItem = Array.isArray(result.content) ? result.content.find((c: any) => c.type === 'text') @@ -17,19 +20,22 @@ export function useToolData(): T | null { try { setData(JSON.parse(textItem.text) as T); } catch { - // Not JSON — use raw text as-is setData(textItem.text as unknown as T); } } } }; - - app.connect(); - - return () => { - app.close(); - }; }, []); - return data; + const { isConnected, error } = useApp({ + appInfo: { name: 'n8n-mcp-ui', version: '1.0.0' }, + capabilities: {}, + onAppCreated, + }); + + return { + data, + error: error?.message ?? null, + isConnected, + }; }