Compare commits

..

2 Commits

Author SHA1 Message Date
Romuald Członkowski
6f695be482 fix: disable MCP Apps that don't render in Claude.ai (#586)
Disable 3 MCP Apps (workflow-list, execution-history, health-dashboard)
that show as collapsed accordions and remove n8n_deploy_template tool
mapping that renders blank content. The server sets _meta correctly on
the wire but the Claude.ai host ignores it for these tools. Keep the 2
working apps (operation-result, validation-summary) active.

Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 02:26:40 +01:00
Romuald Członkowski
34159f4ece fix: add legacy flat _meta key for MCP App rendering in Claude (#585)
Claude.ai reads the flat `_meta["ui/resourceUri"]` key to discover UI apps,
not the nested `_meta.ui.resourceUri`. Without the flat key, tools like
n8n_health_check and n8n_list_workflows showed as collapsed accordions
instead of rendering rich UI. Now sets both keys, matching the behavior
of the official registerAppTool helper from @modelcontextprotocol/ext-apps.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 06:40:52 +01:00
7 changed files with 47 additions and 63 deletions

View File

@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [2.35.2] - 2026-02-09
### Changed
- **MCP Apps: Disable non-rendering apps in Claude.ai**: Disabled 3 MCP Apps (workflow-list, execution-history, health-dashboard) that render as collapsed accordions in Claude.ai, and removed `n8n_deploy_template` tool mapping which renders blank content. The server sets `_meta` correctly on the wire but the Claude.ai host ignores it for these tools. The 2 working apps (operation-result for 6 tools, validation-summary for 3 tools) remain active. Disabled apps can be re-enabled once the host-side issue is resolved.
Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en
## [2.35.1] - 2026-02-09
### Fixed
- **MCP Apps: Fix UI not rendering for some tools in Claude**: Added legacy flat `_meta["ui/resourceUri"]` key alongside the nested `_meta.ui.resourceUri` in tool definitions. Claude.ai reads the flat key format; without it, tools like `n8n_health_check` and `n8n_list_workflows` showed as collapsed accordions instead of rendering their rich UI apps. Both key formats are now set by `injectToolMeta()`, matching the behavior of the official `registerAppTool` helper from `@modelcontextprotocol/ext-apps/server`.
Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en
## [2.35.0] - 2026-02-09 ## [2.35.0] - 2026-02-09
### Added ### Added

View File

@@ -1,6 +1,6 @@
{ {
"name": "n8n-mcp", "name": "n8n-mcp",
"version": "2.35.0", "version": "2.35.2",
"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",

View File

@@ -14,7 +14,7 @@ export const UI_APP_CONFIGS: UIAppConfig[] = [
'n8n_delete_workflow', 'n8n_delete_workflow',
'n8n_test_workflow', 'n8n_test_workflow',
'n8n_autofix_workflow', 'n8n_autofix_workflow',
'n8n_deploy_template', // n8n_deploy_template disabled: Claude.ai renders blank content for this tool
], ],
}, },
{ {
@@ -29,34 +29,8 @@ export const UI_APP_CONFIGS: UIAppConfig[] = [
'n8n_validate_workflow', 'n8n_validate_workflow',
], ],
}, },
{ // workflow-list, execution-history, health-dashboard disabled:
id: 'workflow-list', // Claude.ai does not render these apps (shows collapsed accordions).
displayName: 'Workflow List', // The server sets _meta correctly on the wire but the host ignores it.
description: 'Compact table of workflows with status, tags, and metadata', // Re-enable once the host-side issue is resolved.
uri: 'ui://n8n-mcp/workflow-list',
mimeType: 'text/html;profile=mcp-app',
toolPatterns: [
'n8n_list_workflows',
],
},
{
id: 'execution-history',
displayName: 'Execution History',
description: 'Execution history table with status summary bar',
uri: 'ui://n8n-mcp/execution-history',
mimeType: 'text/html;profile=mcp-app',
toolPatterns: [
'n8n_executions',
],
},
{
id: 'health-dashboard',
displayName: 'Health Dashboard',
description: 'Connection status, versions, and performance metrics',
uri: 'ui://n8n-mcp/health-dashboard',
mimeType: 'text/html;profile=mcp-app',
toolPatterns: [
'n8n_health_check',
],
},
]; ];

View File

@@ -64,13 +64,19 @@ export class UIAppRegistry {
* Enrich tool definitions with _meta.ui.resourceUri for tools that have * Enrich tool definitions with _meta.ui.resourceUri for tools that have
* a matching UI app. Per MCP ext-apps spec, this goes on the tool * a matching UI app. Per MCP ext-apps spec, this goes on the tool
* definition (tools/list), not the tool call response. * definition (tools/list), not the tool call response.
*
* Sets both nested (_meta.ui.resourceUri) and flat (_meta["ui/resourceUri"])
* keys for compatibility with hosts that read either format.
*/ */
static injectToolMeta(tools: Array<{ name: string; [key: string]: any }>): void { static injectToolMeta(tools: Array<{ name: string; [key: string]: any }>): void {
if (!this.loaded) return; if (!this.loaded) return;
for (const tool of tools) { for (const tool of tools) {
const entry = this.toolIndex.get(tool.name); const entry = this.toolIndex.get(tool.name);
if (entry && entry.html) { if (entry && entry.html) {
tool._meta = { ui: { resourceUri: entry.config.uri } }; tool._meta = {
ui: { resourceUri: entry.config.uri },
'ui/resourceUri': entry.config.uri,
};
} }
} }
} }

View File

@@ -86,7 +86,7 @@ describe('UI_APP_CONFIGS', () => {
expect(config!.toolPatterns).toContain('n8n_update_full_workflow'); expect(config!.toolPatterns).toContain('n8n_update_full_workflow');
expect(config!.toolPatterns).toContain('n8n_delete_workflow'); expect(config!.toolPatterns).toContain('n8n_delete_workflow');
expect(config!.toolPatterns).toContain('n8n_test_workflow'); expect(config!.toolPatterns).toContain('n8n_test_workflow');
expect(config!.toolPatterns).toContain('n8n_deploy_template'); expect(config!.toolPatterns).not.toContain('n8n_deploy_template');
}); });
it('should contain the validation-summary config', () => { it('should contain the validation-summary config', () => {
@@ -98,29 +98,14 @@ describe('UI_APP_CONFIGS', () => {
expect(config!.toolPatterns).toContain('n8n_validate_workflow'); expect(config!.toolPatterns).toContain('n8n_validate_workflow');
}); });
it('should have exactly 5 configs', () => { it('should have exactly 2 configs', () => {
expect(UI_APP_CONFIGS.length).toBe(5); expect(UI_APP_CONFIGS.length).toBe(2);
}); });
it('should contain the workflow-list config', () => { it('should not contain disabled apps', () => {
const config = UI_APP_CONFIGS.find(c => c.id === 'workflow-list'); expect(UI_APP_CONFIGS.find(c => c.id === 'workflow-list')).toBeUndefined();
expect(config).toBeDefined(); expect(UI_APP_CONFIGS.find(c => c.id === 'execution-history')).toBeUndefined();
expect(config!.displayName).toBe('Workflow List'); expect(UI_APP_CONFIGS.find(c => c.id === 'health-dashboard')).toBeUndefined();
expect(config!.toolPatterns).toContain('n8n_list_workflows');
});
it('should contain the execution-history config', () => {
const config = UI_APP_CONFIGS.find(c => c.id === 'execution-history');
expect(config).toBeDefined();
expect(config!.displayName).toBe('Execution History');
expect(config!.toolPatterns).toContain('n8n_executions');
});
it('should contain the health-dashboard config', () => {
const config = UI_APP_CONFIGS.find(c => c.id === 'health-dashboard');
expect(config).toBeDefined();
expect(config!.displayName).toBe('Health Dashboard');
expect(config!.toolPatterns).toContain('n8n_health_check');
}); });
it('should have IDs that are valid URI path segments (no spaces or special chars)', () => { it('should have IDs that are valid URI path segments (no spaces or special chars)', () => {

View File

@@ -72,7 +72,7 @@ describe('UI Meta Injection on Tool Definitions', () => {
expect(tools[2]._meta.ui.resourceUri).toBe('ui://n8n-mcp/validation-summary'); expect(tools[2]._meta.ui.resourceUri).toBe('ui://n8n-mcp/validation-summary');
}); });
it('should produce _meta with exact shape { ui: { resourceUri: string } }', () => { it('should produce _meta with both nested and flat resourceUri keys', () => {
const tools: any[] = [ const tools: any[] = [
{ name: 'n8n_create_workflow', description: 'Create', inputSchema: { type: 'object', properties: {} } }, { name: 'n8n_create_workflow', description: 'Create', inputSchema: { type: 'object', properties: {} } },
]; ];
@@ -83,10 +83,10 @@ describe('UI Meta Injection on Tool Definitions', () => {
ui: { ui: {
resourceUri: 'ui://n8n-mcp/operation-result', resourceUri: 'ui://n8n-mcp/operation-result',
}, },
'ui/resourceUri': 'ui://n8n-mcp/operation-result',
}); });
expect(Object.keys(tools[0]._meta)).toEqual(['ui']); expect(tools[0]._meta.ui.resourceUri).toBe('ui://n8n-mcp/operation-result');
expect(Object.keys(tools[0]._meta.ui)).toEqual(['resourceUri']); expect(tools[0]._meta['ui/resourceUri']).toBe('ui://n8n-mcp/operation-result');
expect(typeof tools[0]._meta.ui.resourceUri).toBe('string');
}); });
}); });

View File

@@ -187,8 +187,11 @@ describe('UIAppRegistry', () => {
expect(UIAppRegistry.getAppForTool('n8n_autofix_workflow')!.config.id).toBe('operation-result'); expect(UIAppRegistry.getAppForTool('n8n_autofix_workflow')!.config.id).toBe('operation-result');
}); });
it('should map n8n_deploy_template to operation-result', () => { it('should not map disabled tools', () => {
expect(UIAppRegistry.getAppForTool('n8n_deploy_template')!.config.id).toBe('operation-result'); expect(UIAppRegistry.getAppForTool('n8n_deploy_template')).toBeNull();
expect(UIAppRegistry.getAppForTool('n8n_list_workflows')).toBeNull();
expect(UIAppRegistry.getAppForTool('n8n_executions')).toBeNull();
expect(UIAppRegistry.getAppForTool('n8n_health_check')).toBeNull();
}); });
it('should map validate_node to validation-summary', () => { it('should map validate_node to validation-summary', () => {
@@ -308,7 +311,7 @@ describe('UIAppRegistry', () => {
{ name: 'n8n_create_workflow', description: 'Create', inputSchema: { type: 'object', properties: {} } }, { name: 'n8n_create_workflow', description: 'Create', inputSchema: { type: 'object', properties: {} } },
]; ];
UIAppRegistry.injectToolMeta(tools); UIAppRegistry.injectToolMeta(tools);
expect(tools[0]._meta).toEqual({ ui: { resourceUri: 'ui://n8n-mcp/operation-result' } }); expect(tools[0]._meta).toEqual({ ui: { resourceUri: 'ui://n8n-mcp/operation-result' }, 'ui/resourceUri': 'ui://n8n-mcp/operation-result' });
}); });
it('should set _meta.ui.resourceUri on matching validation tools', () => { it('should set _meta.ui.resourceUri on matching validation tools', () => {
@@ -316,7 +319,7 @@ describe('UIAppRegistry', () => {
{ name: 'validate_node', description: 'Validate', inputSchema: { type: 'object', properties: {} } }, { name: 'validate_node', description: 'Validate', inputSchema: { type: 'object', properties: {} } },
]; ];
UIAppRegistry.injectToolMeta(tools); UIAppRegistry.injectToolMeta(tools);
expect(tools[0]._meta).toEqual({ ui: { resourceUri: 'ui://n8n-mcp/validation-summary' } }); expect(tools[0]._meta).toEqual({ ui: { resourceUri: 'ui://n8n-mcp/validation-summary' }, 'ui/resourceUri': 'ui://n8n-mcp/validation-summary' });
}); });
it('should not set _meta on tools without a matching UI app', () => { it('should not set _meta on tools without a matching UI app', () => {