@n8n/n8n-nodes-langchain
Version:
523 lines • 23 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.configureActivityCallback = exports.microsoftMcpServers = exports.buildMcpToolName = void 0;
exports.extractActivityInfo = extractActivityInfo;
exports.createMicrosoftAgentApplication = createMicrosoftAgentApplication;
exports.getMicrosoftMcpTools = getMicrosoftMcpTools;
exports.disposeActivityResources = disposeActivityResources;
exports.configureAdapterProcessCallback = configureAdapterProcessCallback;
const agents_hosting_1 = require("@microsoft/agents-hosting");
const n8n_workflow_1 = require("n8n-workflow");
const agents_a365_observability_1 = require("@microsoft/agents-a365-observability");
const agents_activity_1 = require("@microsoft/agents-activity");
const langchain_utils_1 = require("./langchain-utils");
const agents_a365_tooling_1 = require("@microsoft/agents-a365-tooling");
const agents_a365_runtime_1 = require("@microsoft/agents-a365-runtime");
const n8n_core_1 = require("n8n-core");
const ai_utilities_1 = require("@n8n/ai-utilities");
const utils_1 = require("../../mcp/shared/utils");
const utils_2 = require("../../mcp/McpClientTool/utils");
Object.defineProperty(exports, "buildMcpToolName", { enumerable: true, get: function () { return utils_2.buildMcpToolName; } });
function extractActivityInfo(activity) {
return {
id: activity.id,
type: activity.type,
channelId: activity.channelId,
conversationId: activity.conversation?.id,
from: activity.from
? {
id: activity.from.id,
name: activity.from.name,
}
: undefined,
recipient: activity.recipient
? {
id: activity.recipient.id,
name: activity.recipient.name,
}
: undefined,
timestamp: activity.timestamp instanceof Date ? activity.timestamp.toISOString() : activity.timestamp,
locale: activity.locale,
};
}
exports.microsoftMcpServers = [
{ name: 'Admin 365', value: 'mcp_Admin365_GraphTools' },
{ name: 'Admin Tools', value: 'mcp_AdminTools' },
{ name: 'Calendar', value: 'mcp_CalendarTools' },
{ name: 'DA Search', value: 'mcp_DASearch' },
{ name: 'Excel', value: 'mcp_ExcelServer' },
{ name: 'Knowledge', value: 'mcp_KnowledgeTools' },
{ name: 'M365 Copilot', value: 'mcp_M365Copilot' },
{ name: 'Mail', value: 'mcp_MailTools' },
{ name: 'OneDrive', value: 'mcp_OneDriveRemoteServer' },
{ name: 'OneDrive & SharePoint', value: 'mcp_ODSPRemoteServer' },
{ name: 'Planner', value: 'mcp_PlannerServer' },
{ name: 'SharePoint', value: 'mcp_SharePointRemoteServer' },
{ name: 'SharePoint Lists', value: 'mcp_SharePointListsTools' },
{ name: 'Task Personalization', value: 'mcp_TaskPersonalizationServer' },
{ name: 'Teams', value: 'mcp_TeamsServer' },
{ name: 'Teams Canary', value: 'mcp_TeamsCanaryServer' },
{ name: 'Teams V1', value: 'mcp_TeamsServerV1' },
{ name: 'Web Search', value: 'mcp_WebSearchTools' },
{ name: 'Windows 365 Computer Use', value: 'mcp_W365ComputerUse' },
{ name: 'Word', value: 'mcp_WordServer' },
];
const MS_TENANT_ID_HEADER = 'x-ms-tenant-id';
const MICROSOFT_TOOL_OPTIONS = { orchestratorName: 'LangChain' };
function hasAuthorizationHeader(headers) {
return Object.keys(headers).some((headerName) => headerName.toLowerCase() === 'authorization');
}
function getAuthorizationHeader(headers) {
if (!headers)
return undefined;
for (const [headerName, headerValue] of Object.entries(headers)) {
if (headerName.toLowerCase() === 'authorization') {
return headerValue;
}
}
return undefined;
}
function isRecord(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function getStringProperty(value, key) {
const property = value[key];
return typeof property === 'string' ? property : undefined;
}
function getHeadersProperty(value) {
const headers = value.headers;
if (!isRecord(headers))
return undefined;
const result = {};
for (const [headerName, headerValue] of Object.entries(headers)) {
if (typeof headerValue === 'string') {
result[headerName] = headerValue;
}
}
return result;
}
function getRawMcpServers(payload) {
if (Array.isArray(payload))
return payload;
if (!isRecord(payload))
return undefined;
const mcpServers = payload.mcpServers;
if (Array.isArray(mcpServers))
return mcpServers;
const value = payload.value;
if (Array.isArray(value))
return value;
return undefined;
}
function getMcpPlatformBaseUrl() {
return agents_a365_tooling_1.defaultToolingConfigurationProvider.getConfiguration().mcpPlatformEndpoint;
}
function getToolingGatewayUrl(agenticAppId) {
return `${getMcpPlatformBaseUrl()}/agents/v2/${agenticAppId}/mcpServers`;
}
function getMcpServerUrl(mcpServerName) {
return `${getMcpPlatformBaseUrl()}/agents/servers/${mcpServerName}/`;
}
function normalizeMcpServerConfig(rawServer) {
if (!isRecord(rawServer))
return undefined;
const mcpServerName = getStringProperty(rawServer, 'mcpServerName') ??
getStringProperty(rawServer, 'mcpServerUniqueName');
if (!mcpServerName)
return undefined;
return {
mcpServerName,
url: getStringProperty(rawServer, 'url') ?? getMcpServerUrl(mcpServerName),
headers: getHeadersProperty(rawServer),
audience: getStringProperty(rawServer, 'audience'),
scope: getStringProperty(rawServer, 'scope'),
publisher: getStringProperty(rawServer, 'publisher'),
};
}
async function getMcpServerConfigsWithoutAudienceTokens(turnContext, mcpAuthToken, secureEgressFilter) {
agents_a365_tooling_1.Utility.ValidateAuthToken(mcpAuthToken);
const agenticAppId = agents_a365_runtime_1.Utility.ResolveAgentIdentity(turnContext, mcpAuthToken);
const endpoint = getToolingGatewayUrl(agenticAppId);
const response = await (0, ai_utilities_1.proxyFetch)({
input: endpoint,
init: {
headers: agents_a365_tooling_1.Utility.GetToolRequestHeaders(mcpAuthToken, turnContext, MICROSOFT_TOOL_OPTIONS),
},
lookup: secureEgressFilter.createSecureLookup(),
});
if (!response.ok) {
throw new Error(`Failed to read MCP servers from endpoint: ${response.status}`);
}
const payload = await response.json();
const rawServers = getRawMcpServers(payload);
if (!rawServers) {
console.error('Microsoft MCP server discovery returned an unsupported payload shape', {
payloadType: Array.isArray(payload) ? 'array' : typeof payload,
});
throw new Error('Failed to read MCP servers from endpoint: response is not a server list');
}
const servers = rawServers
.map((rawServer) => normalizeMcpServerConfig(rawServer))
.filter((server) => server !== undefined);
console.warn(`Microsoft MCP server discovery completed: ${servers.length} servers found`);
return servers;
}
async function attachMcpServerAuthorization(server, turnContext, authorization, mcpAuthToken) {
const sharedScope = agents_a365_tooling_1.defaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope;
const scope = (0, agents_a365_tooling_1.resolveTokenScopeForServer)(server, sharedScope);
if (scope === sharedScope && hasAuthorizationHeader(server.headers ?? {}))
return server;
const token = scope === sharedScope
? mcpAuthToken
: await agents_a365_runtime_1.AgenticAuthenticationService.GetAgenticUserToken(authorization, 'agentic', turnContext, [scope]);
if (!token) {
throw new Error(`Failed to obtain token for MCP server '${server.mcpServerName}'`);
}
return {
...server,
headers: {
...server.headers,
Authorization: `Bearer ${token}`,
},
};
}
function getMcpServerHeaders(server, turnContext, mcpAuthToken, tenantId) {
const headers = {
...agents_a365_tooling_1.Utility.GetToolRequestHeaders(mcpAuthToken, turnContext, MICROSOFT_TOOL_OPTIONS),
};
for (const [headerName, headerValue] of Object.entries(server.headers ?? {})) {
if (headerName.toLowerCase() !== 'authorization') {
headers[headerName] = headerValue;
}
}
const serverAuthorization = getAuthorizationHeader(server.headers);
if (serverAuthorization) {
headers.Authorization = serverAuthorization;
}
else if (mcpAuthToken && !hasAuthorizationHeader(headers)) {
headers.Authorization = `Bearer ${mcpAuthToken}`;
}
if (tenantId) {
headers[MS_TENANT_ID_HEADER] = tenantId;
}
return headers;
}
function isMicrosoftObservabilityEnabled() {
return (process.env.ENABLE_OBSERVABILITY === 'true' &&
process.env.ENABLE_A365_OBSERVABILITY_EXPORTER === 'true');
}
function createMicrosoftAgentApplication(credentials) {
const authConfig = createAuthConfig(credentials);
const adapter = new agents_hosting_1.CloudAdapter(authConfig);
const storage = new agents_hosting_1.MemoryStorage();
const agent = new agents_hosting_1.AgentApplication({
adapter,
storage,
authorization: {
agentic: {
type: 'agentic',
scopes: ['https://graph.microsoft.com/.default'],
},
},
});
return { agent, authConfig };
}
async function getMicrosoftMcpTools(turnContext, authorization, mcpAuthToken, selectedTools, secureEgressFilter) {
const configService = new agents_a365_tooling_1.McpToolServerConfigurationService();
let shouldAttachServerAuthorization = false;
let servers;
try {
servers = await configService.listToolServers(turnContext, authorization, 'agentic', mcpAuthToken, MICROSOFT_TOOL_OPTIONS);
}
catch (error) {
console.warn('Microsoft SDK listToolServers failed, falling back to direct discovery');
servers = await getMcpServerConfigsWithoutAudienceTokens(turnContext, mcpAuthToken, secureEgressFilter);
shouldAttachServerAuthorization = true;
}
if (servers.length === 0)
return undefined;
if (selectedTools?.length) {
servers = servers.filter((server) => selectedTools.includes(server.mcpServerName));
}
const tenantId = turnContext.activity.recipient?.tenantId || turnContext.activity?.channelData?.tenant?.id;
const toolkits = [];
const clients = [];
const mcpToolCallLogs = [];
const timeout = 60000;
for (const server of servers) {
let authorizedServer = server;
if (shouldAttachServerAuthorization) {
try {
authorizedServer = await attachMcpServerAuthorization(server, turnContext, authorization, mcpAuthToken);
}
catch (error) {
console.warn(`Skipping MCP server ${server.mcpServerName}: failed to authorize`, error);
continue;
}
}
const headers = getMcpServerHeaders(authorizedServer, turnContext, mcpAuthToken, tenantId);
const clientResult = await (0, utils_1.connectMcpClient)({
serverTransport: 'httpStreamable',
endpointUrl: authorizedServer.url,
headers,
name: 'Microsoft-Agent-365',
version: 1,
secureEgressFilter,
});
if (!clientResult.ok) {
console.warn(`Skipping MCP server ${server.mcpServerName}: failed to connect`, clientResult.error);
continue;
}
const client = clientResult.result;
clients.push(client);
let mcpTools;
try {
mcpTools = await (0, utils_1.getAllTools)(client);
}
catch (error) {
console.warn(`Skipping MCP server ${server.mcpServerName}: failed to list tools`, error);
continue;
}
const serverName = server.mcpServerName;
const serverTools = mcpTools.map((tool) => {
const prefixedName = (0, utils_2.buildMcpToolName)(serverName, tool.name);
const callToolWithLogging = async (args) => {
let isError = false;
const callTool = (0, utils_2.createCallTool)(tool.name, client, timeout, (errorMessage) => {
console.error(`Tool "${tool.name}" execution error:`, errorMessage);
isError = true;
});
const start = Date.now();
const result = await callTool(args);
mcpToolCallLogs.push({
serverName,
toolName: prefixedName,
input: args,
output: result,
isError,
durationMs: Date.now() - start,
timestamp: new Date().toISOString(),
});
return result;
};
return (0, utils_2.mcpToolToDynamicTool)({ ...tool, name: prefixedName }, callToolWithLogging);
});
if (serverTools.length > 0) {
toolkits.push(new n8n_core_1.StructuredToolkit(serverTools));
}
}
if (toolkits.length === 0)
return undefined;
return {
toolkits,
logs: mcpToolCallLogs,
client: {
async close() {
await Promise.all(clients.map(async (c) => await c.close()));
},
},
};
}
const configureActivityCallback = (nodeContext, credentials, mcpTokenRef, authorization, activityCapture) => {
const systemPrompt = nodeContext.getNodeParameter('systemPrompt');
const { clientId, tenantId } = credentials;
return async (turnContext) => {
const agentId = turnContext.activity.recipient?.agenticAppId ?? clientId;
const agentName = turnContext.activity.recipient?.name ?? 'Microsoft Agent 365';
const tenantDetails = {
tenantId: turnContext.activity.recipient?.tenantId ?? tenantId ?? '',
};
const conversationId = turnContext.activity.conversation?.id;
const inputText = turnContext.activity.text || '';
const baggageScope = new agents_a365_observability_1.BaggageBuilder()
.tenantId(tenantDetails.tenantId)
.agentId(agentId)
.agentName(agentName)
.conversationId(conversationId)
.build();
await baggageScope.run(async () => {
const request = {
content: inputText || 'Unknown text',
sessionId: conversationId,
conversationId,
};
const invokeScopeDetails = {};
const agentDetails = {
agentId,
agentName,
tenantId: tenantDetails.tenantId,
};
const invokeAgentScope = agents_a365_observability_1.InvokeAgentScope.start(request, invokeScopeDetails, agentDetails);
await invokeAgentScope.withActiveSpanAsync(async () => {
invokeAgentScope.recordInputMessages([inputText || 'Unknown text']);
let addMemberMessage = false;
if (inputText.trimStart().startsWith('<addmember>')) {
addMemberMessage = true;
}
let mcpClient = undefined;
let microsoftMcpToolkits = undefined;
let mcpLogs = undefined;
if (!addMemberMessage && mcpTokenRef.token) {
try {
const useMcpTools = nodeContext.getNodeParameter('useMcpTools', false);
if (useMcpTools) {
let selectedTools = undefined;
const include = nodeContext.getNodeParameter('include', 'all');
if (include === 'selected') {
const selected = nodeContext.getNodeParameter('includeTools', []);
selectedTools = exports.microsoftMcpServers
.filter((server) => selected.includes(server.value))
.map((server) => server.value);
}
const result = await getMicrosoftMcpTools(turnContext, authorization, mcpTokenRef.token, selectedTools, nodeContext.helpers.getSecureEgressFilter());
mcpClient = result?.client;
microsoftMcpToolkits = result?.toolkits;
mcpLogs = result?.logs;
}
}
catch (error) {
console.error('Error retrieving MCP tools:', error);
}
}
try {
let response = '';
if (addMemberMessage) {
response = nodeContext.getNodeParameter('options.welcomeMessage', '');
}
else {
response = await (0, langchain_utils_1.invokeAgent)(nodeContext, inputText, systemPrompt, {
configurable: { thread_id: turnContext.activity.conversation.id },
}, microsoftMcpToolkits);
}
invokeAgentScope.recordOutputMessages([`n8n Agent Response: ${response}`]);
await turnContext.sendActivity(response);
}
finally {
if (mcpLogs?.length) {
activityCapture.mcpToolLogs = mcpLogs;
}
await disposeActivityResources(invokeAgentScope, mcpClient);
}
});
});
};
};
exports.configureActivityCallback = configureActivityCallback;
async function disposeActivityResources(invokeAgentScope, mcpClient) {
try {
invokeAgentScope.dispose();
}
catch (error) {
console.error('Failed to dispose invokeAgentScope:', error);
}
if (mcpClient) {
try {
await mcpClient.close();
}
catch (error) {
console.error('Failed to close MCP client connections:', error);
}
}
}
function configureAdapterProcessCallback(nodeContext, agent, credentials, activityCapture) {
return async (turnContext) => {
let observability;
if (isMicrosoftObservabilityEnabled()) {
const observabilityScopes = [
...agents_a365_observability_1.defaultObservabilityConfigurationProvider.getConfiguration()
.observabilityAuthenticationScopes,
];
const { token: aauToken } = await agent.authorization.exchangeToken(turnContext, observabilityScopes, 'agentic');
observability = agents_a365_observability_1.ObservabilityManager.configure((builder) => builder
.withService('n8n-microsoft-agent-365')
.withTokenResolver((_agentId, _tenantId) => aauToken || ''));
observability.start();
}
const mcpTokenRef = { token: undefined };
try {
turnContext.turnState.set('AgenticAuthorization/agentic', undefined);
const tokenResult = await agent.authorization.exchangeToken(turnContext, 'agentic', {
scopes: [
agents_a365_tooling_1.defaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope,
],
});
mcpTokenRef.token = tokenResult.token;
if (!mcpTokenRef.token) {
nodeContext.logger.warn('Microsoft Agent 365: no agentic tooling token was issued; Work IQ (MCP) tools are unavailable for this turn');
}
}
catch (error) {
nodeContext.logger.warn(`Microsoft Agent 365: failed to obtain agentic tooling token; Work IQ (MCP) tools are unavailable for this turn: ${error instanceof Error ? error.message : String(error)}`);
}
try {
const originalSendActivity = turnContext.sendActivity.bind(turnContext);
activityCapture.input = turnContext.activity.text || '';
activityCapture.activity = extractActivityInfo(turnContext.activity);
const sendActivityWrapper = async (activityOrText) => {
if (typeof activityOrText === 'string') {
activityCapture.output.push(activityOrText);
}
else if (activityOrText.text) {
activityCapture.output.push(activityOrText.text);
}
return await originalSendActivity(activityOrText);
};
turnContext.sendActivity = sendActivityWrapper;
const onActivity = (0, exports.configureActivityCallback)(nodeContext, credentials, mcpTokenRef, agent.authorization, activityCapture);
agent.onActivity(agents_activity_1.ActivityTypes.Message, onActivity, ['agentic']);
await agent.run(turnContext);
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(nodeContext.getNode(), error);
}
finally {
if (observability) {
try {
const OBSERVABILITY_SHUTDOWN_TIMEOUT_MS = 5000;
await Promise.race([
observability.shutdown(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Observability shutdown timed out')), OBSERVABILITY_SHUTDOWN_TIMEOUT_MS)),
]);
}
catch (error) {
console.warn('Failed to shut down observability:', error);
}
}
}
};
}
const createAuthConfig = (credentials) => {
const { clientId, tenantId, clientSecret } = credentials;
const connections = new Map();
connections.set('serviceConnection', {
clientId,
clientSecret,
tenantId,
authority: 'https://login.microsoftonline.com',
issuers: [
'https://api.botframework.com',
`https://sts.windows.net/${tenantId}/`,
`https://login.microsoftonline.com/${tenantId}/v2.0`,
],
});
const config = {
clientId,
clientSecret,
tenantId,
authority: 'https://login.microsoftonline.com',
issuers: [
'https://api.botframework.com',
`https://sts.windows.net/${tenantId}/`,
`https://login.microsoftonline.com/${tenantId}/v2.0`,
],
connections,
connectionsMap: [
{
connection: 'serviceConnection',
serviceUrl: '*',
},
],
};
return config;
};
//# sourceMappingURL=microsoft-utils.js.map