mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-09 06:43:08 +00:00
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>
This commit is contained in:
committed by
GitHub
parent
8217229e2f
commit
d4a53db388
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.35.0",
|
||||
"version": "2.35.1",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -64,13 +64,19 @@ export class UIAppRegistry {
|
||||
* 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
|
||||
* 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 {
|
||||
if (!this.loaded) return;
|
||||
for (const tool of tools) {
|
||||
const entry = this.toolIndex.get(tool.name);
|
||||
if (entry && entry.html) {
|
||||
tool._meta = { ui: { resourceUri: entry.config.uri } };
|
||||
tool._meta = {
|
||||
ui: { resourceUri: entry.config.uri },
|
||||
'ui/resourceUri': entry.config.uri,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('UI Meta Injection on Tool Definitions', () => {
|
||||
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[] = [
|
||||
{ name: 'n8n_create_workflow', description: 'Create', inputSchema: { type: 'object', properties: {} } },
|
||||
];
|
||||
@@ -83,10 +83,10 @@ describe('UI Meta Injection on Tool Definitions', () => {
|
||||
ui: {
|
||||
resourceUri: 'ui://n8n-mcp/operation-result',
|
||||
},
|
||||
'ui/resourceUri': 'ui://n8n-mcp/operation-result',
|
||||
});
|
||||
expect(Object.keys(tools[0]._meta)).toEqual(['ui']);
|
||||
expect(Object.keys(tools[0]._meta.ui)).toEqual(['resourceUri']);
|
||||
expect(typeof tools[0]._meta.ui.resourceUri).toBe('string');
|
||||
expect(tools[0]._meta.ui.resourceUri).toBe('ui://n8n-mcp/operation-result');
|
||||
expect(tools[0]._meta['ui/resourceUri']).toBe('ui://n8n-mcp/operation-result');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ describe('UIAppRegistry', () => {
|
||||
{ name: 'n8n_create_workflow', description: 'Create', inputSchema: { type: 'object', properties: {} } },
|
||||
];
|
||||
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', () => {
|
||||
@@ -316,7 +316,7 @@ describe('UIAppRegistry', () => {
|
||||
{ name: 'validate_node', description: 'Validate', inputSchema: { type: 'object', properties: {} } },
|
||||
];
|
||||
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', () => {
|
||||
|
||||
Reference in New Issue
Block a user