mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-03 17:13:07 +00:00
fix: MCP App UI - use official ext-apps hook + align types with server responses (#579)
* fix: use official ext-apps useApp hook to fix blank MCP App rendering 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 <noreply@anthropic.com> * fix: align MCP App UI types with actual server response format - useToolData hook now uses official useApp from ext-apps/react - OperationResultData uses success:boolean + data.id/name (matching McpToolResponse from handlers-n8n-manager.ts) - ValidationSummaryData handles both direct results (validate_node, validate_workflow) and wrapped results (n8n_validate_workflow) - Added visible error/connection states for debugging Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
a57b400bd0
commit
020bc3d43d
@@ -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<ValidationSummaryData>();
|
||||
const { data: raw, error, isConnected } = useToolData<ValidationSummaryData>();
|
||||
|
||||
if (error) {
|
||||
return <div style={{ padding: '16px', color: '#ef4444' }}>Error: {error}</div>;
|
||||
@@ -15,35 +15,46 @@ export default function App() {
|
||||
return <div style={{ padding: '16px', color: '#9ca3af' }}>Connecting...</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
if (!raw) {
|
||||
return <div style={{ padding: '16px', color: '#9ca3af' }}>Waiting for data...</div>;
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div style={{ maxWidth: '480px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
|
||||
<Badge variant={data.valid ? 'success' : 'error'}>
|
||||
{data.valid ? 'Valid' : 'Invalid'}
|
||||
<Badge variant={valid ? 'success' : 'error'}>
|
||||
{valid ? 'Valid' : 'Invalid'}
|
||||
</Badge>
|
||||
{data.displayName && (
|
||||
<span style={{ fontSize: '14px', color: 'var(--n8n-text-muted)' }}>{data.displayName}</span>
|
||||
{displayName && (
|
||||
<span style={{ fontSize: '14px', color: 'var(--n8n-text-muted)' }}>{displayName}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<div style={{ display: 'flex', gap: '16px', fontSize: '13px' }}>
|
||||
<div>
|
||||
<span style={{ color: 'var(--n8n-error)' }}>{data.errorCount}</span> errors
|
||||
<span style={{ color: 'var(--n8n-error)' }}>{errorCount}</span> errors
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ color: 'var(--n8n-warning)' }}>{data.warningCount}</span> warnings
|
||||
<span style={{ color: 'var(--n8n-warning)' }}>{warningCount}</span> warnings
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{data.errors.length > 0 && (
|
||||
<Expandable title="Errors" count={data.errors.length} defaultOpen>
|
||||
{data.errors.map((err, i) => (
|
||||
{errors.length > 0 && (
|
||||
<Expandable title="Errors" count={errors.length} defaultOpen>
|
||||
{errors.map((err, i) => (
|
||||
<div key={i} style={{
|
||||
padding: '8px',
|
||||
marginBottom: '6px',
|
||||
@@ -52,7 +63,9 @@ export default function App() {
|
||||
fontSize: '12px',
|
||||
color: 'var(--n8n-error)',
|
||||
}}>
|
||||
<div style={{ fontWeight: 600 }}>{err.type}</div>
|
||||
{(err.type || err.node) && (
|
||||
<div style={{ fontWeight: 600 }}>{err.type || err.node}</div>
|
||||
)}
|
||||
{err.property && <div style={{ opacity: 0.8 }}>Property: {err.property}</div>}
|
||||
<div>{err.message}</div>
|
||||
{err.fix && (
|
||||
@@ -63,9 +76,9 @@ export default function App() {
|
||||
</Expandable>
|
||||
)}
|
||||
|
||||
{data.warnings.length > 0 && (
|
||||
<Expandable title="Warnings" count={data.warnings.length}>
|
||||
{data.warnings.map((warn, i) => (
|
||||
{warnings.length > 0 && (
|
||||
<Expandable title="Warnings" count={warnings.length}>
|
||||
{warnings.map((warn, i) => (
|
||||
<div key={i} style={{
|
||||
padding: '8px',
|
||||
marginBottom: '6px',
|
||||
@@ -74,7 +87,9 @@ export default function App() {
|
||||
fontSize: '12px',
|
||||
color: 'var(--n8n-warning)',
|
||||
}}>
|
||||
<div style={{ fontWeight: 600 }}>{warn.type}</div>
|
||||
{(warn.type || warn.node) && (
|
||||
<div style={{ fontWeight: 600 }}>{warn.type || warn.node}</div>
|
||||
)}
|
||||
{warn.property && <div style={{ opacity: 0.8 }}>Property: {warn.property}</div>}
|
||||
<div>{warn.message}</div>
|
||||
</div>
|
||||
@@ -82,10 +97,10 @@ export default function App() {
|
||||
</Expandable>
|
||||
)}
|
||||
|
||||
{data.suggestions && data.suggestions.length > 0 && (
|
||||
<Expandable title="Suggestions" count={data.suggestions.length}>
|
||||
{suggestions.length > 0 && (
|
||||
<Expandable title="Suggestions" count={suggestions.length}>
|
||||
<ul style={{ paddingLeft: '16px', fontSize: '12px' }}>
|
||||
{data.suggestions.map((suggestion, i) => (
|
||||
{suggestions.map((suggestion, i) => (
|
||||
<li key={i} style={{ padding: '2px 0', color: 'var(--n8n-info)' }}>{suggestion}</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user