diff --git a/CHANGELOG.md b/CHANGELOG.md
index f54b2a1..f062727 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,11 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 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
+- **MCP Apps: Fix blank UI and wrong status badge in Claude**: Rewrote `useToolData` hook to use the official `useApp` hook from `@modelcontextprotocol/ext-apps/react` for proper lifecycle management. Updated UI types and components to match actual server response format (`success: boolean` instead of `status: string`, nested `data` object for workflow details). Validation summary now handles both direct and wrapped (`n8n_validate_workflow`) response shapes.
Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en
diff --git a/ui-apps/src/apps/operation-result/App.tsx b/ui-apps/src/apps/operation-result/App.tsx
index 231103e..fb24c04 100644
--- a/ui-apps/src/apps/operation-result/App.tsx
+++ b/ui-apps/src/apps/operation-result/App.tsx
@@ -19,7 +19,12 @@ export default function App() {
return
Waiting for data...
;
}
- const isSuccess = data.status === 'success';
+ const isSuccess = data.success === true;
+ const workflowName = data.data?.name || data.data?.workflowName;
+ const workflowId = data.data?.id || data.data?.workflowId;
+ const nodeCount = data.data?.nodeCount;
+ const isDeleted = data.data?.deleted === true;
+ const operationsApplied = data.data?.operationsApplied;
return (
@@ -27,65 +32,34 @@ export default function App() {
{isSuccess ? 'Success' : 'Error'}
-
{data.operation}
-
-
- {data.workflowName &&
Name: {data.workflowName}
}
- {data.workflowId &&
ID: {data.workflowId}
}
- {data.timestamp && (
-
- {data.timestamp}
-
- )}
-
-
-
- {data.message && (
-
- {data.message}
+ {(workflowName || workflowId) && (
+
+
+ {workflowName &&
Name: {workflowName}
}
+ {workflowId &&
ID: {workflowId}
}
+ {nodeCount !== undefined &&
Nodes: {nodeCount}
}
+ {isDeleted &&
Deleted
}
+ {operationsApplied !== undefined && (
+
Operations applied: {operationsApplied}
+ )}
+
)}
- {data.changes && (
- <>
- {data.changes.nodesAdded && data.changes.nodesAdded.length > 0 && (
-
-
- {data.changes.nodesAdded.map((node, i) => (
- -
- + {node}
-
- ))}
-
-
- )}
+ {(data.message || data.error) && (
+
+ {data.message || data.error}
+
+ )}
- {data.changes.nodesModified && data.changes.nodesModified.length > 0 && (
-
-
- {data.changes.nodesModified.map((node, i) => (
- -
- ~ {node}
-
- ))}
-
-
- )}
-
- {data.changes.nodesRemoved && data.changes.nodesRemoved.length > 0 && (
-
-
- {data.changes.nodesRemoved.map((node, i) => (
- -
- - {node}
-
- ))}
-
-
- )}
- >
+ {data.details && (
+
+
+ {JSON.stringify(data.details, null, 2)}
+
+
)}
);
diff --git a/ui-apps/src/apps/validation-summary/App.tsx b/ui-apps/src/apps/validation-summary/App.tsx
index 08f98a7..7fa9083 100644
--- a/ui-apps/src/apps/validation-summary/App.tsx
+++ b/ui-apps/src/apps/validation-summary/App.tsx
@@ -2,10 +2,10 @@ import React from 'react';
import '@shared/styles/theme.css';
import { Card, Badge, Expandable } from '@shared/components';
import { useToolData } from '@shared/hooks/useToolData';
-import type { ValidationSummaryData } from '@shared/types';
+import type { ValidationSummaryData, ValidationError, ValidationWarning } from '@shared/types';
export default function App() {
- const { data, error, isConnected } = useToolData();
+ const { data: raw, error, isConnected } = useToolData();
if (error) {
return Error: {error}
;
@@ -15,35 +15,46 @@ export default function App() {
return Connecting...
;
}
- if (!data) {
+ if (!raw) {
return Waiting for data...
;
}
+ // n8n_validate_workflow wraps result in { success, data: {...} }
+ // validate_node and validate_workflow return data directly
+ const inner = raw.data || raw;
+ const valid = inner.valid ?? raw.valid ?? false;
+ const displayName = raw.displayName || raw.data?.workflowName;
+ const errors: ValidationError[] = inner.errors || raw.errors || [];
+ const warnings: ValidationWarning[] = inner.warnings || raw.warnings || [];
+ const suggestions: string[] = inner.suggestions || raw.suggestions || [];
+ const errorCount = raw.summary?.errorCount ?? inner.summary?.errorCount ?? errors.length;
+ const warningCount = raw.summary?.warningCount ?? inner.summary?.warningCount ?? warnings.length;
+
return (
-
- {data.valid ? 'Valid' : 'Invalid'}
+
+ {valid ? 'Valid' : 'Invalid'}
- {data.displayName && (
- {data.displayName}
+ {displayName && (
+ {displayName}
)}
- {data.errorCount} errors
+ {errorCount} errors
- {data.warningCount} warnings
+ {warningCount} warnings
- {data.errors.length > 0 && (
-
- {data.errors.map((err, i) => (
+ {errors.length > 0 && (
+
+ {errors.map((err, i) => (
-
{err.type}
+ {(err.type || err.node) && (
+
{err.type || err.node}
+ )}
{err.property &&
Property: {err.property}
}
{err.message}
{err.fix && (
@@ -63,9 +76,9 @@ export default function App() {
)}
- {data.warnings.length > 0 && (
-
- {data.warnings.map((warn, i) => (
+ {warnings.length > 0 && (
+
+ {warnings.map((warn, i) => (
-
{warn.type}
+ {(warn.type || warn.node) && (
+
{warn.type || warn.node}
+ )}
{warn.property &&
Property: {warn.property}
}
{warn.message}
@@ -82,10 +97,10 @@ export default function App() {
)}
- {data.suggestions && data.suggestions.length > 0 && (
-
+ {suggestions.length > 0 && (
+
- {data.suggestions.map((suggestion, i) => (
+ {suggestions.map((suggestion, i) => (
- {suggestion}
))}
diff --git a/ui-apps/src/shared/types.ts b/ui-apps/src/shared/types.ts
index 07e1ede..6a81e88 100644
--- a/ui-apps/src/shared/types.ts
+++ b/ui-apps/src/shared/types.ts
@@ -1,38 +1,67 @@
+// Matches the McpToolResponse format from handlers-n8n-manager.ts
export interface OperationResultData {
- status: 'success' | 'error';
- operation: string;
- workflowName?: string;
- workflowId?: string;
- timestamp?: string;
- message?: string;
- changes?: {
- nodesAdded?: string[];
- nodesModified?: string[];
- nodesRemoved?: string[];
+ success: boolean;
+ data?: {
+ id?: string;
+ name?: string;
+ active?: boolean;
+ nodeCount?: number;
+ workflowId?: string;
+ workflowName?: string;
+ deleted?: boolean;
+ operationsApplied?: number;
+ [key: string]: unknown;
};
+ message?: string;
+ error?: string;
details?: Record;
}
export interface ValidationError {
- type: string;
+ type?: string;
property?: string;
message: string;
fix?: string;
+ node?: string;
+ details?: unknown;
}
export interface ValidationWarning {
- type: string;
+ type?: string;
property?: string;
message: string;
+ node?: string;
+ details?: unknown;
}
+// Matches the validate_node / validate_workflow response format from server.ts
export interface ValidationSummaryData {
valid: boolean;
- errorCount: number;
- warningCount: number;
+ nodeType?: string;
+ displayName?: string;
errors: ValidationError[];
warnings: ValidationWarning[];
suggestions?: string[];
- nodeType?: string;
- displayName?: string;
+ summary?: {
+ errorCount?: number;
+ warningCount?: number;
+ hasErrors?: boolean;
+ suggestionCount?: number;
+ [key: string]: unknown;
+ };
+ // n8n_validate_workflow wraps result in success/data
+ success?: boolean;
+ data?: {
+ valid?: boolean;
+ workflowId?: string;
+ workflowName?: string;
+ errors?: ValidationError[];
+ warnings?: ValidationWarning[];
+ suggestions?: string[];
+ summary?: {
+ errorCount?: number;
+ warningCount?: number;
+ [key: string]: unknown;
+ };
+ };
}