From 8957c6171e31a0df0cc0eab5cbaef5dbd3024683 Mon Sep 17 00:00:00 2001 From: Riley Evans Date: Thu, 21 May 2026 21:29:30 -0500 Subject: [PATCH 1/3] fix: add ManagedServiceIdentity auth to Consumption MCP connections (#9205) Align Consumption connector with Standard for MCP connections: - Managed MCP path: build connectionProperties with MSI auth and user-assigned identity from WorkflowService().getAppIdentity() - Built-in MCP _buildMcpAuthentication: add WorkflowService fallback for identity when not in parameterValues - Remove debug console.log statements - Update tests with WorkflowService mock initialization --- .../consumption/__tests__/connector.spec.ts | 15 +++++++ .../lib/consumption/connector.ts | 43 ++++++++++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts index 76bbe5aec79..16d0e6eeaf2 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts @@ -2,6 +2,7 @@ import { describe, vi, beforeEach, it, expect } from 'vitest'; import { ConsumptionConnectorService } from '../connector'; import type { IHttpClient } from '../../httpClient'; import { InitConnectionService } from '../../connection'; +import { InitWorkflowService } from '../../workflow'; import type { Connection } from '../../../../utils/src'; describe('ConsumptionConnectorService', () => { @@ -248,6 +249,9 @@ describe('ConsumptionConnectorService', () => { }, } as unknown as Connection), } as any); + InitWorkflowService({ + getAppIdentity: vi.fn().mockReturnValue({ type: 'SystemAssigned' }), + } as any); }); it('should send managedConnection shape for non-builtin connections', async () => { @@ -268,6 +272,11 @@ describe('ConsumptionConnectorService', () => { expect(content.managedConnection).toEqual({ connection: { id: managedConnectionId }, + connectionProperties: { + authentication: { + type: 'ManagedServiceIdentity', + }, + }, }); expect(content.mcpServerPath).toBe('/mcp/path'); expect(content.connection).toBeUndefined(); @@ -320,6 +329,12 @@ describe('ConsumptionConnectorService', () => { return (connectorService as any)._buildMcpAuthentication(props); }; + beforeEach(() => { + InitWorkflowService({ + getAppIdentity: vi.fn().mockReturnValue({ type: 'SystemAssigned' }), + } as any); + }); + it('should return undefined for None auth type', () => { expect(buildAuth({ authenticationType: 'None' })).toBeUndefined(); }); diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts index 1d75f2c61b3..f07c73335d5 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts @@ -1,5 +1,13 @@ import type { OpenAPIV2 } from '../../../utils/src'; -import { ArgumentException, UnsupportedException, optional, equals, getResourceName } from '../../../utils/src'; +import { + ArgumentException, + UnsupportedException, + optional, + equals, + getResourceName, + isArmResourceId, + ResourceIdentityType, +} from '../../../utils/src'; import type { BaseConnectorServiceOptions } from '../base'; import { BaseConnectorService } from '../base'; import { ConnectionService } from '../connection'; @@ -7,6 +15,7 @@ import type { ListDynamicValue, ManagedIdentityRequestProperties, TreeDynamicExt import { pathCombine, unwrapPaginatedResponse } from '../helpers'; import { LoggerService } from '../logger'; import { LogEntryLevel } from '../logging/logEntry'; +import { WorkflowService } from '../workflow'; interface ConsumptionConnectorServiceOptions extends BaseConnectorServiceOptions { workflowReferenceId: string; @@ -103,11 +112,23 @@ export class ConsumptionConnectorService extends BaseConnectorService { connection: connectionData, mcpServerPath: operationPath, }; - } else if (isRealConnectionId) { - // Managed MCP connection — send managed connection reference + } else if (isRealConnectionId && isArmResourceId(connectionId)) { + // Managed MCP connection — build full managed connection reference with identity + const identity = WorkflowService().getAppIdentity?.(); + const userIdentity = + equals(identity?.type, ResourceIdentityType.USER_ASSIGNED) && identity?.userAssignedIdentities + ? Object.keys(identity.userAssignedIdentities)[0] + : undefined; + const connectionProperties = { + authentication: { + type: 'ManagedServiceIdentity', + ...optional('identity', userIdentity), + }, + }; content = { managedConnection: { connection: { id: connectionId }, + connectionProperties, }, mcpServerPath: operationPath, }; @@ -268,8 +289,20 @@ export class ConsumptionConnectorService extends BaseConnectorService { authentication['value'] = connectionProperties['value']; } else if (mappedAuthType === 'ManagedServiceIdentity') { authentication['audience'] = connectionProperties['audience']; - if (connectionProperties['identity']) { - authentication['identity'] = connectionProperties['identity']; + // Identity may be stored in parameterValues (round-tripped from workflow definition) + // or must be derived from the workflow's managed identity configuration. + const storedIdentity = connectionProperties['identity']; + if (storedIdentity) { + authentication['identity'] = storedIdentity; + } else { + const appIdentity = WorkflowService().getAppIdentity?.(); + const userIdentity = + equals(appIdentity?.type, ResourceIdentityType.USER_ASSIGNED) && appIdentity?.userAssignedIdentities + ? Object.keys(appIdentity.userAssignedIdentities)[0] + : undefined; + if (userIdentity) { + authentication['identity'] = userIdentity; + } } } From 3ad7e21419431bc1d7b278886484bc88a1e4e749 Mon Sep 17 00:00:00 2001 From: Riley Evans Date: Tue, 26 May 2026 11:10:58 -0500 Subject: [PATCH 2/3] fix(MCP): Thread selected identity through dynamic values for MCP connections - Update designer-v2 dynamicdata.ts to extract identity from connectionReference - Update designer-v2 queries/connector.ts to accept and pass identity parameter - Update ConnectorService interface to accept optional identity parameter - Update consumption/standard connector implementations to use passed identity for MCP connections - When identity is provided, use it instead of picking first identity from WorkflowService - This ensures user-selected managed identity is correctly threaded through all MCP calls --- .../src/lib/core/queries/connector.ts | 6 ++++-- .../src/lib/core/utils/parameters/dynamicdata.ts | 8 +++++--- .../designer-client-services/lib/connector.ts | 3 ++- .../lib/consumption/connector.ts | 10 +++------- .../lib/standard/connector.ts | 16 +++++----------- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/libs/designer-v2/src/lib/core/queries/connector.ts b/libs/designer-v2/src/lib/core/queries/connector.ts index 5e985464f53..4b7bdc57b14 100644 --- a/libs/designer-v2/src/lib/core/queries/connector.ts +++ b/libs/designer-v2/src/lib/core/queries/connector.ts @@ -106,7 +106,8 @@ export const getListDynamicValues = async ( operationId: string, parameters: Record, dynamicState: any, - operationPath?: string + operationPath?: string, + identity?: string ): Promise => { const queryClient = getReactQueryClient(); const service = ConnectorService(); @@ -119,8 +120,9 @@ export const getListDynamicValues = async ( operationId.toLowerCase(), dynamicState.operationId?.toLowerCase(), getParametersKey({ ...dynamicState.parameters, ...parameters }), + identity ?? '', ], - () => service.getListDynamicValues(connectionId, connectorId, operationId, parameters, dynamicState, undefined, operationPath) + () => service.getListDynamicValues(connectionId, connectorId, operationId, parameters, dynamicState, undefined, operationPath, identity) ); }; diff --git a/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts b/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts index 281482edb0c..a0b080cd45e 100644 --- a/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts +++ b/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts @@ -113,13 +113,16 @@ export async function getDynamicValues( shouldEncodeBasedOnMetadata ); + // Thread the selected identity from connectionReference if present + const selectedIdentity = connectionReference?.connectionProperties?.authentication?.identity; return getListDynamicValues( connectionReference?.connection.id, operationInfo.connectorId, operationInfo.operationId, operationParameters, dynamicState, - operationInfo.operationPath + operationInfo.operationPath, + selectedIdentity ); } if (isLegacyDynamicValuesExtension(definition)) { @@ -739,7 +742,7 @@ function loadUnknownManifestBasedParameters( if (isNullOrEmpty(input) || !isObject(input)) { if (!knownKeys.has(keyPrefix)) { // Add a generic unknown parameter. - // eslint-disable-next-line no-param-reassign + result[keyPrefix] = { key: keyPrefix, name: previousKeyPath, @@ -947,7 +950,6 @@ function evaluateTemplateExpressions( function evaluateParameter(parameter: SerializedParameter, evaluator: ExpressionEvaluator): void { const value = parameter.value; if (isTemplateExpression(value)) { - // eslint-disable-next-line no-param-reassign parameter.value = evaluator.evaluate(value); } if (value !== parameter.value) { diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts index a7dab84d3e5..000233af8e7 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts @@ -68,7 +68,8 @@ export interface IConnectorService { parameters: Record, dynamicState: any, isManagedIdentityConnection?: boolean, - operationPath?: string + operationPath?: string, + identity?: string ): Promise; /** diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts index f07c73335d5..c523c8c4243 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts @@ -63,7 +63,8 @@ export class ConsumptionConnectorService extends BaseConnectorService { parameters: Record, dynamicState: any, isManagedIdentityConnection?: boolean, - operationPath?: string + operationPath?: string, + identity?: string ): Promise { const { apiVersion, httpClient } = this.options; const { operationId: dynamicOperation, apiType } = dynamicState; @@ -114,15 +115,10 @@ export class ConsumptionConnectorService extends BaseConnectorService { }; } else if (isRealConnectionId && isArmResourceId(connectionId)) { // Managed MCP connection — build full managed connection reference with identity - const identity = WorkflowService().getAppIdentity?.(); - const userIdentity = - equals(identity?.type, ResourceIdentityType.USER_ASSIGNED) && identity?.userAssignedIdentities - ? Object.keys(identity.userAssignedIdentities)[0] - : undefined; const connectionProperties = { authentication: { type: 'ManagedServiceIdentity', - ...optional('identity', userIdentity), + ...optional('identity', identity), }, }; content = { diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts index 1397166f222..c7283377596 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts @@ -2,8 +2,6 @@ import type { Connector, OpenAPIV2, OperationManifest } from '../../../utils/src import { isArmResourceId, UnsupportedException, - ResourceIdentityType, - equals, optional, getConnectionParametersWithType, ConnectionParameterTypes, @@ -102,7 +100,8 @@ export class StandardConnectorService extends BaseConnectorService { parameters: Record, dynamicState: any, connectionId: string | undefined, - operationPath?: string + operationPath?: string, + identity?: string ): Promise { const { baseUrl, apiVersion, httpClient, getConfiguration } = this.options; const { operationId: dynamicOperation, apiType } = dynamicState; @@ -145,22 +144,17 @@ export class StandardConnectorService extends BaseConnectorService { // Generate connection reference for managed connections when it's not found. const connectionFromService = await ConnectionService().getConnection(connectionId); if (connectionFromService) { - const identity = WorkflowService().getAppIdentity?.(); - const userIdentity = - equals(identity?.type, ResourceIdentityType.USER_ASSIGNED) && identity?.userAssignedIdentities - ? Object.keys(identity.userAssignedIdentities)[0] - : undefined; const properties = connectionFromService.properties as any; let connectionProperties: any; try { const connector = await ConnectionService().getConnector(properties.api.id); - connectionProperties = getConnectionProperties(connector, userIdentity); + connectionProperties = getConnectionProperties(connector, identity); } catch { connectionProperties = { authentication: { type: 'ManagedServiceIdentity', - ...optional('identity', userIdentity), + ...optional('identity', identity), }, }; } @@ -169,7 +163,7 @@ export class StandardConnectorService extends BaseConnectorService { connection: { id: connectionId }, authentication: { type: 'ManagedServiceIdentity', - ...optional('identity', userIdentity), + ...optional('identity', identity), }, connectionRuntimeUrl: properties.connectionRuntimeUrl ?? '', connectionProperties, From 9fce1a64ab782fa75ef0bdc36193d01d0f666842 Mon Sep 17 00:00:00 2001 From: Riley Evans Date: Tue, 26 May 2026 11:40:29 -0500 Subject: [PATCH 3/3] fix(MCP): Restore WorkflowService identity fallback in standard connector When no identity is explicitly threaded from the connection reference, fall back to WorkflowService().getAppIdentity() to resolve user-assigned identity. This preserves backward compatibility for callers that don't pass identity (e.g., designer-v1). --- .../lib/standard/connector.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts index c7283377596..c00a63384da 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts @@ -2,6 +2,8 @@ import type { Connector, OpenAPIV2, OperationManifest } from '../../../utils/src import { isArmResourceId, UnsupportedException, + ResourceIdentityType, + equals, optional, getConnectionParametersWithType, ConnectionParameterTypes, @@ -144,17 +146,26 @@ export class StandardConnectorService extends BaseConnectorService { // Generate connection reference for managed connections when it's not found. const connectionFromService = await ConnectionService().getConnection(connectionId); if (connectionFromService) { + // Use explicitly passed identity, otherwise fall back to WorkflowService + const resolvedIdentity = + identity ?? + (() => { + const appIdentity = WorkflowService().getAppIdentity?.(); + return equals(appIdentity?.type, ResourceIdentityType.USER_ASSIGNED) && appIdentity?.userAssignedIdentities + ? Object.keys(appIdentity.userAssignedIdentities)[0] + : undefined; + })(); const properties = connectionFromService.properties as any; let connectionProperties: any; try { const connector = await ConnectionService().getConnector(properties.api.id); - connectionProperties = getConnectionProperties(connector, identity); + connectionProperties = getConnectionProperties(connector, resolvedIdentity); } catch { connectionProperties = { authentication: { type: 'ManagedServiceIdentity', - ...optional('identity', identity), + ...optional('identity', resolvedIdentity), }, }; } @@ -163,7 +174,7 @@ export class StandardConnectorService extends BaseConnectorService { connection: { id: connectionId }, authentication: { type: 'ManagedServiceIdentity', - ...optional('identity', identity), + ...optional('identity', resolvedIdentity), }, connectionRuntimeUrl: properties.connectionRuntimeUrl ?? '', connectionProperties,