Compare commits

..

1 Commits

Author SHA1 Message Date
Romuald Członkowski
38aa70261a fix: use text/html;profile=mcp-app MIME type for MCP Apps resources (#577)
The ext-apps spec requires RESOURCE_MIME_TYPE (text/html;profile=mcp-app)
for hosts to recognize resources as MCP Apps. Without the profile parameter,
Claude Desktop/web fails with "Failed to load MCP App: the resource may
exceed the 5 MB size limit."

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 15:18:50 +01:00
5 changed files with 20 additions and 54 deletions

View File

@@ -7,18 +7,6 @@ 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

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-mcp",
"version": "2.34.4",
"version": "2.34.3",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -5,18 +5,10 @@ import { useToolData } from '@shared/hooks/useToolData';
import type { OperationResultData } from '@shared/types';
export default function App() {
const { data, error, isConnected } = useToolData<OperationResultData>();
if (error) {
return <div style={{ padding: '16px', color: '#ef4444' }}>Error: {error}</div>;
}
if (!isConnected) {
return <div style={{ padding: '16px', color: '#9ca3af' }}>Connecting...</div>;
}
const data = useToolData<OperationResultData>();
if (!data) {
return <div style={{ padding: '16px', color: '#9ca3af' }}>Waiting for data...</div>;
return <div style={{ padding: '16px', color: 'var(--n8n-text-muted)' }}>Loading...</div>;
}
const isSuccess = data.status === 'success';

View File

@@ -5,18 +5,10 @@ import { useToolData } from '@shared/hooks/useToolData';
import type { ValidationSummaryData } from '@shared/types';
export default function App() {
const { data, error, isConnected } = useToolData<ValidationSummaryData>();
if (error) {
return <div style={{ padding: '16px', color: '#ef4444' }}>Error: {error}</div>;
}
if (!isConnected) {
return <div style={{ padding: '16px', color: '#9ca3af' }}>Connecting...</div>;
}
const data = useToolData<ValidationSummaryData>();
if (!data) {
return <div style={{ padding: '16px', color: '#9ca3af' }}>Waiting for data...</div>;
return <div style={{ padding: '16px', color: 'var(--n8n-text-muted)' }}>Loading...</div>;
}
return (

View File

@@ -1,17 +1,14 @@
import { useState, useCallback } from 'react';
import { useApp } from '@modelcontextprotocol/ext-apps/react';
import { useState, useEffect } from 'react';
import { App } from '@modelcontextprotocol/ext-apps';
interface UseToolDataResult<T> {
data: T | null;
error: string | null;
isConnected: boolean;
}
export function useToolData<T>(): UseToolDataResult<T> {
export function useToolData<T>(): T | null {
const [data, setData] = useState<T | null>(null);
const onAppCreated = useCallback((app: any) => {
useEffect(() => {
const app = new App();
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')
@@ -20,22 +17,19 @@ export function useToolData<T>(): UseToolDataResult<T> {
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();
};
}, []);
const { isConnected, error } = useApp({
appInfo: { name: 'n8n-mcp-ui', version: '1.0.0' },
capabilities: {},
onAppCreated,
});
return {
data,
error: error?.message ?? null,
isConnected,
};
return data;
}