feat(editor): Add New tag to MCP category and registry tools (#30439)

This commit is contained in:
Elias Meire 2026-05-14 11:04:06 +02:00 committed by GitHub
parent a2835d7e88
commit 563089ba70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 52 additions and 2 deletions

View File

@ -455,6 +455,7 @@ export type SimplifiedNodeType = Pick<
| 'outputs'
> & {
tag?: NodeCreatorTag;
isNew?: boolean;
};
export interface SubcategoryItemProps {
description?: string;

View File

@ -63,4 +63,4 @@ export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fB
export const RECOMMENDED_NODES: string[] = [DATA_TABLE_NODE_TYPE, DATA_TABLE_TOOL_NODE_TYPE];
export const BETA_NODES: string[] = ['@n8n/n8n-nodes-langchain.microsoftAgent365Trigger'];
export const NEW_TOOL_CATEGORIES: string[] = [AI_CATEGORY_HUMAN_IN_THE_LOOP];
export const NEW_TOOL_CATEGORIES: string[] = [AI_CATEGORY_MCP_NODES];

View File

@ -150,6 +150,10 @@ const tag = computed(() => {
return undefined;
});
// Only surface the "new" badge in search results under the category itself
// the parent subcategory tile already carries the badge.
const showNewBadge = computed(() => Boolean(props.nodeType.isNew && activeViewStack.search));
function onDragStart(event: DragEvent): void {
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'copy';
@ -190,6 +194,7 @@ function onCommunityNodeTooltipClick(event: MouseEvent) {
:is-official="isOfficial"
:data-test-id="dataTestId"
:tag="tag"
:is-new="showNewBadge"
@dragstart="onDragStart"
@dragend="onDragEnd"
>

View File

@ -5,6 +5,7 @@ import type {
SimplifiedNodeType,
} from '@/Interface';
import {
finalizeItems,
formatTriggerActionName,
filterAndSearchNodes,
groupItemsInSections,
@ -33,6 +34,9 @@ import {
AI_CATEGORY_OTHER_TOOLS,
AI_CATEGORY_VECTOR_STORES,
AI_CATEGORY_HUMAN_IN_THE_LOOP,
AI_CATEGORY_MCP_NODES,
AI_CATEGORY_ROOT_NODES,
AI_SUBCATEGORY,
} from '@/app/constants';
vi.mock('@/app/stores/settings.store', () => ({
@ -705,6 +709,35 @@ describe('NodeCreator - utils', () => {
});
});
describe('finalizeItems - MCP registry tool isNew flag', () => {
const makeMcpNode = (subcategoriesAi: string[]) =>
mockNodeCreateElement(undefined, {
name: 'mcpRegistryNode',
codex: {
categories: ['AI'],
subcategories: { [AI_SUBCATEGORY]: subcategoriesAi },
},
});
it('should flag registry-generated MCP tools as new', () => {
const node = makeMcpNode([AI_CATEGORY_MCP_NODES]);
const [result] = finalizeItems([node]) as NodeCreateElement[];
expect(result.properties.isNew).toBe(true);
});
it('should not flag MCP nodes that are also Root Nodes (e.g. McpTrigger)', () => {
const node = makeMcpNode([AI_CATEGORY_ROOT_NODES, AI_CATEGORY_MCP_NODES]);
const [result] = finalizeItems([node]) as NodeCreateElement[];
expect(result.properties.isNew).toBeUndefined();
});
it('should not flag tools that are not in the MCP subcategory', () => {
const node = makeMcpNode(['Tools']);
const [result] = finalizeItems([node]) as NodeCreateElement[];
expect(result.properties.isNew).toBeUndefined();
});
});
describe('mapToolSubcategoryIcon', () => {
it('should return "globe" for AI_CATEGORY_OTHER_TOOLS', () => {
expect(mapToolSubcategoryIcon(AI_CATEGORY_OTHER_TOOLS)).toBe('globe');

View File

@ -15,6 +15,7 @@ import {
AI_CATEGORY_HUMAN_IN_THE_LOOP,
AI_CATEGORY_MCP_NODES,
AI_CATEGORY_OTHER_TOOLS,
AI_CATEGORY_ROOT_NODES,
AI_CATEGORY_VECTOR_STORES,
AI_SUBCATEGORY,
AI_TRANSFORM_NODE_TYPE,
@ -288,7 +289,17 @@ export const removePreviewToken = (key: string) =>
export const isNodePreviewKey = (key = '') => key.includes(COMMUNITY_NODE_TYPE_PREVIEW_TOKEN);
function applyNodeTags(element: INodeCreateElement): INodeCreateElement {
if (element.type !== 'node' || element.properties.tag) return element;
if (element.type !== 'node') return element;
const aiSubcategories = element.properties.codex?.subcategories?.[AI_SUBCATEGORY] ?? [];
if (
aiSubcategories.includes(AI_CATEGORY_MCP_NODES) &&
!aiSubcategories.includes(AI_CATEGORY_ROOT_NODES)
) {
element.properties.isNew = true;
}
if (element.properties.tag) return element;
if (RECOMMENDED_NODES.includes(element.properties.name)) {
element.properties.tag = {