n8n
Version:
n8n Workflow Automation Tool
130 lines • 5.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildVerifyMcpServerTool = buildVerifyMcpServerTool;
const tool_1 = require("@n8n/agents/tool");
const api_types_1 = require("@n8n/api-types");
const zod_1 = require("zod");
const builder_tool_names_1 = require("./builder-tool-names");
const mcp_client_factory_1 = require("../json-config/mcp-client-factory");
const DEFAULT_MCP_VERIFICATION_TIMEOUT_MS = 10_000;
async function listToolsWithinDeadline(client, timeoutMs, abortSignal) {
if (abortSignal?.aborted) {
throw new Error('MCP server verification was cancelled');
}
let timeoutId;
let onAbort;
const control = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(`MCP server verification timed out after ${timeoutMs}ms`));
}, timeoutMs);
if (abortSignal) {
onAbort = () => reject(new Error('MCP server verification was cancelled'));
abortSignal.addEventListener('abort', onAbort, { once: true });
}
});
try {
return await Promise.race([client.listTools(), control]);
}
finally {
if (timeoutId !== undefined)
clearTimeout(timeoutId);
if (onAbort)
abortSignal?.removeEventListener('abort', onAbort);
}
}
const verifyMcpServerInputSchema = zod_1.z.object({
name: zod_1.z
.string()
.min(1)
.max(64)
.refine((name) => name.trim().length > 0, 'MCP server name cannot be blank')
.describe('The user-facing server name; it is normalized for model-facing tool names'),
url: zod_1.z.string().min(1).describe('The MCP server endpoint URL'),
transport: zod_1.z
.enum(['sse', 'streamableHttp'])
.default('streamableHttp')
.describe('Transport type. Defaults to streamableHttp'),
authentication: zod_1.z
.union([api_types_1.McpAuthenticationSchemaTypes, zod_1.z.string().endsWith('McpOAuth2Api')])
.default('none')
.describe('Authentication scheme'),
credential: zod_1.z
.string()
.optional()
.describe('Credential id returned by ask_credential. Required when authentication is not "none"'),
connectionTimeoutMs: zod_1.z
.number()
.int()
.min(1)
.max(120_000)
.optional()
.describe('Timeout in milliseconds for the whole verification (connect + list tools). Defaults to 10000ms'),
});
function buildVerifyMcpServerTool(deps) {
return new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.VERIFY_MCP_SERVER)
.description('Test connectivity to an MCP server before adding it to the agent config. ' +
'Establishes a temporary connection, lists the available tools, then closes the connection. ' +
'Returns { ok: true, tools: [{ name, description }] } on success, or ' +
'{ ok: false, error: string } on failure. ' +
'Tool names are the original MCP names without the model-facing server prefix. ' +
'When a credential is provided and a matching mcpServers entry already exists, ' +
'a successful verify also writes the credential into that entry ' +
'({ credentialApplied: true, configMutated: true, agentId }) — no read_config/patch_config follow-up. ' +
'Call this after ask_credential when authentication is not "none".')
.input(verifyMcpServerInputSchema)
.handler(async (input, ctx) => {
const timeoutMs = input.connectionTimeoutMs ?? DEFAULT_MCP_VERIFICATION_TIMEOUT_MS;
let client;
try {
client = await (0, mcp_client_factory_1.buildMcpClientForServer)({
name: input.name,
url: input.url,
transport: input.transport,
authentication: input.authentication,
credential: input.credential,
connectionTimeoutMs: timeoutMs,
}, deps);
const tools = await listToolsWithinDeadline(client, timeoutMs, ctx.abortSignal);
const mappedTools = tools.map((t) => ({
name: t.mcpToolName ?? t.name,
description: t.description ?? '',
}));
if (input.credential && deps.applyCredentialToMcpServer) {
try {
const { applied } = await deps.applyCredentialToMcpServer(input.name, input.credential);
if (applied && deps.agentId) {
return {
ok: true,
tools: mappedTools,
credentialApplied: true,
configMutated: true,
agentId: deps.agentId,
};
}
}
catch {
return {
ok: true,
tools: mappedTools,
credentialApplied: false,
};
}
}
return {
ok: true,
tools: mappedTools,
};
}
catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
finally {
await client?.close().catch(() => { });
}
})
.build();
}
//# sourceMappingURL=verify-mcp-server.tool.js.map