mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
feat(core): Rename the MCP n8n Connect services tool to Gateway credits (no-changelog) (#37272)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c96e7c0da4
commit
db49eaec07
|
|
@ -954,7 +954,7 @@ describe('create-workflow-from-code MCP tool', () => {
|
|||
},
|
||||
{
|
||||
nodeName: 'OpenAI',
|
||||
credentialName: 'n8n credits',
|
||||
credentialName: 'Gateway credits',
|
||||
credentialType: 'openAiApi',
|
||||
source: 'aiGateway',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -397,12 +397,12 @@ describe('autoPopulateNodeCredentials', () => {
|
|||
);
|
||||
|
||||
expect(node.credentials).toEqual({
|
||||
slackApi: { id: null, name: 'n8n credits', __aiGatewayManaged: true },
|
||||
slackApi: { id: null, name: 'Gateway credits', __aiGatewayManaged: true },
|
||||
});
|
||||
expect(result.assignments).toEqual([
|
||||
{
|
||||
nodeName: 'Test Node',
|
||||
credentialName: 'n8n credits',
|
||||
credentialName: 'Gateway credits',
|
||||
credentialType: 'slackApi',
|
||||
source: 'aiGateway',
|
||||
},
|
||||
|
|
@ -473,13 +473,13 @@ describe('autoPopulateNodeCredentials', () => {
|
|||
);
|
||||
|
||||
expect(node.credentials).toEqual({
|
||||
serviceApiKey: { id: null, name: 'n8n credits', __aiGatewayManaged: true },
|
||||
serviceApiKey: { id: null, name: 'Gateway credits', __aiGatewayManaged: true },
|
||||
});
|
||||
expect(node.parameters.authentication).toBe('apiKey');
|
||||
expect(result.assignments).toEqual([
|
||||
{
|
||||
nodeName: 'Test Node',
|
||||
credentialName: 'n8n credits',
|
||||
credentialName: 'Gateway credits',
|
||||
credentialType: 'serviceApiKey',
|
||||
source: 'aiGateway',
|
||||
},
|
||||
|
|
@ -551,7 +551,7 @@ describe('autoPopulateNodeCredentials', () => {
|
|||
);
|
||||
|
||||
expect(node.credentials).toEqual({
|
||||
serviceApiKey: { id: null, name: 'n8n credits', __aiGatewayManaged: true },
|
||||
serviceApiKey: { id: null, name: 'Gateway credits', __aiGatewayManaged: true },
|
||||
});
|
||||
expect(node.parameters.authentication).toBe('apiKey');
|
||||
});
|
||||
|
|
@ -616,7 +616,7 @@ describe('autoPopulateNodeCredentials', () => {
|
|||
);
|
||||
|
||||
expect(node.credentials).toEqual({
|
||||
serviceApiKey: { id: null, name: 'n8n credits', __aiGatewayManaged: true },
|
||||
serviceApiKey: { id: null, name: 'Gateway credits', __aiGatewayManaged: true },
|
||||
});
|
||||
expect(node.parameters.authentication).toBe('apiKeyLegacy');
|
||||
});
|
||||
|
|
@ -807,7 +807,7 @@ describe('autoPopulateNodeCredentials', () => {
|
|||
// Canonicalized to the sentinel and kept — the explicit n8n Connect request
|
||||
// wins over the owned credential, and no assignment is recorded for it.
|
||||
expect(node.credentials).toEqual({
|
||||
slackApi: { id: null, name: 'n8n credits', __aiGatewayManaged: true },
|
||||
slackApi: { id: null, name: 'Gateway credits', __aiGatewayManaged: true },
|
||||
});
|
||||
expect(result.assignments).toEqual([]);
|
||||
expect(result.outcomes).toEqual([]);
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ describe('get-workflow-details MCP tool', () => {
|
|||
disabled: false,
|
||||
parameters: {},
|
||||
credentials: {
|
||||
openAiApi: { id: null, name: 'n8n credits', __aiGatewayManaged: true },
|
||||
openAiApi: { id: null, name: 'Gateway credits', __aiGatewayManaged: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ describe('get-workflow-node-types MCP tool', () => {
|
|||
expect(result.structuredContent).toEqual({ definitions: 'typescript definitions' });
|
||||
});
|
||||
|
||||
test('adds n8nConnect block when gateway is available', async () => {
|
||||
test('adds gatewayCredits block when gateway is available', async () => {
|
||||
aiGatewayService.isAvailable.mockResolvedValue({
|
||||
available: true,
|
||||
config: {
|
||||
|
|
@ -68,18 +68,18 @@ describe('get-workflow-node-types MCP tool', () => {
|
|||
|
||||
expect(result.structuredContent).toEqual({
|
||||
definitions: 'typescript definitions',
|
||||
n8nConnect: {
|
||||
gatewayCredits: {
|
||||
credentialTypes: ['openAiApi'],
|
||||
nodes: ['@n8n/n8n-nodes-langchain.openAi'],
|
||||
},
|
||||
});
|
||||
// Also mirrored into the unstructured content for text-only clients.
|
||||
expect((result.content[0] as { text: string }).text).toBe(
|
||||
'typescript definitions\n\nn8nConnect: {"credentialTypes":["openAiApi"],"nodes":["@n8n/n8n-nodes-langchain.openAi"]}',
|
||||
'typescript definitions\n\ngatewayCredits: {"credentialTypes":["openAiApi"],"nodes":["@n8n/n8n-nodes-langchain.openAi"]}',
|
||||
);
|
||||
});
|
||||
|
||||
test('omits n8nConnect block when unavailable', async () => {
|
||||
test('omits gatewayCredits block when unavailable', async () => {
|
||||
const tool = createTool();
|
||||
const result = await tool.handler(
|
||||
{ nodeIds: [{ nodeId: 'n8n-nodes-base.slack' }] },
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ describe('list-credentials MCP tool', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('n8nConnect block', () => {
|
||||
describe('gatewayCredits block', () => {
|
||||
async function callHandler(opts: { available?: boolean } = {}) {
|
||||
const { credentialsService, telemetry } = createMocks([buildCredential()]);
|
||||
const { aiGatewayService } = makeAiGatewayMocks(opts);
|
||||
|
|
@ -298,21 +298,21 @@ describe('list-credentials MCP tool', () => {
|
|||
return result.structuredContent as {
|
||||
data: unknown[];
|
||||
count: number;
|
||||
n8nConnect?: { credentialTypes: string[]; nodes: string[] };
|
||||
gatewayCredits?: { credentialTypes: string[]; nodes: string[] };
|
||||
};
|
||||
}
|
||||
|
||||
test('includes n8nConnect block when gateway is available', async () => {
|
||||
test('includes gatewayCredits block when gateway is available', async () => {
|
||||
const structured = await callHandler({ available: true });
|
||||
expect(structured.n8nConnect).toEqual({
|
||||
expect(structured.gatewayCredits).toEqual({
|
||||
credentialTypes: ['openAiApi'],
|
||||
nodes: ['@n8n/n8n-nodes-langchain.openAi'],
|
||||
});
|
||||
});
|
||||
|
||||
test('omits n8nConnect block when unavailable', async () => {
|
||||
test('omits gatewayCredits block when unavailable', async () => {
|
||||
const structured = await callHandler({ available: false });
|
||||
expect(structured.n8nConnect).toBeUndefined();
|
||||
expect(structured.gatewayCredits).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { mock } from 'vitest-mock-extended';
|
|||
import type { AiGatewayService } from '@/services/ai-gateway.service';
|
||||
import { Telemetry } from '@/telemetry';
|
||||
|
||||
import { createListN8nConnectServicesTool } from '../tools/list-n8n-connect-services.tool';
|
||||
import { createListN8nGatewayServicesTool } from '../tools/list-n8n-gateway-services.tool';
|
||||
|
||||
const user = Object.assign(new User(), { id: 'user-1' });
|
||||
|
||||
|
|
@ -39,11 +39,11 @@ function makeMocks(opts: { available?: boolean; config?: AiGatewayConfigDto } =
|
|||
return { aiGatewayService, telemetry };
|
||||
}
|
||||
|
||||
describe('list_n8n_connect_services MCP tool', () => {
|
||||
test('registers under the name list_n8n_connect_services', () => {
|
||||
describe('list_n8n_gateway_services MCP tool', () => {
|
||||
test('registers under the name list_n8n_gateway_services', () => {
|
||||
const { aiGatewayService, telemetry } = makeMocks();
|
||||
const tool = createListN8nConnectServicesTool(user, aiGatewayService, telemetry);
|
||||
expect(tool.name).toBe('list_n8n_connect_services');
|
||||
const tool = createListN8nGatewayServicesTool(user, aiGatewayService, telemetry);
|
||||
expect(tool.name).toBe('list_n8n_gateway_services');
|
||||
expect(tool.config.annotations).toMatchObject({
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
|
|
@ -54,7 +54,7 @@ describe('list_n8n_connect_services MCP tool', () => {
|
|||
|
||||
test('returns full coverage payload when available', async () => {
|
||||
const { aiGatewayService, telemetry } = makeMocks({ available: true });
|
||||
const tool = createListN8nConnectServicesTool(user, aiGatewayService, telemetry);
|
||||
const tool = createListN8nGatewayServicesTool(user, aiGatewayService, telemetry);
|
||||
const result = await tool.handler({}, {} as never);
|
||||
expect(result.structuredContent).toEqual({
|
||||
available: true,
|
||||
|
|
@ -70,20 +70,20 @@ describe('list_n8n_connect_services MCP tool', () => {
|
|||
|
||||
test('returns { available: false } when unavailable', async () => {
|
||||
const { aiGatewayService, telemetry } = makeMocks({ available: false });
|
||||
const tool = createListN8nConnectServicesTool(user, aiGatewayService, telemetry);
|
||||
const tool = createListN8nGatewayServicesTool(user, aiGatewayService, telemetry);
|
||||
const result = await tool.handler({}, {} as never);
|
||||
expect(result.structuredContent).toEqual({ available: false });
|
||||
});
|
||||
|
||||
test('emits USER_CALLED_MCP_TOOL_EVENT with tool_name and success', async () => {
|
||||
const { aiGatewayService, telemetry } = makeMocks();
|
||||
const tool = createListN8nConnectServicesTool(user, aiGatewayService, telemetry);
|
||||
const tool = createListN8nGatewayServicesTool(user, aiGatewayService, telemetry);
|
||||
await tool.handler({}, {} as never);
|
||||
expect(telemetry.track).toHaveBeenCalledWith(
|
||||
'User called mcp tool',
|
||||
expect.objectContaining({
|
||||
user_id: 'user-1',
|
||||
tool_name: 'list_n8n_connect_services',
|
||||
tool_name: 'list_n8n_gateway_services',
|
||||
results: { success: true, data: { available: true } },
|
||||
}),
|
||||
);
|
||||
|
|
@ -4,7 +4,7 @@ describe('getMcpInstructions', () => {
|
|||
test('returns intro-only string when builder is disabled', () => {
|
||||
const instructions = getMcpInstructions({ isBuilderEnabled: false });
|
||||
expect(instructions).toContain('official MCP server for n8n');
|
||||
expect(instructions).not.toContain('n8nConnect');
|
||||
expect(instructions).not.toContain('gatewayCredits');
|
||||
});
|
||||
|
||||
test('includes n8n credits hint when builder is enabled and n8n Connect is available', () => {
|
||||
|
|
@ -12,10 +12,10 @@ describe('getMcpInstructions', () => {
|
|||
isBuilderEnabled: true,
|
||||
isN8nConnectAvailable: true,
|
||||
});
|
||||
expect(instructions).toContain('nodes covered by n8n credits');
|
||||
expect(instructions).toContain('n8nConnect.nodes');
|
||||
expect(instructions).toContain('n8n credits');
|
||||
expect(instructions).toContain('list_n8n_connect_services');
|
||||
expect(instructions).toContain('nodes covered by Gateway credits');
|
||||
expect(instructions).toContain('gatewayCredits.nodes');
|
||||
expect(instructions).toContain('Gateway credits');
|
||||
expect(instructions).toContain('list_n8n_gateway_services');
|
||||
});
|
||||
|
||||
test('omits n8n credits hint when n8n Connect is not available', () => {
|
||||
|
|
@ -24,14 +24,14 @@ describe('getMcpInstructions', () => {
|
|||
isN8nConnectAvailable: false,
|
||||
});
|
||||
expect(instructions).toContain('official MCP server for n8n');
|
||||
expect(instructions).not.toContain('n8n credits');
|
||||
expect(instructions).not.toContain('n8nConnect');
|
||||
expect(instructions).not.toContain('list_n8n_connect_services');
|
||||
expect(instructions).not.toContain('Gateway credits');
|
||||
expect(instructions).not.toContain('gatewayCredits');
|
||||
expect(instructions).not.toContain('list_n8n_gateway_services');
|
||||
});
|
||||
|
||||
test('omits n8n credits hint by default', () => {
|
||||
const instructions = getMcpInstructions({ isBuilderEnabled: true });
|
||||
expect(instructions).not.toContain('n8n credits');
|
||||
expect(instructions).not.toContain('Gateway credits');
|
||||
});
|
||||
|
||||
describe('node groups pointer', () => {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ describe('getAllowedToolNames', () => {
|
|||
it('grants only call_agent with agent:execute', () => {
|
||||
expect(getAllowedToolNames(['agent:execute'])).toEqual(new Set(['call_agent']));
|
||||
});
|
||||
|
||||
it('exposes the renamed list_n8n_gateway_services tool via credential:read', () => {
|
||||
expect(getAllowedToolNames(['credential:read'])).toContain('list_n8n_gateway_services');
|
||||
});
|
||||
});
|
||||
|
||||
describe('McpService scope enforcement', () => {
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ describe('search-workflow-nodes MCP tool', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('n8nConnect block', () => {
|
||||
test('includes n8nConnect block when gateway is available', async () => {
|
||||
describe('gatewayCredits block', () => {
|
||||
test('includes gatewayCredits block when gateway is available', async () => {
|
||||
aiGatewayService.isAvailable.mockResolvedValue({
|
||||
available: true,
|
||||
config: {
|
||||
|
|
@ -134,18 +134,18 @@ describe('search-workflow-nodes MCP tool', () => {
|
|||
|
||||
expect(result.structuredContent).toEqual({
|
||||
results: 'search-result',
|
||||
n8nConnect: {
|
||||
gatewayCredits: {
|
||||
credentialTypes: ['openAiApi'],
|
||||
nodes: ['@n8n/n8n-nodes-langchain.openAi'],
|
||||
},
|
||||
});
|
||||
// Also mirrored into the unstructured content for text-only clients.
|
||||
expect((result.content[0] as { text: string }).text).toBe(
|
||||
'search-result\n\nn8nConnect: {"credentialTypes":["openAiApi"],"nodes":["@n8n/n8n-nodes-langchain.openAi"]}',
|
||||
'search-result\n\ngatewayCredits: {"credentialTypes":["openAiApi"],"nodes":["@n8n/n8n-nodes-langchain.openAi"]}',
|
||||
);
|
||||
});
|
||||
|
||||
test('omits n8nConnect block when unavailable', async () => {
|
||||
test('omits gatewayCredits block when unavailable', async () => {
|
||||
const tool = createTool();
|
||||
const result = await tool.handler({ queries: ['openai'] }, {} as never);
|
||||
expect(result.structuredContent).toEqual({ results: 'search-result' });
|
||||
|
|
|
|||
|
|
@ -1958,7 +1958,7 @@ describe('update-workflow MCP tool', () => {
|
|||
},
|
||||
{
|
||||
nodeName: 'D',
|
||||
credentialName: 'n8n credits',
|
||||
credentialName: 'Gateway credits',
|
||||
credentialType: 'openAiApi',
|
||||
source: 'aiGateway',
|
||||
},
|
||||
|
|
@ -1983,7 +1983,7 @@ describe('update-workflow MCP tool', () => {
|
|||
{ nodeName: 'C', credentialName: 'My Slack', credentialType: 'slackApi', source: 'user' },
|
||||
{
|
||||
nodeName: 'D',
|
||||
credentialName: 'n8n credits',
|
||||
credentialName: 'Gateway credits',
|
||||
credentialType: 'openAiApi',
|
||||
source: 'aiGateway',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export const TOOLS_BY_SCOPE: Record<McpScope, readonly string[]> = {
|
|||
'agent:execute': ['call_agent'],
|
||||
// explore_node_resources queries external services with stored credentials,
|
||||
// so it must sit behind the credential scope rather than a workflow one.
|
||||
'credential:read': ['list_credentials', 'list_n8n_connect_services', 'explore_node_resources'],
|
||||
'credential:read': ['list_credentials', 'list_n8n_gateway_services', 'explore_node_resources'],
|
||||
'dataTable:read': ['search_data_tables', 'get_data_table_rows'],
|
||||
// Writing requires finding tables, so search rides along.
|
||||
'dataTable:write': [
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const MISSING_PROTOCOL_VERSION_ERROR_MESSAGE =
|
|||
/**
|
||||
* Tool name constants
|
||||
*/
|
||||
export const LIST_N8N_CONNECT_SERVICES_TOOL_NAME = 'list_n8n_connect_services';
|
||||
export const LIST_N8N_GATEWAY_SERVICES_TOOL_NAME = 'list_n8n_gateway_services';
|
||||
export const MCP_CALL_AGENT_TOOL_NAME = 'call_agent';
|
||||
export const MCP_CREATE_AGENT_TOOL_NAME = 'create_agent';
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ import { createGetWorkflowHistoryTool } from './tools/get-workflow-history.tool'
|
|||
import { createGetWorkflowVersionTool } from './tools/get-workflow-version.tool';
|
||||
import { createGetWorkflowVersionsDiffTool } from './tools/get-workflow-versions-diff.tool';
|
||||
import { createListCredentialsTool } from './tools/list-credentials.tool';
|
||||
import { createListN8nConnectServicesTool } from './tools/list-n8n-connect-services.tool';
|
||||
import { createListN8nGatewayServicesTool } from './tools/list-n8n-gateway-services.tool';
|
||||
import { createListTagsTool } from './tools/list-tags.tool';
|
||||
import { createMoveWorkflowsToFolderTool } from './tools/move-workflows-to-folder.tool';
|
||||
import { createPrepareTestPinDataTool } from './tools/prepare-workflow-pin-data.tool';
|
||||
|
|
@ -560,13 +560,13 @@ export class McpService {
|
|||
this.aiGatewayService,
|
||||
);
|
||||
|
||||
const listN8nConnectServicesTool = createListN8nConnectServicesTool(
|
||||
const listN8nGatewayServicesTool = createListN8nGatewayServicesTool(
|
||||
user,
|
||||
this.aiGatewayService,
|
||||
this.telemetry,
|
||||
);
|
||||
registerIfAllowed(listCredentialsTool);
|
||||
registerIfAllowed(listN8nConnectServicesTool);
|
||||
registerIfAllowed(listN8nGatewayServicesTool);
|
||||
|
||||
if (!this.globalConfig.tags.disabled) {
|
||||
const listTagsTool = createListTagsTool(user, this.tagService, this.telemetry);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type { AiGatewayService } from '@/services/ai-gateway.service';
|
|||
import type { Telemetry } from '@/telemetry';
|
||||
|
||||
import { toN8nConnectCoverage } from '../mcp-ai-gateway.helper';
|
||||
import { LIST_N8N_CONNECT_SERVICES_TOOL_NAME, USER_CALLED_MCP_TOOL_EVENT } from '../mcp.constants';
|
||||
import { LIST_N8N_GATEWAY_SERVICES_TOOL_NAME, USER_CALLED_MCP_TOOL_EVENT } from '../mcp.constants';
|
||||
import type {
|
||||
N8nConnectCoverage,
|
||||
ToolDefinition,
|
||||
|
|
@ -51,14 +51,14 @@ const n8nConnectSchema = z
|
|||
.object({
|
||||
credentialTypes: z
|
||||
.array(z.string())
|
||||
.describe('Credential type names that n8n credits can provide (e.g. "openAiApi").'),
|
||||
.describe('Credential type names that Gateway credits can provide (e.g. "openAiApi").'),
|
||||
nodes: z
|
||||
.array(z.string())
|
||||
.describe('Node types covered by n8n credits (e.g. "@n8n/n8n-nodes-langchain.openAi").'),
|
||||
.describe('Node types covered by Gateway credits (e.g. "@n8n/n8n-nodes-langchain.openAi").'),
|
||||
})
|
||||
.optional()
|
||||
.describe(
|
||||
`Present when n8n credits is available for this instance. Omitted otherwise. Candidate coverage only — actual eligibility for a managed credential also depends on the node action, minimum type version, and hidden properties; call ${LIST_N8N_CONNECT_SERVICES_TOOL_NAME} for the authoritative contract.`,
|
||||
`Present when Gateway credits are available for this instance. Omitted otherwise. Candidate coverage only — actual eligibility for a managed credential also depends on the node action, minimum type version, and hidden properties; call ${LIST_N8N_GATEWAY_SERVICES_TOOL_NAME} for the authoritative contract.`,
|
||||
);
|
||||
|
||||
const outputSchema = {
|
||||
|
|
@ -80,7 +80,7 @@ const outputSchema = {
|
|||
)
|
||||
.describe('List of credentials accessible to the current user'),
|
||||
count: z.number().int().min(0).describe('Number of credentials returned'),
|
||||
n8nConnect: n8nConnectSchema,
|
||||
gatewayCredits: n8nConnectSchema,
|
||||
error: z.string().optional().describe('Error message when the tool failed'),
|
||||
} satisfies z.ZodRawShape;
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ export type ListCredentialsItem = {
|
|||
export type ListCredentialsResult = {
|
||||
data: ListCredentialsItem[];
|
||||
count: number;
|
||||
n8nConnect?: N8nConnectCoverage;
|
||||
gatewayCredits?: N8nConnectCoverage;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ export const createListCredentialsTool = (
|
|||
});
|
||||
|
||||
const coverage = toN8nConnectCoverage(await aiGatewayService.isAvailable());
|
||||
if (coverage) payload.n8nConnect = coverage;
|
||||
if (coverage) payload.gatewayCredits = coverage;
|
||||
|
||||
telemetryPayload.results = {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import z from 'zod';
|
|||
import type { AiGatewayService } from '@/services/ai-gateway.service';
|
||||
import type { Telemetry } from '@/telemetry';
|
||||
|
||||
import { LIST_N8N_CONNECT_SERVICES_TOOL_NAME, USER_CALLED_MCP_TOOL_EVENT } from '../mcp.constants';
|
||||
import { LIST_N8N_GATEWAY_SERVICES_TOOL_NAME, USER_CALLED_MCP_TOOL_EVENT } from '../mcp.constants';
|
||||
import type { ToolDefinition, UserCalledMCPToolEventPayload } from '../mcp.types';
|
||||
|
||||
const inputSchema = {} satisfies z.ZodRawShape;
|
||||
|
|
@ -13,16 +13,16 @@ const outputSchema = {
|
|||
available: z
|
||||
.boolean()
|
||||
.describe(
|
||||
'True when n8n credits is available for this instance. When false, the remaining fields are omitted.',
|
||||
'True when Gateway credits are available for this instance. When false, the remaining fields are omitted.',
|
||||
),
|
||||
credentialTypes: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Credential type names Connect can provide (e.g. "openAiApi").'),
|
||||
.describe('Credential type names Gateway credits can provide (e.g. "openAiApi").'),
|
||||
nodes: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Node types Connect covers (e.g. "@n8n/n8n-nodes-langchain.openAi").'),
|
||||
.describe('Node types Gateway credits cover (e.g. "@n8n/n8n-nodes-langchain.openAi").'),
|
||||
supportedActions: z
|
||||
.record(z.record(z.array(z.string())))
|
||||
.optional()
|
||||
|
|
@ -32,35 +32,37 @@ const outputSchema = {
|
|||
minNodeTypeVersion: z
|
||||
.record(z.number())
|
||||
.optional()
|
||||
.describe('Minimum `typeVersion` per node type for Connect coverage.'),
|
||||
.describe('Minimum `typeVersion` per node type for Gateway credits coverage.'),
|
||||
hiddenNodeProperties: z
|
||||
.record(z.array(z.string()))
|
||||
.optional()
|
||||
.describe('Per-node property names hidden from the user when Connect provides the credential.'),
|
||||
.describe(
|
||||
'Per-node property names hidden from the user when Gateway credits provide the credential.',
|
||||
),
|
||||
} satisfies z.ZodRawShape;
|
||||
|
||||
/**
|
||||
* Returns the current n8n Connect coverage snapshot for the
|
||||
* instance: which node types and credential types Connect can serve, plus
|
||||
* Returns the current Gateway credits coverage snapshot for the
|
||||
* instance: which node types and credential types Gateway credits can serve, plus
|
||||
* per-node action allowlists, min versions, and hidden properties.
|
||||
*
|
||||
* Read-only. Omits all coverage fields when Connect is unavailable
|
||||
* Read-only. Omits all coverage fields when Gateway credits are unavailable
|
||||
* (unlicensed, misconfigured, or gateway down) — callers should key on
|
||||
* `available: false` and fall back to user credentials.
|
||||
*/
|
||||
export const createListN8nConnectServicesTool = (
|
||||
export const createListN8nGatewayServicesTool = (
|
||||
user: User,
|
||||
aiGatewayService: AiGatewayService,
|
||||
telemetry: Telemetry,
|
||||
): ToolDefinition<typeof inputSchema> => ({
|
||||
name: LIST_N8N_CONNECT_SERVICES_TOOL_NAME,
|
||||
name: LIST_N8N_GATEWAY_SERVICES_TOOL_NAME,
|
||||
config: {
|
||||
description:
|
||||
'List n8n credits coverage: node and credential types the platform can provide managed credentials for, plus supported resource+operation combinations, minimum type versions, and hidden node properties. Use this to decide which nodes let the user skip credential setup.',
|
||||
'List Gateway credits coverage: node and credential types the platform can provide managed credentials for, plus supported resource+operation combinations, minimum type versions, and hidden node properties. Use this to decide which nodes let the user skip credential setup.',
|
||||
inputSchema,
|
||||
outputSchema,
|
||||
annotations: {
|
||||
title: 'List services available with n8n credits',
|
||||
title: 'List services available with Gateway credits',
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
|
|
@ -70,7 +72,7 @@ export const createListN8nConnectServicesTool = (
|
|||
handler: async () => {
|
||||
const telemetryPayload: UserCalledMCPToolEventPayload = {
|
||||
user_id: user.id,
|
||||
tool_name: LIST_N8N_CONNECT_SERVICES_TOOL_NAME,
|
||||
tool_name: LIST_N8N_GATEWAY_SERVICES_TOOL_NAME,
|
||||
};
|
||||
|
||||
const availability = await aiGatewayService.isAvailable();
|
||||
|
|
@ -128,7 +128,7 @@ const outputSchema = {
|
|||
.enum(['user', 'aiGateway'])
|
||||
.optional()
|
||||
.describe(
|
||||
'Where the credential came from: "user" for an existing user credential, "aiGateway" for a credential managed via n8n credits.',
|
||||
'Where the credential came from: "user" for an existing user credential, "aiGateway" for a credential managed via Gateway credits.',
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import type { Telemetry } from '@/telemetry';
|
|||
import { MCP_CREDENTIALS_AUTOASSIGN_EVENT } from '../../mcp.constants';
|
||||
|
||||
/** Display name written into AI Gateway-managed credential sentinels. User-facing brand. */
|
||||
const AI_GATEWAY_CREDENTIAL_NAME = 'n8n credits';
|
||||
const AI_GATEWAY_CREDENTIAL_NAME = 'Gateway credits';
|
||||
|
||||
/**
|
||||
* Attach the managed (n8n-credits) sentinel to `credentialType` and, unless it's
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { Telemetry } from '@/telemetry';
|
|||
import { CODE_BUILDER_GET_NODE_TYPES_TOOL } from './constants';
|
||||
import { toN8nConnectCoverage } from '../../mcp-ai-gateway.helper';
|
||||
import {
|
||||
LIST_N8N_CONNECT_SERVICES_TOOL_NAME,
|
||||
LIST_N8N_GATEWAY_SERVICES_TOOL_NAME,
|
||||
USER_CALLED_MCP_TOOL_EVENT,
|
||||
} from '../../mcp.constants';
|
||||
import type {
|
||||
|
|
@ -36,18 +36,20 @@ const inputSchema = {
|
|||
|
||||
const outputSchema = {
|
||||
definitions: z.string().describe('TypeScript type definitions for the requested nodes'),
|
||||
n8nConnect: z
|
||||
gatewayCredits: z
|
||||
.object({
|
||||
credentialTypes: z.array(z.string()).describe('Credential types n8n Connect can provide.'),
|
||||
credentialTypes: z
|
||||
.array(z.string())
|
||||
.describe('Credential types Gateway credits can provide.'),
|
||||
nodes: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'Node types n8n Connect may cover. Prefer these when the user has not specified an integration. Candidate coverage only — exact eligibility also depends on the node action, minimum type version, and hidden properties.',
|
||||
'Node types Gateway credits may cover. Prefer these when the user has not specified an integration. Candidate coverage only — exact eligibility also depends on the node action, minimum type version, and hidden properties.',
|
||||
),
|
||||
})
|
||||
.optional()
|
||||
.describe(
|
||||
`Present when n8n Connect is available. Candidate coverage — cross-reference against the returned node types, but call ${LIST_N8N_CONNECT_SERVICES_TOOL_NAME} for exact eligibility (supported actions, min versions, hidden properties).`,
|
||||
`Present when Gateway credits are available. Candidate coverage — cross-reference against the returned node types, but call ${LIST_N8N_GATEWAY_SERVICES_TOOL_NAME} for exact eligibility (supported actions, min versions, hidden properties).`,
|
||||
),
|
||||
} satisfies z.ZodRawShape;
|
||||
|
||||
|
|
@ -95,12 +97,12 @@ export const createGetWorkflowNodeTypesTool = (
|
|||
|
||||
const structured: {
|
||||
definitions: string;
|
||||
n8nConnect?: N8nConnectCoverage;
|
||||
gatewayCredits?: N8nConnectCoverage;
|
||||
} = { definitions: result };
|
||||
const coverage = toN8nConnectCoverage(availability);
|
||||
if (coverage) structured.n8nConnect = coverage;
|
||||
if (coverage) structured.gatewayCredits = coverage;
|
||||
|
||||
const text = coverage ? `${result}\n\nn8nConnect: ${JSON.stringify(coverage)}` : result;
|
||||
const text = coverage ? `${result}\n\ngatewayCredits: ${JSON.stringify(coverage)}` : result;
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
CODE_BUILDER_VALIDATE_TOOL,
|
||||
CODE_BUILDER_VALIDATE_NODE_TOOL,
|
||||
} from './constants';
|
||||
import { LIST_N8N_CONNECT_SERVICES_TOOL_NAME } from '../../mcp.constants';
|
||||
import { LIST_N8N_GATEWAY_SERVICES_TOOL_NAME } from '../../mcp.constants';
|
||||
|
||||
export type McpInstructionsOptions = {
|
||||
/**
|
||||
|
|
@ -64,7 +64,7 @@ Node groups: when a workflow has several distinct stages, organise it into named
|
|||
const N8N_CONNECT_HINT = isN8nConnectAvailable
|
||||
? `
|
||||
|
||||
Explore nodes covered by n8n credits when the user has not specified a particular integration. n8n credits let users consume LLMs and third-party services directly through n8n with usage-based billing, so they can skip credential setup. Discovery tools (${CODE_BUILDER_SEARCH_NODES_TOOL.toolName}, ${CODE_BUILDER_GET_NODE_TYPES_TOOL.toolName}) and list_credentials return an optional \`n8nConnect.nodes\` array when the instance has n8n credits available. Nodes in that array can attach a managed credential automatically — the workflow runs without the user configuring keys. If the user asked for a specific integration or none of the covered nodes fit, use the requested integration with regular credentials. Call ${LIST_N8N_CONNECT_SERVICES_TOOL_NAME} if you need details (per-node supported resource+operation combos, min type versions, hidden properties).`
|
||||
Explore nodes covered by Gateway credits when the user has not specified a particular integration. Gateway credits let users consume LLMs and third-party services directly through n8n with usage-based billing, so they can skip credential setup. Discovery tools (${CODE_BUILDER_SEARCH_NODES_TOOL.toolName}, ${CODE_BUILDER_GET_NODE_TYPES_TOOL.toolName}) and list_credentials return an optional \`gatewayCredits.nodes\` array when the instance has Gateway credits available. Nodes in that array can attach a managed credential automatically — the workflow runs without the user configuring keys. If the user asked for a specific integration or none of the covered nodes fit, use the requested integration with regular credentials. Call ${LIST_N8N_GATEWAY_SERVICES_TOOL_NAME} if you need details (per-node supported resource+operation combos, min type versions, hidden properties).`
|
||||
: '';
|
||||
|
||||
const ARTIFACT_ROUTING_INSTRUCTIONS = `Choose the artifact before choosing build tools. Treat an explicit artifact request as a routing instruction: if the user asks to build or create an "agent" or "assistant", default to a first-class n8n Agent; if they explicitly ask for a workflow or an AI Agent node, use the workflow tools.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { Telemetry } from '@/telemetry';
|
|||
import { CODE_BUILDER_SEARCH_NODES_TOOL } from './constants';
|
||||
import { toN8nConnectCoverage } from '../../mcp-ai-gateway.helper';
|
||||
import {
|
||||
LIST_N8N_CONNECT_SERVICES_TOOL_NAME,
|
||||
LIST_N8N_GATEWAY_SERVICES_TOOL_NAME,
|
||||
USER_CALLED_MCP_TOOL_EVENT,
|
||||
} from '../../mcp.constants';
|
||||
import type {
|
||||
|
|
@ -36,18 +36,20 @@ const outputSchema = {
|
|||
results: z
|
||||
.string()
|
||||
.describe('Search results with matching node IDs, discriminators, and related nodes'),
|
||||
n8nConnect: z
|
||||
gatewayCredits: z
|
||||
.object({
|
||||
credentialTypes: z.array(z.string()).describe('Credential types n8n Connect can provide.'),
|
||||
credentialTypes: z
|
||||
.array(z.string())
|
||||
.describe('Credential types Gateway credits can provide.'),
|
||||
nodes: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'Node types n8n Connect may cover. Prefer these when the user has not specified an integration. Candidate coverage only — exact eligibility also depends on the node action, minimum type version, and hidden properties.',
|
||||
'Node types Gateway credits may cover. Prefer these when the user has not specified an integration. Candidate coverage only — exact eligibility also depends on the node action, minimum type version, and hidden properties.',
|
||||
),
|
||||
})
|
||||
.optional()
|
||||
.describe(
|
||||
`Present when n8n Connect is available. Candidate coverage — cross-reference against the search results, but call ${LIST_N8N_CONNECT_SERVICES_TOOL_NAME} for exact eligibility (supported actions, min versions, hidden properties).`,
|
||||
`Present when Gateway credits are available. Candidate coverage — cross-reference against the search results, but call ${LIST_N8N_GATEWAY_SERVICES_TOOL_NAME} for exact eligibility (supported actions, min versions, hidden properties).`,
|
||||
),
|
||||
} satisfies z.ZodRawShape;
|
||||
|
||||
|
|
@ -122,14 +124,14 @@ export const createSearchWorkflowNodesTool = (
|
|||
|
||||
const structured: {
|
||||
results: string;
|
||||
n8nConnect?: N8nConnectCoverage;
|
||||
gatewayCredits?: N8nConnectCoverage;
|
||||
} = {
|
||||
results,
|
||||
};
|
||||
const coverage = toN8nConnectCoverage(availability);
|
||||
if (coverage) structured.n8nConnect = coverage;
|
||||
if (coverage) structured.gatewayCredits = coverage;
|
||||
|
||||
const text = coverage ? `${results}\n\nn8nConnect: ${JSON.stringify(coverage)}` : results;
|
||||
const text = coverage ? `${results}\n\ngatewayCredits: ${JSON.stringify(coverage)}` : results;
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user