mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-08 22:33:08 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a57b400bd0 | ||
|
|
38aa70261a | ||
|
|
1b328d8168 |
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -283,8 +283,8 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project (server + UI apps)
|
||||||
run: npm run build
|
run: npm run build:all
|
||||||
|
|
||||||
# Database is already built and committed during development
|
# Database is already built and committed during development
|
||||||
# Rebuilding here causes segfault due to memory pressure (exit code 139)
|
# Rebuilding here causes segfault due to memory pressure (exit code 139)
|
||||||
@@ -322,8 +322,8 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project (server + UI apps)
|
||||||
run: npm run build
|
run: npm run build:all
|
||||||
|
|
||||||
# Database is already built and committed during development
|
# Database is already built and committed during development
|
||||||
- name: Verify database exists
|
- name: Verify database exists
|
||||||
@@ -347,6 +347,8 @@ jobs:
|
|||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
cp -r dist $PUBLISH_DIR/
|
cp -r dist $PUBLISH_DIR/
|
||||||
cp -r data $PUBLISH_DIR/
|
cp -r data $PUBLISH_DIR/
|
||||||
|
mkdir -p $PUBLISH_DIR/ui-apps
|
||||||
|
cp -r ui-apps/dist $PUBLISH_DIR/ui-apps/
|
||||||
cp README.md $PUBLISH_DIR/
|
cp README.md $PUBLISH_DIR/
|
||||||
cp LICENSE $PUBLISH_DIR/
|
cp LICENSE $PUBLISH_DIR/
|
||||||
cp .env.example $PUBLISH_DIR/
|
cp .env.example $PUBLISH_DIR/
|
||||||
@@ -377,7 +379,7 @@ jobs:
|
|||||||
pkg.license = 'MIT';
|
pkg.license = 'MIT';
|
||||||
pkg.bugs = { url: 'https://github.com/czlonkowski/n8n-mcp/issues' };
|
pkg.bugs = { url: 'https://github.com/czlonkowski/n8n-mcp/issues' };
|
||||||
pkg.homepage = 'https://github.com/czlonkowski/n8n-mcp#readme';
|
pkg.homepage = 'https://github.com/czlonkowski/n8n-mcp#readme';
|
||||||
pkg.files = ['dist/**/*', 'data/nodes.db', '.env.example', 'README.md', 'LICENSE'];
|
pkg.files = ['dist/**/*', 'ui-apps/dist/**/*', 'data/nodes.db', '.env.example', 'README.md', 'LICENSE'];
|
||||||
delete pkg.private;
|
delete pkg.private;
|
||||||
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
|
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
|
||||||
"
|
"
|
||||||
|
|||||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
||||||
|
|
||||||
|
- **MCP Apps: Use correct MIME type for ext-apps spec**: Changed resource MIME type from `text/html` to `text/html;profile=mcp-app` (the `RESOURCE_MIME_TYPE` constant from `@modelcontextprotocol/ext-apps`). Without this profile parameter, Claude Desktop/web fails to recognize resources as MCP Apps and shows "Failed to load MCP App: the resource may exceed the 5 MB size limit."
|
||||||
|
|
||||||
|
Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en
|
||||||
|
|
||||||
|
## [2.34.2] - 2026-02-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CI: UI apps missing from npm package**: Release pipeline only ran `npm run build` (TypeScript), so `ui-apps/dist/` was never built and excluded from published packages
|
||||||
|
- Changed build step to `npm run build:all` in `build-and-verify` and `publish-npm` jobs
|
||||||
|
- Added `ui-apps/dist/` to npm publish staging directory
|
||||||
|
- Added `ui-apps/dist/**/*` to published package files list
|
||||||
|
|
||||||
|
Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en
|
||||||
|
|
||||||
## [2.34.1] - 2026-02-07
|
## [2.34.1] - 2026-02-07
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.34.1",
|
"version": "2.34.4",
|
||||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const UI_APP_CONFIGS: UIAppConfig[] = [
|
|||||||
displayName: 'Operation Result',
|
displayName: 'Operation Result',
|
||||||
description: 'Visual summary of workflow operations (create, update, delete, test)',
|
description: 'Visual summary of workflow operations (create, update, delete, test)',
|
||||||
uri: 'ui://n8n-mcp/operation-result',
|
uri: 'ui://n8n-mcp/operation-result',
|
||||||
mimeType: 'text/html',
|
mimeType: 'text/html;profile=mcp-app',
|
||||||
toolPatterns: [
|
toolPatterns: [
|
||||||
'n8n_create_workflow',
|
'n8n_create_workflow',
|
||||||
'n8n_update_full_workflow',
|
'n8n_update_full_workflow',
|
||||||
@@ -22,7 +22,7 @@ export const UI_APP_CONFIGS: UIAppConfig[] = [
|
|||||||
displayName: 'Validation Summary',
|
displayName: 'Validation Summary',
|
||||||
description: 'Visual summary of node and workflow validation results',
|
description: 'Visual summary of node and workflow validation results',
|
||||||
uri: 'ui://n8n-mcp/validation-summary',
|
uri: 'ui://n8n-mcp/validation-summary',
|
||||||
mimeType: 'text/html',
|
mimeType: 'text/html;profile=mcp-app',
|
||||||
toolPatterns: [
|
toolPatterns: [
|
||||||
'validate_node',
|
'validate_node',
|
||||||
'validate_workflow',
|
'validate_workflow',
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ describe('UI_APP_CONFIGS', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have consistent mimeType of text/html', () => {
|
it('should have consistent mimeType of text/html;profile=mcp-app', () => {
|
||||||
for (const config of UI_APP_CONFIGS) {
|
for (const config of UI_APP_CONFIGS) {
|
||||||
expect(config.mimeType).toBe('text/html');
|
expect(config.mimeType).toBe('text/html;profile=mcp-app');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,18 @@ import { useToolData } from '@shared/hooks/useToolData';
|
|||||||
import type { OperationResultData } from '@shared/types';
|
import type { OperationResultData } from '@shared/types';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const data = useToolData<OperationResultData>();
|
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>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <div style={{ padding: '16px', color: 'var(--n8n-text-muted)' }}>Loading...</div>;
|
return <div style={{ padding: '16px', color: '#9ca3af' }}>Waiting for data...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSuccess = data.status === 'success';
|
const isSuccess = data.status === 'success';
|
||||||
|
|||||||
@@ -5,10 +5,18 @@ import { useToolData } from '@shared/hooks/useToolData';
|
|||||||
import type { ValidationSummaryData } from '@shared/types';
|
import type { ValidationSummaryData } from '@shared/types';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const data = useToolData<ValidationSummaryData>();
|
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>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <div style={{ padding: '16px', color: 'var(--n8n-text-muted)' }}>Loading...</div>;
|
return <div style={{ padding: '16px', color: '#9ca3af' }}>Waiting for data...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { App } from '@modelcontextprotocol/ext-apps';
|
import { useApp } from '@modelcontextprotocol/ext-apps/react';
|
||||||
|
|
||||||
export function useToolData<T>(): T | null {
|
interface UseToolDataResult<T> {
|
||||||
|
data: T | null;
|
||||||
|
error: string | null;
|
||||||
|
isConnected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useToolData<T>(): UseToolDataResult<T> {
|
||||||
const [data, setData] = useState<T | null>(null);
|
const [data, setData] = useState<T | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const onAppCreated = useCallback((app: any) => {
|
||||||
const app = new App();
|
|
||||||
|
|
||||||
app.ontoolresult = (result: any) => {
|
app.ontoolresult = (result: any) => {
|
||||||
// The host pushes tool result content; parse the first text item as JSON
|
|
||||||
if (result?.content) {
|
if (result?.content) {
|
||||||
const textItem = Array.isArray(result.content)
|
const textItem = Array.isArray(result.content)
|
||||||
? result.content.find((c: any) => c.type === 'text')
|
? result.content.find((c: any) => c.type === 'text')
|
||||||
@@ -17,19 +20,22 @@ export function useToolData<T>(): T | null {
|
|||||||
try {
|
try {
|
||||||
setData(JSON.parse(textItem.text) as T);
|
setData(JSON.parse(textItem.text) as T);
|
||||||
} catch {
|
} catch {
|
||||||
// Not JSON — use raw text as-is
|
|
||||||
setData(textItem.text as unknown as T);
|
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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user