mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-07 14:03:08 +00:00
* feat: add MCP Apps with rich HTML UIs for tool results Add MCP Apps infrastructure that allows MCP hosts like Claude Desktop to render rich HTML UIs alongside tool results via `_meta.ui` and the MCP resources protocol. - Server-side UI module (src/mcp/ui/) with UIAppRegistry, tool-to-UI mapping, and _meta.ui injection into tool responses - React + Vite build pipeline (ui-apps/) producing self-contained HTML per app using vite-plugin-singlefile - Operation Result UI for workflow CRUD tools (create, update, delete, test, autofix, deploy) - Validation Summary UI for validation tools (validate_node, validate_workflow, n8n_validate_workflow) - Shared component library (Card, Badge, Expandable) with n8n dark theme - MCP resources protocol support (ListResources, ReadResource handlers) - Graceful degradation when ui-apps/dist/ is not built - 22 unit tests across 3 test files Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: improve MCP Apps test coverage and add security hardening - Expand test suite from 22 to 57 tests across 3 test files - Add UIAppRegistry.reset() for proper test isolation between tests - Replace some fs mocks with real temp directory tests in registry - Add edge case coverage: empty strings, pre-load state, double load, malformed URIs, duplicate tool patterns, empty HTML files - Add regression tests for specific tool-to-UI mappings - Add URI format consistency validation across all configs - Improve _meta.ui injection tests with structuredContent coexistence - Coverage: statements 79.4% -> 80%, lines 79.4% -> 80% 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>
111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { UI_APP_CONFIGS } from '@/mcp/ui/app-configs';
|
|
|
|
describe('UI_APP_CONFIGS', () => {
|
|
it('should have all required fields for every config', () => {
|
|
for (const config of UI_APP_CONFIGS) {
|
|
expect(config.id).toBeDefined();
|
|
expect(typeof config.id).toBe('string');
|
|
expect(config.id.length).toBeGreaterThan(0);
|
|
|
|
expect(config.displayName).toBeDefined();
|
|
expect(typeof config.displayName).toBe('string');
|
|
expect(config.displayName.length).toBeGreaterThan(0);
|
|
|
|
expect(config.description).toBeDefined();
|
|
expect(typeof config.description).toBe('string');
|
|
expect(config.description.length).toBeGreaterThan(0);
|
|
|
|
expect(config.uri).toBeDefined();
|
|
expect(typeof config.uri).toBe('string');
|
|
|
|
expect(config.mimeType).toBeDefined();
|
|
expect(typeof config.mimeType).toBe('string');
|
|
|
|
expect(config.toolPatterns).toBeDefined();
|
|
expect(Array.isArray(config.toolPatterns)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('should have URIs following n8n-mcp://ui/{id} pattern', () => {
|
|
for (const config of UI_APP_CONFIGS) {
|
|
expect(config.uri).toBe(`n8n-mcp://ui/${config.id}`);
|
|
}
|
|
});
|
|
|
|
it('should have unique IDs', () => {
|
|
const ids = UI_APP_CONFIGS.map(c => c.id);
|
|
const uniqueIds = new Set(ids);
|
|
expect(uniqueIds.size).toBe(ids.length);
|
|
});
|
|
|
|
it('should have non-empty toolPatterns arrays', () => {
|
|
for (const config of UI_APP_CONFIGS) {
|
|
expect(config.toolPatterns.length).toBeGreaterThan(0);
|
|
for (const pattern of config.toolPatterns) {
|
|
expect(typeof pattern).toBe('string');
|
|
expect(pattern.length).toBeGreaterThan(0);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should not have duplicate tool patterns across configs', () => {
|
|
const allPatterns: string[] = [];
|
|
for (const config of UI_APP_CONFIGS) {
|
|
allPatterns.push(...config.toolPatterns);
|
|
}
|
|
const uniquePatterns = new Set(allPatterns);
|
|
expect(uniquePatterns.size).toBe(allPatterns.length);
|
|
});
|
|
|
|
it('should not have duplicate tool patterns within a single config', () => {
|
|
for (const config of UI_APP_CONFIGS) {
|
|
const unique = new Set(config.toolPatterns);
|
|
expect(unique.size).toBe(config.toolPatterns.length);
|
|
}
|
|
});
|
|
|
|
it('should have consistent mimeType of text/html', () => {
|
|
for (const config of UI_APP_CONFIGS) {
|
|
expect(config.mimeType).toBe('text/html');
|
|
}
|
|
});
|
|
|
|
it('should have URIs that start with the n8n-mcp://ui/ scheme', () => {
|
|
for (const config of UI_APP_CONFIGS) {
|
|
expect(config.uri).toMatch(/^n8n-mcp:\/\/ui\//);
|
|
}
|
|
});
|
|
|
|
// Regression: verify expected configs are present
|
|
it('should contain the operation-result config', () => {
|
|
const config = UI_APP_CONFIGS.find(c => c.id === 'operation-result');
|
|
expect(config).toBeDefined();
|
|
expect(config!.displayName).toBe('Operation Result');
|
|
expect(config!.toolPatterns).toContain('n8n_create_workflow');
|
|
expect(config!.toolPatterns).toContain('n8n_update_full_workflow');
|
|
expect(config!.toolPatterns).toContain('n8n_delete_workflow');
|
|
expect(config!.toolPatterns).toContain('n8n_test_workflow');
|
|
expect(config!.toolPatterns).toContain('n8n_deploy_template');
|
|
});
|
|
|
|
it('should contain the validation-summary config', () => {
|
|
const config = UI_APP_CONFIGS.find(c => c.id === 'validation-summary');
|
|
expect(config).toBeDefined();
|
|
expect(config!.displayName).toBe('Validation Summary');
|
|
expect(config!.toolPatterns).toContain('validate_node');
|
|
expect(config!.toolPatterns).toContain('validate_workflow');
|
|
expect(config!.toolPatterns).toContain('n8n_validate_workflow');
|
|
});
|
|
|
|
it('should have exactly 2 configs', () => {
|
|
expect(UI_APP_CONFIGS.length).toBe(2);
|
|
});
|
|
|
|
it('should have IDs that are valid URI path segments (no spaces or special chars)', () => {
|
|
for (const config of UI_APP_CONFIGS) {
|
|
expect(config.id).toMatch(/^[a-z0-9-]+$/);
|
|
}
|
|
});
|
|
});
|