From 4fceba6c624779431c2f4d1c8fa907e51742aa98 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Fri, 6 Mar 2026 14:12:09 +0530 Subject: [PATCH 01/13] First look at MCP consumption --- .../createConnection/createConnection.tsx | 21 +- .../consumption/__tests__/connection.spec.ts | 301 +++++++++++++++ .../lib/consumption/connection.ts | 175 ++++++++- .../manifests/mcpclientconnector.ts | 345 ++++++++++++++++++ package.json | 2 +- 5 files changed, 837 insertions(+), 7 deletions(-) create mode 100644 libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connection.spec.ts create mode 100644 libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/mcpclientconnector.ts diff --git a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx index 31da5ecc740..c4a2a1b4153 100644 --- a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx +++ b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx @@ -405,12 +405,23 @@ export const CreateConnection = (props: CreateConnectionProps) => { }, [enabledCapabilities, parametersByCapability]); // Don't show name for simple connections - const showNameInput = useMemo( - () => + const showNameInput = useMemo(() => { + const isMcpClientConnection = connectorId?.toLowerCase().includes('mcpclient'); + + if (isMcpClientConnection) { + const connectionService = ConnectionService(); + const isConsumptionSku = connectionService.constructor.name === 'ConsumptionConnectionService'; + + if (isConsumptionSku) { + return false; + } + } + + return ( !(isUsingOAuth && !isMultiAuth) && - (isMultiAuth || Object.keys(capabilityEnabledParameters ?? {}).length > 0 || legacyManagedIdentitySelected), - [isUsingOAuth, isMultiAuth, capabilityEnabledParameters, legacyManagedIdentitySelected] - ); + (isMultiAuth || Object.keys(capabilityEnabledParameters ?? {}).length > 0 || legacyManagedIdentitySelected) + ); + }, [connectorId, isUsingOAuth, isMultiAuth, capabilityEnabledParameters, legacyManagedIdentitySelected]); const validParams = useMemo(() => { if (showNameInput && !connectionDisplayName) { diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connection.spec.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connection.spec.ts new file mode 100644 index 00000000000..b0515529c69 --- /dev/null +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connection.spec.ts @@ -0,0 +1,301 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ConsumptionConnectionService } from '../connection'; +import type { Connector } from '../../../../utils/src'; +import type { ConnectionCreationInfo } from '../../connection'; +import { InitLoggerService } from '../../logger'; + +// Mock the LoggerService +const mockLoggerService = { + log: vi.fn(), + startTrace: vi.fn().mockReturnValue('mock-trace-id'), + endTrace: vi.fn(), + logErrorWithFormatting: vi.fn(), +}; + +describe('ConsumptionConnectionService', () => { + const mockHttpClient = { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + }; + + const mockOptions = { + apiVersion: '2018-07-01-preview', + baseUrl: 'https://management.azure.com', + subscriptionId: 'test-sub', + resourceGroup: 'test-rg', + location: 'eastus', + httpClient: mockHttpClient, + apiHubServiceDetails: { + apiVersion: '2018-07-01-preview', + baseUrl: 'https://management.azure.com', + subscriptionId: 'test-sub', + resourceGroup: 'test-rg', + location: 'eastus', + httpClient: mockHttpClient, + }, + }; + + let service: ConsumptionConnectionService; + + beforeEach(() => { + // Initialize the logger service before each test + InitLoggerService([mockLoggerService]); + + service = new ConsumptionConnectionService(mockOptions as any); + vi.clearAllMocks(); + + // Re-setup logger mocks after clearAllMocks + mockLoggerService.startTrace.mockReturnValue('mock-trace-id'); + }); + + describe('createBuiltInMcpConnection', () => { + it('should create a built-in MCP connection with correct structure', async () => { + const connector: Partial = { + id: 'connectionProviders/mcpclient', + type: 'connectionProviders/mcpclient', + name: 'mcpclient', + properties: { + displayName: 'MCP Client', + iconUri: 'https://example.com/icon.png', + brandColor: '#000000', + capabilities: ['builtin'], + description: 'MCP Client Connector', + generalInformation: { + displayName: 'MCP Client', + iconUrl: 'https://example.com/icon.png', + }, + }, + }; + + const connectionInfo: ConnectionCreationInfo = { + displayName: 'test-mcp-connection', + connectionParameters: { + serverUrl: { value: 'https://mcp-server.example.com' }, + }, + connectionParametersSet: { + name: 'ApiKey', + values: { + key: { value: 'test-api-key' }, + }, + }, + }; + + const result = await service.createConnection('test-connection-id', connector as Connector, connectionInfo); + + expect(result).toBeDefined(); + expect(result.name).toBe('test-mcp-connection'); + expect(result.id).toContain('connectionProviders/mcpclient/connections/'); + expect((result.properties as any).parameterValues.mcpServerUrl).toBe('https://mcp-server.example.com'); + expect((result.properties as any).parameterValues.authenticationType).toBe('ApiKey'); + }); + + it('should throw error when serverUrl is missing', async () => { + const connector: Partial = { + id: 'connectionProviders/mcpclient', + type: 'connectionProviders/mcpclient', + name: 'mcpclient', + properties: { + displayName: 'MCP Client', + capabilities: ['builtin'], + generalInformation: { + displayName: 'MCP Client', + }, + iconUri: '', + }, + }; + + const connectionInfo: ConnectionCreationInfo = { + displayName: 'test-mcp-connection', + connectionParameters: {}, + }; + + await expect(service.createConnection('test-connection-id', connector as Connector, connectionInfo)).rejects.toThrow( + 'Server URL is required for MCP connection' + ); + }); + + it('should use connectionId as fallback name when displayName is not provided', async () => { + const connector: Partial = { + id: 'connectionProviders/mcpclient', + type: 'connectionProviders/mcpclient', + name: 'mcpclient', + properties: { + displayName: 'MCP Client', + capabilities: ['builtin'], + generalInformation: { + displayName: 'MCP Client', + }, + iconUri: '', + }, + }; + + const connectionInfo: ConnectionCreationInfo = { + connectionParameters: { + serverUrl: { value: 'https://mcp-server.example.com' }, + }, + }; + + const result = await service.createConnection( + '/subscriptions/sub/connections/my-connection-name', + connector as Connector, + connectionInfo + ); + + expect(result.name).toBe('my-connection-name'); + }); + + it('should handle None authentication type', async () => { + const connector: Partial = { + id: 'connectionProviders/mcpclient', + type: 'connectionProviders/mcpclient', + name: 'mcpclient', + properties: { + displayName: 'MCP Client', + capabilities: ['builtin'], + generalInformation: { + displayName: 'MCP Client', + }, + iconUri: '', + }, + }; + + const connectionInfo: ConnectionCreationInfo = { + displayName: 'test-mcp-connection', + connectionParameters: { + serverUrl: { value: 'https://mcp-server.example.com' }, + }, + connectionParametersSet: { + name: 'None', + values: {}, + }, + }; + + const result = await service.createConnection('test-connection-id', connector as Connector, connectionInfo); + + expect((result.properties as any).parameterValues.authenticationType).toBe('None'); + }); + }); + + describe('extractParameterValue', () => { + it('should extract value from wrapped object', () => { + const result = (service as any).extractParameterValue({ value: 'test' }); + expect(result).toBe('test'); + }); + + it('should return direct value if not wrapped', () => { + const result = (service as any).extractParameterValue('direct-value'); + expect(result).toBe('direct-value'); + }); + + it('should handle null value', () => { + const result = (service as any).extractParameterValue(null); + expect(result).toBe(null); + }); + + it('should handle undefined value', () => { + const result = (service as any).extractParameterValue(undefined); + expect(result).toBe(undefined); + }); + + it('should handle object without value property', () => { + const result = (service as any).extractParameterValue({ other: 'prop' }); + expect(result).toEqual({ other: 'prop' }); + }); + }); + + describe('extractAuthParameters', () => { + it('should extract authentication parameters correctly', () => { + const result = (service as any).extractAuthParameters({ + name: 'ApiKey', + values: { + key: { value: 'my-api-key' }, + keyHeaderName: { value: 'X-API-Key' }, + }, + }); + + expect(result.authenticationType).toBe('ApiKey'); + expect(result.authParams.key).toBe('my-api-key'); + expect(result.authParams.keyHeaderName).toBe('X-API-Key'); + }); + + it('should return None for undefined connectionParametersSet', () => { + const result = (service as any).extractAuthParameters(undefined); + + expect(result.authenticationType).toBe('None'); + expect(result.authParams).toEqual({}); + }); + + it('should return None for null connectionParametersSet', () => { + const result = (service as any).extractAuthParameters(null); + + expect(result.authenticationType).toBe('None'); + expect(result.authParams).toEqual({}); + }); + + it('should handle BasicAuth parameters', () => { + const result = (service as any).extractAuthParameters({ + name: 'BasicAuth', + values: { + username: { value: 'testuser' }, + password: { value: 'testpass' }, + }, + }); + + expect(result.authenticationType).toBe('BasicAuth'); + expect(result.authParams.username).toBe('testuser'); + expect(result.authParams.password).toBe('testpass'); + }); + + it('should handle OAuth2 parameters', () => { + const result = (service as any).extractAuthParameters({ + name: 'OAuth2', + values: { + clientId: { value: 'client-123' }, + secret: { value: 'secret-456' }, + tenant: { value: 'tenant-789' }, + authority: { value: 'https://login.microsoftonline.com' }, + audience: { value: 'api://my-app' }, + }, + }); + + expect(result.authenticationType).toBe('OAuth2'); + expect(result.authParams.clientId).toBe('client-123'); + expect(result.authParams.secret).toBe('secret-456'); + expect(result.authParams.tenant).toBe('tenant-789'); + expect(result.authParams.authority).toBe('https://login.microsoftonline.com'); + expect(result.authParams.audience).toBe('api://my-app'); + }); + + it('should only extract known auth keys', () => { + const result = (service as any).extractAuthParameters({ + name: 'Custom', + values: { + key: { value: 'valid-key' }, + unknownParam: { value: 'should-be-ignored' }, + anotherUnknown: { value: 'also-ignored' }, + }, + }); + + expect(result.authParams.key).toBe('valid-key'); + expect(result.authParams.unknownParam).toBeUndefined(); + expect(result.authParams.anotherUnknown).toBeUndefined(); + }); + }); + + describe('getConnector', () => { + it('should return mcpclient connector for mcpclient connectorId', async () => { + const result = await service.getConnector('connectionProviders/mcpclient'); + + expect(result).toBeDefined(); + expect(result.id).toContain('mcpclient'); + }); + + it('should return agent connector for agent connectorId', async () => { + const result = await service.getConnector('connectionProviders/agent'); + + expect(result).toBeDefined(); + }); + }); +}); diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts index b89ac826eb8..1e49fb3a41f 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts @@ -1,5 +1,5 @@ import type { QueryClient } from '@tanstack/react-query'; -import type { Connector, Connection } from '../../../utils/src'; +import type { Connector, Connection, ConnectionStatus } from '../../../utils/src'; import type { BaseConnectionServiceOptions } from '../base'; import { BaseConnectionService } from '../base'; import type { ConnectionCreationInfo, ConnectionParametersMetadata, CreateConnectionResult } from '../connection'; @@ -8,6 +8,7 @@ import { LogEntryLevel, Status } from '../logging/logEntry'; import type { IOAuthPopup } from '../oAuth'; import { OAuthService } from '../oAuth'; import agentLoopConnector from './manifests/agentLoopConnector'; +import mcpclientconnector from './manifests/mcpclientconnector'; export interface ConsumptionConnectionServiceOptions extends BaseConnectionServiceOptions { getCachedConnector?: (connectorId: string) => Promise; @@ -19,6 +20,37 @@ export class ConsumptionConnectionService extends BaseConnectionService { this._vVersion = 'V1'; } + private extractParameterValue(val: unknown): unknown { + if (typeof val === 'object' && val !== null && 'value' in val) { + return (val as { value: unknown }).value; + } + return val; + } + + private extractAuthParameters(connectionParametersSet: ConnectionCreationInfo['connectionParametersSet']): { + authenticationType: string; + authParams: Record; + } { + let authenticationType = 'None'; + const authParams: Record = {}; + + if (connectionParametersSet?.name && connectionParametersSet.name !== 'None') { + authenticationType = connectionParametersSet.name; + } + + if (connectionParametersSet?.values) { + const values = connectionParametersSet.values; + const authKeys = ['username', 'password', 'key', 'keyHeaderName', 'clientId', 'secret', 'tenant', 'authority', 'audience', 'pfx']; + for (const key of authKeys) { + if (values[key] !== undefined) { + authParams[key] = this.extractParameterValue(values[key]); + } + } + } + + return { authenticationType, authParams }; + } + async getConnector(connectorId: string, getCached = false): Promise { let connector: Connector | undefined; if (getCached && this._options.getCachedConnector) { @@ -28,6 +60,9 @@ export class ConsumptionConnectionService extends BaseConnectionService { if (connectorIdKeyword === 'agent') { return agentLoopConnector; } + if (connectorIdKeyword === 'mcpclient') { + return mcpclientconnector; + } return connector ?? this._getAzureConnector(connectorId); } @@ -49,6 +84,22 @@ export class ConsumptionConnectionService extends BaseConnectionService { _parametersMetadata?: ConnectionParametersMetadata, shouldTestConnection = true ): Promise { + const isBuiltInMcpConnection = + (connector.id?.toLowerCase().includes('mcpclient') || connector.type?.toLowerCase() === 'mcpclient') && + connector.properties?.capabilities?.includes('builtin'); + + if (isBuiltInMcpConnection) { + return this.createBuiltInMcpConnection(connectionId, connector, connectionInfo); + } + + const isManagedMcpConnection = + (connector.id?.toLowerCase().includes('mcpclient') || connector.type?.toLowerCase() === 'mcpclient') && + !connector.properties?.capabilities?.includes('builtin'); + + if (isManagedMcpConnection) { + return this.createManagedMcpConnection(connectionId, connector, connectionInfo); + } + const connectionName = connectionId.split('/').at(-1) as string; const logId = LoggerService().startTrace({ action: 'createConnection', @@ -83,6 +134,128 @@ export class ConsumptionConnectionService extends BaseConnectionService { } } + private createBuiltInMcpConnection(connectionId: string, connector: Connector, connectionInfo: ConnectionCreationInfo): Connection { + const logId = LoggerService().startTrace({ + action: 'createBuiltInMcpConnection', + name: 'Creating Built-in MCP Connection', + source: 'connection.ts', + }); + + try { + const connectionName = connectionInfo.displayName || connectionId.split('/').at(-1) || `mcp-${Date.now()}`; + + const connectionParameters = connectionInfo.connectionParameters ?? {}; + + let serverUrl = ''; + if (connectionParameters['serverUrl']) { + serverUrl = this.extractParameterValue(connectionParameters['serverUrl']) as string; + } + + if (!serverUrl) { + throw new Error('Server URL is required for MCP connection'); + } + + const { authenticationType, authParams } = this.extractAuthParameters(connectionInfo.connectionParametersSet); + + const connection = { + id: `connectionProviders/mcpclient/connections/${connectionName}`, + name: connectionName, + type: 'connections', + location: '', + properties: { + displayName: connectionName, + overallStatus: 'Connected', + statuses: [{ status: 'Connected' }] as ConnectionStatus[], + api: { + id: connector.id, + name: 'mcpclient', + displayName: connector.properties?.displayName || 'MCP Client', + iconUri: connector.properties?.iconUri ?? '', + brandColor: connector.properties?.brandColor ?? '#000000', + description: connector.properties?.description ?? '', + category: 'MCP', + type: 'mcpclient', + }, + createdTime: new Date().toISOString(), + parameterValues: { + mcpServerUrl: serverUrl, + authenticationType, + ...authParams, + }, + }, + }; + + LoggerService().endTrace(logId, { status: Status.Success }); + return connection as unknown as Connection; + } catch (error) { + const errorMessage = `Failed to create built-in MCP connection: ${this.tryParseErrorMessage(error)}`; + LoggerService().log({ + level: LogEntryLevel.Error, + area: 'createBuiltInMcpConnection', + message: errorMessage, + error: error instanceof Error ? error : undefined, + traceId: logId, + }); + LoggerService().endTrace(logId, { status: Status.Failure }); + throw new Error(errorMessage); + } + } + + private async createManagedMcpConnection( + connectionId: string, + connector: Connector, + connectionInfo: ConnectionCreationInfo + ): Promise { + const connectionName = connectionInfo.displayName || connectionId.split('/').at(-1) || `mcp-${Date.now()}`; + + const parameterValues: Record = {}; + const connectionParameters = connectionInfo.connectionParameters ?? {}; + + if (connectionParameters['serverUrl']) { + parameterValues['serverUrl'] = this.extractParameterValue(connectionParameters['serverUrl']); + } + + const { authenticationType, authParams } = this.extractAuthParameters(connectionInfo.connectionParametersSet); + if (authenticationType !== 'None') { + parameterValues['authentication'] = { + type: authenticationType, + ...authParams, + }; + } + + const mcpConnectionInfo: ConnectionCreationInfo = { + displayName: connectionName, + connectionParameters: parameterValues, + }; + + const logId = LoggerService().startTrace({ + action: 'createManagedMcpConnection', + name: 'Creating Managed MCP Connection', + source: 'connection.ts', + }); + + try { + const connection = await this.createConnectionInApiHub(connectionName, connector.id, mcpConnectionInfo); + + LoggerService().endTrace(logId, { + status: Status.Success, + data: { connectorId: connector.id }, + }); + return connection; + } catch (error) { + const errorMessage = `Failed to create managed MCP connection: ${this.tryParseErrorMessage(error)}`; + LoggerService().log({ + level: LogEntryLevel.Error, + area: 'createManagedMcpConnection', + message: errorMessage, + error: error instanceof Error ? error : undefined, + traceId: logId, + }); + LoggerService().endTrace(logId, { status: Status.Failure }); + return Promise.reject(errorMessage); + } + } + async createAndAuthorizeOAuthConnection( connectionId: string, connectorId: string, diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/mcpclientconnector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/mcpclientconnector.ts new file mode 100644 index 00000000000..ef7f83f241d --- /dev/null +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/mcpclientconnector.ts @@ -0,0 +1,345 @@ +import type { Connector } from '../../../../utils/src'; + +export default { + type: 'McpClient', + name: 'mcpclient', + id: 'connectionProviders/mcpclient', + properties: { + displayName: 'MCP Client', + iconUri: + 'data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Crect%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22%23242424%22%2F%3E%0A%3Cg%20clip-path%3D%22url(%23clip0_1258_56822)%22%3E%0A%3Crect%20width%3D%2220%22%20height%3D%2220%22%20transform%3D%22translate(2%202)%22%20fill%3D%22%23242424%22%2F%3E%0A%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M15.0733%203.9524C14.6707%203.56054%2014.131%203.34127%2013.5691%203.34127C13.0073%203.34127%2012.4676%203.56054%2012.065%203.9524L4.04331%2011.8191C3.90907%2011.9495%203.72926%2012.0225%203.54206%2012.0225C3.35486%2012.0225%203.17504%2011.9495%203.04081%2011.8191C2.97509%2011.7552%202.92285%2011.6787%202.88718%2011.5943C2.85151%2011.5098%202.83313%2011.4191%202.83313%2011.3274C2.83313%2011.2357%202.85151%2011.145%202.88718%2011.0605C2.92285%2010.9761%202.97509%2010.8996%203.04081%2010.8357L11.0625%202.96907C11.7335%202.31606%2012.6328%201.95068%2013.5691%201.95068C14.5055%201.95068%2015.4048%202.31606%2016.0758%202.96907C16.4641%203.34665%2016.7574%203.81081%2016.9318%204.32356C17.1062%204.8363%2017.1567%205.38306%2017.0791%205.91907C17.6223%205.84182%2018.1759%205.89031%2018.6973%206.06079C19.2187%206.23128%2019.6941%206.51921%2020.0866%206.9024L20.1283%206.94407C20.4569%207.26363%2020.7181%207.64584%2020.8965%208.06808C21.0748%208.49032%2021.1667%208.94404%2021.1667%209.4024C21.1667%209.86077%2021.0748%2010.3145%2020.8965%2010.7367C20.7181%2011.159%2020.4569%2011.5412%2020.1283%2011.8607L12.8733%2018.9749C12.8514%2018.9962%2012.834%2019.0216%2012.8221%2019.0498C12.8102%2019.0779%2012.8041%2019.1081%2012.8041%2019.1387C12.8041%2019.1692%2012.8102%2019.1994%2012.8221%2019.2275C12.834%2019.2557%2012.8514%2019.2811%2012.8733%2019.3024L14.3633%2020.7641C14.429%2020.828%2014.4813%2020.9044%2014.5169%2020.9889C14.5526%2021.0733%2014.571%2021.1641%2014.571%2021.2557C14.571%2021.3474%2014.5526%2021.4381%2014.5169%2021.5226C14.4813%2021.607%2014.429%2021.6835%2014.3633%2021.7474C14.2291%2021.8779%2014.0493%2021.9509%2013.8621%2021.9509C13.6749%2021.9509%2013.495%2021.8779%2013.3608%2021.7474L11.8708%2020.2866C11.7173%2020.1374%2011.5953%2019.9591%2011.512%2019.762C11.4287%2019.5649%2011.3858%2019.353%2011.3858%2019.1391C11.3858%2018.9251%2011.4287%2018.7133%2011.512%2018.5162C11.5953%2018.3191%2011.7173%2018.1407%2011.8708%2017.9916L19.1258%2010.8766C19.3229%2010.6848%2019.4795%2010.4554%2019.5864%2010.2021C19.6934%209.94875%2019.7485%209.67655%2019.7485%209.40157C19.7485%209.12658%2019.6934%208.85438%2019.5864%208.60104C19.4795%208.34771%2019.3229%208.11837%2019.1258%207.92657L19.0841%207.88573C18.6819%207.49428%2018.143%207.27504%2017.5817%207.27457C17.0205%207.27411%2016.4812%207.49245%2016.0783%207.88323L10.1016%2013.7449L10.1%2013.7466L10.0183%2013.8274C9.88404%2013.9581%209.70404%2014.0313%209.51664%2014.0313C9.32925%2014.0313%209.14925%2013.9581%209.01498%2013.8274C8.94926%2013.7635%208.89702%2013.687%208.86135%2013.6026C8.82568%2013.5182%208.8073%2013.4274%208.8073%2013.3357C8.8073%2013.2441%208.82568%2013.1533%208.86135%2013.0689C8.89702%2012.9844%208.94926%2012.908%209.01498%2012.8441L15.0758%206.8999C15.2723%206.70797%2015.4284%206.47866%2015.5349%206.22546C15.6414%205.97226%2015.6962%205.70031%2015.696%205.42562C15.6957%205.15094%2015.6405%204.87908%2015.5336%204.62606C15.4266%204.37305%2015.2701%204.14399%2015.0733%203.9524Z%22%20fill%3D%22white%22%2F%3E%0A%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M14.0708%205.91914C14.1365%205.85522%2014.1887%205.77878%2014.2244%205.69433C14.2601%205.60989%2014.2785%205.51914%2014.2785%205.42747C14.2785%205.3358%2014.2601%205.24505%2014.2244%205.1606C14.1887%205.07616%2014.1365%204.99972%2014.0708%204.9358C13.9365%204.80508%2013.7565%204.73193%2013.5691%204.73193C13.3817%204.73193%2013.2017%204.80508%2013.0675%204.9358L7.13495%2010.7541C6.80636%2011.0737%206.54516%2011.4559%206.36681%2011.8781C6.18845%2012.3004%206.09656%2012.7541%206.09656%2013.2125C6.09656%2013.6708%206.18845%2014.1245%206.36681%2014.5468C6.54516%2014.969%206.80636%2015.3512%207.13495%2015.6708C7.80607%2016.3236%208.70538%2016.6889%209.64162%2016.6889C10.5779%2016.6889%2011.4772%2016.3236%2012.1483%2015.6708L18.0816%209.85247C18.1473%209.78856%2018.1996%209.71212%2018.2352%209.62767C18.2709%209.54322%2018.2893%209.45248%2018.2893%209.3608C18.2893%209.26913%2018.2709%209.17839%2018.2352%209.09394C18.1996%209.00949%2018.1473%208.93305%2018.0816%208.86914C17.9473%208.73841%2017.7674%208.66527%2017.58%208.66527C17.3926%208.66527%2017.2126%208.73841%2017.0783%208.86914L11.1458%2014.6875C10.7431%2015.0793%2010.2035%2015.2986%209.64162%2015.2986C9.07977%2015.2986%208.5401%2015.0793%208.13745%2014.6875C7.9404%2014.4957%207.78377%2014.2663%207.67683%2014.013C7.56988%2013.7597%207.51478%2013.4875%207.51478%2013.2125C7.51478%2012.9375%207.56988%2012.6653%207.67683%2012.4119C7.78377%2012.1586%207.9404%2011.9293%208.13745%2011.7375L14.0708%205.91914Z%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fg%3E%0A%3Cdefs%3E%0A%3CclipPath%20id%3D%22clip0_1258_56822%22%3E%0A%3Crect%20width%3D%2220%22%20height%3D%2220%22%20fill%3D%22white%22%20transform%3D%22translate(2%202)%22%2F%3E%0A%3C%2FclipPath%3E%0A%3C%2Fdefs%3E%0A%3C%2Fsvg%3E%0A', + brandColor: '#000000', + description: 'Easily integrate cutting-edge artificial intelligence capabilities into your workflows', + capabilities: ['actions', 'builtin'], + connectionMode: 'mcp', + connectionParameterSets: { + uiDefinition: { + displayName: 'Authentication type', + description: 'The authentication type to use.', + }, + values: [ + { + name: 'None', + parameters: { + serverUrl: { + type: 'string', + uiDefinition: { + displayName: 'Server URL', + constraints: { + required: 'true', + }, + description: 'Server URL', + }, + }, + }, + uiDefinition: { + displayName: 'None', + description: 'None', + }, + }, + { + name: 'Basic', + parameters: { + serverUrl: { + type: 'string', + uiDefinition: { + displayName: 'Server URL', + constraints: { + required: 'true', + }, + description: 'Server URL', + }, + }, + username: { + type: 'string', + uiDefinition: { + displayName: 'Username', + constraints: { + propertyPath: ['authentication'], + required: 'true', + }, + description: 'Username', + }, + }, + password: { + type: 'securestring', + uiDefinition: { + displayName: 'Password', + constraints: { + propertyPath: ['authentication'], + required: 'true', + }, + description: 'Password', + }, + }, + }, + uiDefinition: { + displayName: 'Basic', + }, + }, + { + name: 'ClientCertificate', + parameters: { + serverUrl: { + type: 'string', + uiDefinition: { + displayName: 'Server URL', + constraints: { + required: 'true', + }, + description: 'Server URL', + }, + }, + pfx: { + type: 'securestring', + uiDefinition: { + displayName: 'Pfx', + constraints: { + required: 'true', + propertyPath: ['authentication'], + }, + description: 'Client Certificate pfx', + }, + }, + password: { + type: 'securestring', + uiDefinition: { + displayName: 'Password', + constraints: { + propertyPath: ['authentication'], + }, + description: 'Client Certificate password', + }, + }, + }, + uiDefinition: { + displayName: 'Client certificate', + }, + }, + { + name: 'ActiveDirectoryOAuth', + parameters: { + serverUrl: { + type: 'string', + uiDefinition: { + displayName: 'Server URL', + constraints: { + required: 'true', + }, + description: 'Server URL', + }, + }, + authority: { + type: 'string', + uiDefinition: { + displayName: 'Authority', + constraints: { + propertyPath: ['authentication'], + }, + description: 'Active Directory authority', + }, + }, + tenant: { + type: 'string', + uiDefinition: { + displayName: 'Tenant', + constraints: { + required: 'true', + propertyPath: ['authentication'], + }, + description: 'Active Directory tenant', + }, + }, + audience: { + type: 'string', + parameterSource: 'AppConfiguration', + uiDefinition: { + displayName: 'Audience', + constraints: { + required: 'true', + propertyPath: ['authentication'], + }, + description: 'Active Directory audience', + }, + }, + credentialType: { + type: 'string', + uiDefinition: { + displayName: 'Credential type', + constraints: { + propertyPath: ['authentication'], + allowedValues: [ + { + text: 'Certificate', + value: 'Certificate', + }, + { + text: 'Secret', + value: 'Secret', + }, + ], + }, + description: 'Active Directory credential type', + }, + }, + clientId: { + type: 'string', + uiDefinition: { + displayName: 'Client ID', + constraints: { + required: 'true', + propertyPath: ['authentication'], + }, + description: 'Active Directory client ID', + }, + }, + secret: { + type: 'securestring', + uiDefinition: { + displayName: 'Client secret', + constraints: { + required: 'true', + propertyPath: ['authentication'], + dependentParameter: { + parameter: 'CredentialType', + value: 'Secret', + }, + }, + description: 'Active Directory client secret', + }, + }, + pfx: { + type: 'securestring', + uiDefinition: { + displayName: 'Pfx', + constraints: { + required: 'true', + propertyPath: ['authentication'], + dependentParameter: { + parameter: 'CredentialType', + value: 'Certificate', + }, + }, + description: 'Active Directory pfx', + }, + }, + password: { + type: 'securestring', + uiDefinition: { + displayName: 'Password', + constraints: { + required: 'true', + propertyPath: ['authentication'], + dependentParameter: { + parameter: 'CredentialType', + value: 'Certificate', + }, + }, + description: 'Active Directory password', + }, + }, + }, + uiDefinition: { + displayName: 'Active Directory OAuth', + }, + }, + { + name: 'Raw', + parameters: { + serverUrl: { + type: 'string', + uiDefinition: { + displayName: 'Server URL', + constraints: { + required: 'true', + }, + description: 'Server URL', + }, + }, + value: { + type: 'string', + uiDefinition: { + displayName: 'Value', + constraints: { + propertyPath: ['authentication'], + required: 'true', + }, + }, + }, + }, + uiDefinition: { + displayName: 'Raw', + }, + }, + { + name: 'Key', + parameters: { + serverUrl: { + type: 'string', + uiDefinition: { + displayName: 'Server URL', + constraints: { + required: 'true', + }, + description: 'Server URL', + }, + }, + key: { + type: 'securestring', + uiDefinition: { + displayName: 'Key', + constraints: { + required: 'true', + propertyPath: ['authentication'], + }, + description: 'Key', + }, + }, + keyHeaderName: { + type: 'string', + uiDefinition: { + displayName: 'Key Header Name', + constraints: { + propertyPath: ['authentication'], + }, + description: 'Key header name', + }, + }, + }, + uiDefinition: { + displayName: 'Key', + }, + }, + { + name: 'ManagedServiceIdentity', + parameters: { + serverUrl: { + type: 'string', + uiDefinition: { + displayName: 'Server URL', + constraints: { + required: 'true', + }, + description: 'Server URL', + }, + }, + audience: { + type: 'string', + uiDefinition: { + displayName: 'Audience', + constraints: { + required: 'true', + propertyPath: ['authentication'], + }, + description: 'The audience', + }, + }, + }, + uiDefinition: { + displayName: 'Managed identity', + }, + }, + ], + }, + }, +} as Connector; diff --git a/package.json b/package.json index 57b3968d040..b04847d0b2e 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "lint-staged": { "*.{js,ts,tsx}": [ "npm run extract", - "eslint --cache --fix", + "eslint --cache --fix --no-warn-ignored", "biome check --write" ] }, From 30b24ff85c21d9f971ebe3884ea45ef109af9442 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Mon, 9 Mar 2026 13:52:42 +0530 Subject: [PATCH 02/13] added builtin mcp client manifest --- .../consumption/manifests/builtinmcpclient.ts | 69 +++++++++++++++++++ .../lib/consumption/operationmanifest.ts | 45 +++++++++--- 2 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/builtinmcpclient.ts diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/builtinmcpclient.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/builtinmcpclient.ts new file mode 100644 index 00000000000..49e3ea89644 --- /dev/null +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/manifests/builtinmcpclient.ts @@ -0,0 +1,69 @@ +import type { OperationManifest } from '../../../../utils/src'; +import mcpclientconnector from './mcpclientconnector'; + +export default { + properties: { + iconUri: mcpclientconnector.properties.iconUri, + brandColor: '#000000', + description: 'Uses an MCP server', + inputsBindingMode: 'untyped', + inputs: { + type: 'object', + properties: { + headers: { + type: 'object', + title: 'Headers', + description: 'Enter JSON object of request headers', + 'x-ms-editor': 'dictionary', + 'x-ms-editor-options': { + valueType: 'string', + }, + }, + allowedTools: { + type: 'array', + items: { + type: 'string', + }, + title: 'Allowed tools', + 'x-ms-editor': 'combobox', + 'x-ms-editor-options': { + multiSelect: true, + titleSeparator: ',', + serialization: { + valueType: 'array', + }, + }, + 'x-ms-dynamic-list': { + dynamicState: { + apiType: 'mcp', + operationId: 'listMcpTools', + }, + }, + }, + }, + }, + outputsBindingMode: 'untyped', + outputs: { + type: 'object', + properties: {}, + }, + isOutputsOptional: false, + inputsLocation: ['inputs', 'parameters'], + isInputsOptional: false, + + runAfter: { + type: 'notsupported', + }, + + connection: { + required: true, + type: 'mcp', + disableAutoSelection: true, + }, + connectionReference: { + referenceKeyFormat: 'mcpconnection', + }, + + connector: mcpclientconnector, + }, +} as OperationManifest; diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/operationmanifest.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/operationmanifest.ts index 5a75360ddb1..178f186b001 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/operationmanifest.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/operationmanifest.ts @@ -7,6 +7,7 @@ import { agentType, getBuiltInOperationInfo, isBuiltInOperation, + mcpclientConnectorId, supportedBaseManifestObjects, supportedBaseManifestTypes, } from '../base/operationmanifest'; @@ -39,6 +40,8 @@ import { import { getBuiltInConnectorsInConsumption } from './search'; import agentloop from './manifests/agentloop'; import a2arequestManifest from './manifests/a2arequest'; +import builtinMcpClientManifest from './manifests/builtinmcpclient'; +import mcpclientconnector from './manifests/mcpclientconnector'; interface ConsumptionOperationManifestServiceOptions extends BaseOperationManifestServiceOptions { subscriptionId: string; @@ -62,6 +65,19 @@ export class ConsumptionOperationManifestService extends BaseOperationManifestSe result[connector.id.toLowerCase()] = connector; return result; }, {}); + + this.allBuiltInConnectors[mcpclientconnector.id.toLowerCase()] = mcpclientconnector; + } + + override isSupported(operationType?: string, operationKind?: string): boolean { + if (operationType?.toLowerCase() === 'mcpclienttool' && operationKind?.toLowerCase() === 'builtin') { + return true; + } + const { supportedTypes } = this.options; + const normalizedOperationType = operationType?.toLowerCase() ?? ''; + return supportedTypes + ? supportedTypes.indexOf(normalizedOperationType) > -1 + : supportedConsumptionManifestTypes.indexOf(normalizedOperationType) > -1; } override async getOperationInfo(definition: any, isTrigger: boolean): Promise { @@ -69,6 +85,12 @@ export class ConsumptionOperationManifestService extends BaseOperationManifestSe const normalizedOperationType = definition.type?.toLowerCase(); switch (normalizedOperationType) { + case 'mcpclienttool': + return { + connectorId: mcpclientConnectorId, + operationId: 'nativemcpclient', + }; + case 'workflow': return { connectorId: invokeWorkflowGroup.id, @@ -102,15 +124,11 @@ export class ConsumptionOperationManifestService extends BaseOperationManifestSe throw new UnsupportedException(`Operation type: ${definition.type} does not support manifest.`); } - override isSupported(operationType?: string, _operationKind?: string): boolean { - const { supportedTypes } = this.options; - const normalizedOperationType = operationType?.toLowerCase() ?? ''; - return supportedTypes - ? supportedTypes.indexOf(normalizedOperationType) > -1 - : supportedConsumptionManifestTypes.indexOf(normalizedOperationType) > -1; - } - override async getOperationManifest(connectorId: string, operationId: string): Promise { + if (connectorId?.toLowerCase() === mcpclientConnectorId.toLowerCase() && operationId === 'nativemcpclient') { + return builtinMcpClientManifest; + } + const supportedManifest = supportedConsumptionManifestObjects.get(operationId); if (supportedManifest) { @@ -160,6 +178,17 @@ export class ConsumptionOperationManifestService extends BaseOperationManifestSe } override async getOperation(_connectorId: string, operationId: string, _useCachedData = false): Promise { + if (_connectorId?.toLowerCase() === mcpclientConnectorId.toLowerCase() && operationId === 'nativemcpclient') { + return { + properties: { + connector: { properties: { displayName: builtinMcpClientManifest.properties.connector?.properties.displayName } }, + brandColor: builtinMcpClientManifest.properties.brandColor, + description: builtinMcpClientManifest.properties.description, + iconUri: builtinMcpClientManifest.properties.iconUri, + }, + }; + } + const supportedManifest = supportedConsumptionManifestObjects.get(operationId); if (supportedManifest) { From 7526ba33b412a68484e1075c8a7ec245ea552284 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Mon, 9 Mar 2026 14:25:41 +0530 Subject: [PATCH 03/13] createconnection in v1 designer --- .../createConnection/createConnection.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx index a5f26e4dab7..4cb1bb31644 100644 --- a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx +++ b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx @@ -407,12 +407,23 @@ export const CreateConnection = (props: CreateConnectionProps) => { }, [enabledCapabilities, parametersByCapability]); // Don't show name for simple connections - const showNameInput = useMemo( - () => + const showNameInput = useMemo(() => { + const isMcpClientConnection = connectorId?.toLowerCase().includes('mcpclient'); + + if (isMcpClientConnection) { + const connectionService = ConnectionService(); + const isConsumptionSku = connectionService.constructor.name === 'ConsumptionConnectionService'; + + if (isConsumptionSku) { + return false; + } + } + + return ( !(isUsingOAuth && !isMultiAuth) && - (isMultiAuth || Object.keys(capabilityEnabledParameters ?? {}).length > 0 || legacyManagedIdentitySelected), - [isUsingOAuth, isMultiAuth, capabilityEnabledParameters, legacyManagedIdentitySelected] - ); + (isMultiAuth || Object.keys(capabilityEnabledParameters ?? {}).length > 0 || legacyManagedIdentitySelected) + ); + }, [connectorId, isUsingOAuth, isMultiAuth, capabilityEnabledParameters, legacyManagedIdentitySelected]); const validParams = useMemo(() => { if (showNameInput && !connectionDisplayName) { From ecb5ca8caedd310045ae01577b351abb19daba70 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Wed, 11 Mar 2026 12:53:18 +0530 Subject: [PATCH 04/13] calling list tool api in custom and managed connector --- .../laDesignerConsumption.tsx | 2 +- .../lib/consumption/connection.ts | 4 +- .../lib/consumption/connector.ts | 86 ++++++++++++++++++- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesignerConsumption.tsx b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesignerConsumption.tsx index c318a5fbdbf..7b1ee9d6911 100644 --- a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesignerConsumption.tsx +++ b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesignerConsumption.tsx @@ -63,7 +63,6 @@ const httpClient = new HttpClient(); const DesignerEditorConsumption = () => { const dispatch = useDispatch(); const { id: workflowId } = useSelector((state: RootState) => ({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion id: state.workflowLoader.resourcePath!, })); @@ -310,6 +309,7 @@ const DesignerEditorConsumption = () => { ...getSKUDefaultHostOptions(Constants.SKU.CONSUMPTION), }, showPerformanceDebug, + mcpClientToolEnabled: true, }} > {definition ? ( diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts index 1e49fb3a41f..1ba79a7729b 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts @@ -186,7 +186,9 @@ export class ConsumptionConnectionService extends BaseConnectionService { }; LoggerService().endTrace(logId, { status: Status.Success }); - return connection as unknown as Connection; + const result = connection as unknown as Connection; + this._connections[result.id] = result; + return result; } catch (error) { const errorMessage = `Failed to create built-in MCP connection: ${this.tryParseErrorMessage(error)}`; LoggerService().log({ 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 bf58bf04382..ced6a4d54fc 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 @@ -2,6 +2,7 @@ import type { OpenAPIV2 } from '../../../utils/src'; import { ArgumentException, UnsupportedException, optional, equals, getResourceName } from '../../../utils/src'; import type { BaseConnectorServiceOptions } from '../base'; import { BaseConnectorService } from '../base'; +import { ConnectionService } from '../connection'; import type { ListDynamicValue, ManagedIdentityRequestProperties, TreeDynamicExtension, TreeDynamicValue } from '../connector'; import { pathCombine, unwrapPaginatedResponse } from '../helpers'; import { LoggerService } from '../logger'; @@ -52,10 +53,12 @@ export class ConsumptionConnectorService extends BaseConnectorService { operationId: string, parameters: Record, dynamicState: any, - isManagedIdentityConnection?: boolean + isManagedIdentityConnection?: boolean, + operationPath?: string ): Promise { - const { apiVersion, httpClient } = this.options; - const { operationId: dynamicOperation } = dynamicState; + const { apiVersion, httpClient, baseUrl } = this.options; + const { operationId: dynamicOperation, apiType } = dynamicState; + const isMcpConnection = apiType === 'mcp' && dynamicOperation === 'listMcpTools'; const invokeParameters = this._getInvokeParameters(parameters, dynamicState); @@ -69,6 +72,55 @@ export class ConsumptionConnectorService extends BaseConnectorService { }); } + if (isMcpConnection) { + const { workflowReferenceId } = this.options; + const uri = `${baseUrl}${workflowReferenceId}/listMcpTools`; + + let content: any; + if (connectionId) { + const connection = await ConnectionService().getConnection(connectionId); + const connectionProperties = (connection?.properties as any)?.parameterValues ?? {}; + const isBuiltInConnection = connectionId?.includes('connectionProviders/mcpclient'); + + if (isBuiltInConnection) { + // Native/built-in MCP connection — backend expects AgentMcpConnection shape + const mcpServerUrl = connectionProperties['mcpServerUrl'] ?? connectionProperties['serverUrl']; + const authentication = this._buildMcpAuthentication(connectionProperties); + content = { + connection: { + displayName: (connection?.properties as any)?.displayName ?? '', + mcpServerUrl, + ...(authentication ? { authentication } : {}), + }, + mcpServerPath: operationPath, + }; + } else { + // Managed API connection — backend expects ConnectionReference shape + content = { + managedConnection: { + connection: { id: connectionId }, + }, + mcpServerPath: operationPath, + }; + } + } else { + content = { mcpServerPath: operationPath }; + } + + const mcpToolsResponse = await httpClient.post({ + uri, + queryParameters: { 'api-version': apiVersion }, + content, + }); + const tools = this._getResponseFromDynamicApi(mcpToolsResponse, uri); + + return (tools ?? []).map((tool: any) => ({ + value: tool.name, + displayName: tool.name, + description: tool.description, + })); + } + const uri = `${connectionId}/dynamicList`; const response = await httpClient.post({ uri, @@ -174,4 +226,32 @@ export class ConsumptionConnectorService extends BaseConnectorService { return undefined; } + + private _buildMcpAuthentication(connectionProperties: Record): Record | undefined { + const authType = connectionProperties['authenticationType']; + if (!authType || authType === 'None') { + return undefined; + } + + const authentication: Record = { type: authType }; + if (authType === 'ApiKey') { + authentication['value'] = connectionProperties['key']; + authentication['name'] = connectionProperties['keyHeaderName']; + authentication['in'] = 'header'; + } else if (authType === 'Basic') { + authentication['username'] = connectionProperties['username']; + authentication['password'] = connectionProperties['password']; + } else if (authType === 'ActiveDirectoryOAuth') { + authentication['tenant'] = connectionProperties['tenant']; + authentication['clientId'] = connectionProperties['clientId']; + authentication['secret'] = connectionProperties['secret']; + authentication['authority'] = connectionProperties['authority']; + authentication['audience'] = connectionProperties['audience']; + } else if (authType === 'ClientCertificate') { + authentication['pfx'] = connectionProperties['pfx']; + authentication['password'] = connectionProperties['password']; + } + + return authentication; + } } From d823324528cb929a82d86ac44ebf5245c3dc5b8d Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Wed, 11 Mar 2026 15:26:59 +0530 Subject: [PATCH 05/13] Resolved comments --- .../createConnection/createConnection.tsx | 3 +- .../createConnection/createConnection.tsx | 3 +- .../consumption/__tests__/connector.spec.ts | 398 ++++++++++++++++++ .../__tests__/operationmanifest.spec.ts | 25 ++ .../lib/consumption/connection.ts | 16 +- 5 files changed, 441 insertions(+), 4 deletions(-) create mode 100644 libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts diff --git a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx index c4a2a1b4153..764f6a4ebb7 100644 --- a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx +++ b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx @@ -21,6 +21,7 @@ import { import { ConnectionParameterEditorService, ConnectionService, + ConsumptionConnectionService, Capabilities, ConnectionParameterTypes, SERVICE_PRINCIPLE_CONSTANTS, @@ -410,7 +411,7 @@ export const CreateConnection = (props: CreateConnectionProps) => { if (isMcpClientConnection) { const connectionService = ConnectionService(); - const isConsumptionSku = connectionService.constructor.name === 'ConsumptionConnectionService'; + const isConsumptionSku = connectionService instanceof ConsumptionConnectionService; if (isConsumptionSku) { return false; diff --git a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx index 4cb1bb31644..f26fc97b67c 100644 --- a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx +++ b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnection.tsx @@ -21,6 +21,7 @@ import { import { ConnectionParameterEditorService, ConnectionService, + ConsumptionConnectionService, Capabilities, ConnectionParameterTypes, SERVICE_PRINCIPLE_CONSTANTS, @@ -412,7 +413,7 @@ export const CreateConnection = (props: CreateConnectionProps) => { if (isMcpClientConnection) { const connectionService = ConnectionService(); - const isConsumptionSku = connectionService.constructor.name === 'ConsumptionConnectionService'; + const isConsumptionSku = connectionService instanceof ConsumptionConnectionService; if (isConsumptionSku) { return false; 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 new file mode 100644 index 00000000000..55396f333c5 --- /dev/null +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts @@ -0,0 +1,398 @@ +import { describe, vi, beforeEach, it, expect } from 'vitest'; +import { ConsumptionConnectorService } from '../connector'; +import type { IHttpClient } from '../../httpClient'; +import { InitConnectionService } from '../../connection'; +import type { Connection } from '../../../../utils/src'; + +describe('ConsumptionConnectorService', () => { + let mockHttpClient: IHttpClient; + let connectorService: ConsumptionConnectorService; + + const baseUrl = 'https://management.azure.com'; + const workflowReferenceId = '/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Logic/workflows/test-workflow'; + const apiVersion = '2018-07-01-preview'; + + const createMockOptions = () => ({ + apiVersion, + baseUrl, + httpClient: {} as IHttpClient, + workflowReferenceId, + clientSupportedOperations: [] as { connectorId: string; operationId: string }[], + schemaClient: {}, + valuesClient: {}, + }); + + beforeEach(() => { + mockHttpClient = { + dispose: vi.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + patch: vi.fn(), + delete: vi.fn(), + }; + + const options = createMockOptions(); + options.httpClient = mockHttpClient; + connectorService = new ConsumptionConnectorService(options); + }); + + describe('constructor', () => { + it('should throw when workflowReferenceId is not provided', () => { + expect( + () => + new ConsumptionConnectorService({ + ...createMockOptions(), + workflowReferenceId: '', + }) + ).toThrow('workflowReferenceId required'); + }); + }); + + describe('getListDynamicValues', () => { + const mcpDynamicState = { + operationId: 'listMcpTools', + apiType: 'mcp', + }; + + const nonMcpDynamicState = { + operationId: 'someOperation', + parameters: {}, + }; + + const mockMcpToolsResponse = { + response: { + statusCode: 'OK', + body: [ + { name: 'tool1', description: 'First tool' }, + { name: 'tool2', description: 'Second tool' }, + ], + headers: {}, + }, + }; + + describe('MCP built-in connections', () => { + const builtInConnectionId = '/connectionProviders/mcpclient/connections/test-mcp'; + + beforeEach(() => { + InitConnectionService({ + getConnection: vi.fn().mockResolvedValue({ + id: builtInConnectionId, + name: 'test-mcp', + properties: { + displayName: 'Test MCP Server', + parameterValues: { + mcpServerUrl: 'https://mcp.example.com', + authenticationType: 'None', + }, + }, + } as unknown as Connection), + } as any); + }); + + it('should call listMcpTools endpoint with correct URL', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + await connectorService.getListDynamicValues( + builtInConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState + ); + + expect(mockHttpClient.post).toHaveBeenCalledWith( + expect.objectContaining({ + uri: `${baseUrl}${workflowReferenceId}/listMcpTools`, + }) + ); + }); + + it('should send native connection shape with mcpServerUrl', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + await connectorService.getListDynamicValues( + builtInConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState + ); + + const postCallArgs = vi.mocked(mockHttpClient.post).mock.calls[0][0]; + const content = postCallArgs.content as any; + + expect(content.connection).toBeDefined(); + expect(content.connection.mcpServerUrl).toBe('https://mcp.example.com'); + expect(content.connection.displayName).toBe('Test MCP Server'); + }); + + it('should include authentication for ApiKey auth type', async () => { + InitConnectionService({ + getConnection: vi.fn().mockResolvedValue({ + id: builtInConnectionId, + name: 'test-mcp', + properties: { + displayName: 'Test MCP Server', + parameterValues: { + mcpServerUrl: 'https://mcp.example.com', + authenticationType: 'ApiKey', + key: 'test-api-key', + keyHeaderName: 'X-Api-Key', + }, + }, + } as unknown as Connection), + } as any); + + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + await connectorService.getListDynamicValues( + builtInConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState + ); + + const postCallArgs = vi.mocked(mockHttpClient.post).mock.calls[0][0]; + const content = postCallArgs.content as any; + + expect(content.connection.authentication).toEqual({ + type: 'ApiKey', + value: 'test-api-key', + name: 'X-Api-Key', + in: 'header', + }); + }); + + it('should not include authentication when auth type is None', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + await connectorService.getListDynamicValues( + builtInConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState + ); + + const postCallArgs = vi.mocked(mockHttpClient.post).mock.calls[0][0]; + const content = postCallArgs.content as any; + + expect(content.connection.authentication).toBeUndefined(); + }); + + it('should map tools response to ListDynamicValue array', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + const result = await connectorService.getListDynamicValues( + builtInConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState + ); + + expect(result).toEqual([ + { value: 'tool1', displayName: 'tool1', description: 'First tool' }, + { value: 'tool2', displayName: 'tool2', description: 'Second tool' }, + ]); + }); + + it('should return empty array when tools response is null', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue({ + response: { statusCode: 'OK', body: null, headers: {} }, + }); + + const result = await connectorService.getListDynamicValues( + builtInConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState + ); + + expect(result).toEqual([]); + }); + + it('should pass mcpServerPath from operationPath', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + await connectorService.getListDynamicValues( + builtInConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState, + false, + '/custom/mcp/path' + ); + + const postCallArgs = vi.mocked(mockHttpClient.post).mock.calls[0][0]; + const content = postCallArgs.content as any; + + expect(content.mcpServerPath).toBe('/custom/mcp/path'); + }); + }); + + describe('MCP managed connections', () => { + const managedConnectionId = '/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Web/connections/mcp-managed'; + + beforeEach(() => { + InitConnectionService({ + getConnection: vi.fn().mockResolvedValue({ + id: managedConnectionId, + name: 'mcp-managed', + properties: { + displayName: 'Managed MCP', + }, + } as unknown as Connection), + } as any); + }); + + it('should send managedConnection shape for non-builtin connections', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + await connectorService.getListDynamicValues( + managedConnectionId, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState, + false, + '/mcp/path' + ); + + const postCallArgs = vi.mocked(mockHttpClient.post).mock.calls[0][0]; + const content = postCallArgs.content as any; + + expect(content.managedConnection).toEqual({ + connection: { id: managedConnectionId }, + }); + expect(content.mcpServerPath).toBe('/mcp/path'); + expect(content.connection).toBeUndefined(); + }); + }); + + describe('MCP with no connectionId', () => { + it('should send only mcpServerPath when connectionId is undefined', async () => { + vi.mocked(mockHttpClient.post).mockResolvedValue(mockMcpToolsResponse); + + await connectorService.getListDynamicValues( + undefined, + '/connectionProviders/mcpclient', + 'nativemcpclient', + {}, + mcpDynamicState, + false, + '/mcp/path' + ); + + const postCallArgs = vi.mocked(mockHttpClient.post).mock.calls[0][0]; + const content = postCallArgs.content as any; + + expect(content).toEqual({ mcpServerPath: '/mcp/path' }); + }); + }); + + describe('non-MCP dynamic list', () => { + it('should call dynamicList endpoint for non-MCP connections', async () => { + const connectionId = '/some/connection/id'; + + vi.mocked(mockHttpClient.post).mockResolvedValue({ + response: { + statusCode: 'OK', + body: { value: [{ value: 'option1', displayName: 'Option 1' }] }, + headers: {}, + }, + }); + + await connectorService.getListDynamicValues(connectionId, 'someConnector', 'someOperation', {}, nonMcpDynamicState); + + expect(mockHttpClient.post).toHaveBeenCalledWith( + expect.objectContaining({ + uri: `${connectionId}/dynamicList`, + }) + ); + }); + }); + }); + + describe('_buildMcpAuthentication', () => { + // Access private method via any cast for testing + const buildAuth = (props: Record) => { + return (connectorService as any)._buildMcpAuthentication(props); + }; + + it('should return undefined for None auth type', () => { + expect(buildAuth({ authenticationType: 'None' })).toBeUndefined(); + }); + + it('should return undefined when no auth type', () => { + expect(buildAuth({})).toBeUndefined(); + }); + + it('should build ApiKey authentication', () => { + const result = buildAuth({ + authenticationType: 'ApiKey', + key: 'my-key', + keyHeaderName: 'Authorization', + }); + + expect(result).toEqual({ + type: 'ApiKey', + value: 'my-key', + name: 'Authorization', + in: 'header', + }); + }); + + it('should build Basic authentication', () => { + const result = buildAuth({ + authenticationType: 'Basic', + username: 'user', + password: 'pass', + }); + + expect(result).toEqual({ + type: 'Basic', + username: 'user', + password: 'pass', + }); + }); + + it('should build ActiveDirectoryOAuth authentication', () => { + const result = buildAuth({ + authenticationType: 'ActiveDirectoryOAuth', + tenant: 'my-tenant', + clientId: 'my-client', + secret: 'my-secret', + authority: 'https://login.microsoftonline.com', + audience: 'https://api.example.com', + }); + + expect(result).toEqual({ + type: 'ActiveDirectoryOAuth', + tenant: 'my-tenant', + clientId: 'my-client', + secret: 'my-secret', + authority: 'https://login.microsoftonline.com', + audience: 'https://api.example.com', + }); + }); + + it('should build ClientCertificate authentication', () => { + const result = buildAuth({ + authenticationType: 'ClientCertificate', + pfx: 'cert-data', + password: 'cert-pass', + }); + + expect(result).toEqual({ + type: 'ClientCertificate', + pfx: 'cert-data', + password: 'cert-pass', + }); + }); + }); +}); diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/operationmanifest.spec.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/operationmanifest.spec.ts index 4e25a2c1c30..f2920c6652e 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/operationmanifest.spec.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/operationmanifest.spec.ts @@ -29,6 +29,18 @@ describe('ConsumptionOperationManifestService', () => { }); describe('getOperationInfo', () => { + test('should return correct operation info for mcpclienttool type', async () => { + const definition = { + type: 'McpClientTool', + inputs: {}, + }; + + const result = await operationManifestService.getOperationInfo(definition, false); + + expect(result.connectorId).toBe('connectionProviders/mcpclient'); + expect(result.operationId).toBe('nativemcpclient'); + }); + test('should return correct operation info for workflow type', async () => { const definition = { type: 'workflow', @@ -91,6 +103,11 @@ describe('ConsumptionOperationManifestService', () => { }); describe('isSupported', () => { + test('should return true for mcpclienttool builtin operation type', () => { + const result = operationManifestService.isSupported('mcpclienttool', 'builtin'); + expect(result).toBe(true); + }); + test('should return true for nestedagent operation type', () => { const result = operationManifestService.isSupported('nestedagent'); expect(result).toBe(true); @@ -108,6 +125,14 @@ describe('ConsumptionOperationManifestService', () => { }); describe('getOperationManifest', () => { + test('should return MCP manifest for nativemcpclient operation', async () => { + const result = await operationManifestService.getOperationManifest('connectionProviders/mcpclient', 'nativemcpclient'); + + expect(result).toBeDefined(); + expect(result.properties).toBeDefined(); + expect(result.properties.description).toBe('Uses an MCP server'); + }); + test('should return manifest for invokenestedagent operation', async () => { const result = await operationManifestService.getOperationManifest('/connectionProviders/workflow', 'invokenestedagent'); diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts index 1ba79a7729b..527a5e624f9 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connection.ts @@ -40,7 +40,19 @@ export class ConsumptionConnectionService extends BaseConnectionService { if (connectionParametersSet?.values) { const values = connectionParametersSet.values; - const authKeys = ['username', 'password', 'key', 'keyHeaderName', 'clientId', 'secret', 'tenant', 'authority', 'audience', 'pfx']; + const authKeys = [ + 'username', + 'password', + 'key', + 'keyHeaderName', + 'value', + 'clientId', + 'secret', + 'tenant', + 'authority', + 'audience', + 'pfx', + ]; for (const key of authKeys) { if (values[key] !== undefined) { authParams[key] = this.extractParameterValue(values[key]); @@ -158,7 +170,7 @@ export class ConsumptionConnectionService extends BaseConnectionService { const { authenticationType, authParams } = this.extractAuthParameters(connectionInfo.connectionParametersSet); const connection = { - id: `connectionProviders/mcpclient/connections/${connectionName}`, + id: `/connectionProviders/mcpclient/connections/${connectionName}`, name: connectionName, type: 'connections', location: '', From acda446db052684bd0f9bc2f1bb0acd3d26195c8 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Wed, 11 Mar 2026 15:57:01 +0530 Subject: [PATCH 06/13] Undo changes in package file --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b04847d0b2e..57b3968d040 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "lint-staged": { "*.{js,ts,tsx}": [ "npm run extract", - "eslint --cache --fix --no-warn-ignored", + "eslint --cache --fix", "biome check --write" ] }, From 7bfacc11215188c88cf36a9fad064e8e7f197830 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Mon, 16 Mar 2026 14:14:41 +0530 Subject: [PATCH 07/13] Builtin MCP tool fix --- .../lib/consumption/connector.ts | 67 +++++++++++++++++-- package.json | 2 +- 2 files changed, 64 insertions(+), 5 deletions(-) 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 bf58bf04382..f88138c724c 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 @@ -2,6 +2,7 @@ import type { OpenAPIV2 } from '../../../utils/src'; import { ArgumentException, UnsupportedException, optional, equals, getResourceName } from '../../../utils/src'; import type { BaseConnectorServiceOptions } from '../base'; import { BaseConnectorService } from '../base'; +import { ConnectionService } from '../connection'; import type { ListDynamicValue, ManagedIdentityRequestProperties, TreeDynamicExtension, TreeDynamicValue } from '../connector'; import { pathCombine, unwrapPaginatedResponse } from '../helpers'; import { LoggerService } from '../logger'; @@ -55,7 +56,8 @@ export class ConsumptionConnectorService extends BaseConnectorService { isManagedIdentityConnection?: boolean ): Promise { const { apiVersion, httpClient } = this.options; - const { operationId: dynamicOperation } = dynamicState; + const { operationId: dynamicOperation, apiType } = dynamicState; + const isMcpConnection = apiType === 'mcp' && dynamicOperation === 'listMcpTools'; const invokeParameters = this._getInvokeParameters(parameters, dynamicState); @@ -69,7 +71,55 @@ export class ConsumptionConnectorService extends BaseConnectorService { }); } - const uri = `${connectionId}/dynamicList`; + // MCP-specific: use listMcpTools endpoint instead of dynamicList + if (isMcpConnection) { + const { workflowReferenceId } = this.options; + const uri = `${workflowReferenceId}/listMcpTools`; + + // Get the connection data from ConnectionService + let connection: any; + try { + connection = connectionId ? await ConnectionService().getConnection(connectionId) : undefined; + } catch { + // Connection may not be in cache/API Hub for built-in MCP + } + + let content: any = {}; + if (connection?.properties?.parameterValues) { + // Built-in MCP connection — send connection info in the expected format + const { mcpServerUrl, authenticationType, ...authParams } = connection.properties.parameterValues; + content = { + connection: { + inputs: { + Connection: { + McpServerUrl: mcpServerUrl, + Authentication: authenticationType || 'None', + ...authParams, + }, + }, + type: 'McpClientTool', + kind: 'BuiltIn', + }, + }; + } + + const mcpToolsResponse = await httpClient.post({ + uri, + queryParameters: { 'api-version': '2018-07-01-preview' }, + content, + }); + const tools = this._getResponseFromDynamicApi(mcpToolsResponse, uri); + + return (tools ?? []).map((tool: any) => ({ + value: tool.name, + displayName: tool.name, + description: tool.description, + })); + } + + // Regular dynamic list for non-MCP connections + const resolvedConnectionId = this._resolveConnectionId(connectionId); + const uri = `${resolvedConnectionId}/dynamicList`; const response = await httpClient.post({ uri, queryParameters: { 'api-version': apiVersion }, @@ -110,7 +160,8 @@ export class ConsumptionConnectorService extends BaseConnectorService { }); } - const uri = `${connectionId}/dynamicProperties`; + const resolvedConnectionId = this._resolveConnectionId(connectionId); + const uri = `${resolvedConnectionId}/dynamicProperties`; const response = await httpClient.post({ uri, queryParameters: { 'api-version': apiVersion }, @@ -136,7 +187,8 @@ export class ConsumptionConnectorService extends BaseConnectorService { const { apiVersion, httpClient } = this.options; const { dynamicState, selectionState } = dynamicExtension; - const uri = `${connectionId}/dynamicTree`; + const resolvedConnectionId = this._resolveConnectionId(connectionId); + const uri = `${resolvedConnectionId}/dynamicTree`; const response = await httpClient.post({ uri, queryParameters: { 'api-version': apiVersion }, @@ -156,6 +208,13 @@ export class ConsumptionConnectorService extends BaseConnectorService { })); } + private _resolveConnectionId(connectionId: string | undefined): string | undefined { + if (connectionId && !connectionId.startsWith('/subscriptions/')) { + return `${this.options.workflowReferenceId}/${connectionId}`; + } + return connectionId; + } + private _getPropertiesIfNeeded(isManagedIdentityConnection?: boolean): | { workflowReference: { name: string; id: string; type: string }; diff --git a/package.json b/package.json index b04847d0b2e..57b3968d040 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "lint-staged": { "*.{js,ts,tsx}": [ "npm run extract", - "eslint --cache --fix --no-warn-ignored", + "eslint --cache --fix", "biome check --write" ] }, From df9a6fa6166d743028efa95ee5e71582368c1eff Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Mon, 16 Mar 2026 14:58:56 +0530 Subject: [PATCH 08/13] Delete extra files --- AzuriteConfig | 1 - __azurite_db_table__.json | 1 - 2 files changed, 2 deletions(-) delete mode 100644 AzuriteConfig delete mode 100644 __azurite_db_table__.json diff --git a/AzuriteConfig b/AzuriteConfig deleted file mode 100644 index cd6edbe4a1d..00000000000 --- a/AzuriteConfig +++ /dev/null @@ -1 +0,0 @@ -{"instaceID":"428c5a83-4dc0-4944-9b94-f1dfeedd4626"} \ No newline at end of file diff --git a/__azurite_db_table__.json b/__azurite_db_table__.json deleted file mode 100644 index c724f620ba5..00000000000 --- a/__azurite_db_table__.json +++ /dev/null @@ -1 +0,0 @@ -{"filename":"c:\\dev\\LogicAppsUX\\__azurite_db_table__.json","collections":[{"name":"$TABLES_COLLECTION$","data":[],"idIndex":null,"binaryIndices":{"account":{"name":"account","dirty":false,"values":[]},"table":{"name":"table","dirty":false,"values":[]}},"constraints":null,"uniqueNames":[],"transforms":{},"objType":"$TABLES_COLLECTION$","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"adaptiveBinaryIndices":true,"transactional":false,"cloneObjects":false,"cloneMethod":"parse-stringify","asyncListeners":false,"disableMeta":false,"disableChangesApi":true,"disableDeltaChangesApi":true,"autoupdate":false,"serializableIndices":true,"disableFreeze":true,"ttl":null,"maxId":0,"DynamicViews":[],"events":{"insert":[],"update":[],"pre-insert":[],"pre-update":[],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[],"dirtyIds":[]},{"name":"$SERVICES_COLLECTION$","data":[],"idIndex":null,"binaryIndices":{},"constraints":null,"uniqueNames":["accountName"],"transforms":{},"objType":"$SERVICES_COLLECTION$","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"adaptiveBinaryIndices":true,"transactional":false,"cloneObjects":false,"cloneMethod":"parse-stringify","asyncListeners":false,"disableMeta":false,"disableChangesApi":true,"disableDeltaChangesApi":true,"autoupdate":false,"serializableIndices":true,"disableFreeze":true,"ttl":null,"maxId":0,"DynamicViews":[],"events":{"insert":[],"update":[],"pre-insert":[],"pre-update":[],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[],"dirtyIds":[]}],"databaseVersion":1.5,"engineVersion":1.5,"autosave":true,"autosaveInterval":5000,"autosaveHandle":null,"throttledSaves":true,"options":{"persistenceMethod":"fs","autosave":true,"autosaveInterval":5000,"serializationMethod":"normal","destructureDelimiter":"$<\n"},"persistenceMethod":"fs","persistenceAdapter":null,"verbose":false,"events":{"init":[null],"loaded":[],"flushChanges":[],"close":[],"changes":[],"warning":[]},"ENV":"NODEJS"} \ No newline at end of file From 2bb4e2622d4b496ad9a88c902476ef766ad8d973 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Mon, 16 Mar 2026 21:19:04 +0530 Subject: [PATCH 09/13] fix code view native mcp --- .../core/actions/bjsworkflow/serializer.ts | 114 +++++++++++------- .../src/lib/core/state/workflow/helper.ts | 4 + .../core/actions/bjsworkflow/serializer.ts | 63 +++++++++- .../src/lib/core/state/workflow/helper.ts | 4 + 4 files changed, 140 insertions(+), 45 deletions(-) diff --git a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts index c1560013626..b911ec8bc41 100644 --- a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts +++ b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts @@ -26,6 +26,7 @@ import { LogEntryLevel, LoggerService, OperationManifestService, + ConnectionService, WorkflowService, getIntl, create, @@ -75,7 +76,7 @@ import type { import merge from 'lodash.merge'; import { createTokenValueSegment } from '../../utils/parameters/segment'; import { ConnectorManifest } from './agent'; -import { isA2AWorkflow } from '../../../core/state/workflow/helper'; +import { isA2AWorkflow, isBuiltInMcpOperation } from '../../../core/state/workflow/helper'; export interface SerializeOptions { skipValidation: boolean; @@ -288,9 +289,12 @@ export const serializeOperation = async ( let serializedOperation: LogicAppsV2.OperationDefinition; const isManagedMcpClient = operation.type?.toLowerCase() === 'mcpclienttool' && operation.kind?.toLowerCase() === 'managed'; + const isBuiltInMcpClient = isBuiltInMcpOperation(operation); if (isManagedMcpClient) { serializedOperation = await serializeManagedMcpOperation(rootState, operationId); + } else if (isBuiltInMcpClient) { + serializedOperation = await serializeBuiltInMcpOperation(rootState, operationId); } else if (OperationManifestService().isSupported(operation.type, operation.kind)) { serializedOperation = await serializeManifestBasedOperation(rootState, operationId); } else { @@ -334,22 +338,6 @@ export const serializeOperation = async ( removeRunAfterTriggerFromOperation(serializedOperation, replacedTriggerId); } - // If dynamic inputs failed to load and no stashed parameters exist (initial load failure), - // preserve the original definition's inputs so dynamic parameter values are not lost on save. - const nodeInputs = getRecordEntry(rootState.operations.inputParameters, operationId); - const hasDynamicInputsError = !!errors?.[ErrorLevel.DynamicInputs]; - const hasStash = !!nodeInputs?.stashedDynamicParameterValues?.length; - const hasDynamicParamsInGroups = getOperationInputParameters(nodeInputs as NodeInputs).some((p) => p.info.isDynamic); - if (hasDynamicInputsError && !hasStash && !hasDynamicParamsInGroups) { - const originalDef = getRecordEntry(rootState.workflow.operations, operationId); - if (originalDef && 'inputs' in originalDef && originalDef.inputs && 'inputs' in serializedOperation) { - serializedOperation = { - ...serializedOperation, - inputs: merge({}, originalDef.inputs, serializedOperation.inputs), - }; - } - } - return serializedOperation; }; @@ -521,6 +509,60 @@ const serializeManagedMcpOperation = async (rootState: RootState, nodeId: string }; }; +const serializeBuiltInMcpOperation = async (rootState: RootState, nodeId: string): Promise => { + const operationInfo = getRecordEntry(rootState.operations.operationInfo, nodeId) as NodeOperation; + if (!operationInfo) { + throw new AssertionException(AssertionErrorCode.OPERATION_NOT_FOUND, `Operation with id ${nodeId} not found`); + } + const { type, kind } = operationInfo; + + const inputsToSerialize = getOperationInputsToSerialize(rootState, nodeId); + + const nativeMcpOperationInfo = { connectorId: 'connectionProviders/mcpclient', operationId: 'nativemcpclient' }; + const manifest = await getOperationManifest(nativeMcpOperationInfo); + const inputParameters = serializeParametersFromManifest(inputsToSerialize, manifest); + + const operationFromWorkflow = getRecordEntry(rootState.workflow.operations, nodeId) as LogicAppsV2.OperationDefinition; + + // Look up the connection to get MCP server URL and authentication type + const referenceKey = getRecordEntry(rootState.connections.connectionsMapping, nodeId); + const connectionReference = referenceKey ? getRecordEntry(rootState.connections.connectionReferences, referenceKey) : undefined; + const connectionId = connectionReference?.connection?.id; + + let mcpServerUrl = ''; + let authenticationType = 'None'; + + if (connectionId) { + try { + const connection = await ConnectionService().getConnection(connectionId); + const parameterValues = (connection?.properties as any)?.parameterValues; + if (parameterValues) { + mcpServerUrl = parameterValues.mcpServerUrl ?? ''; + authenticationType = parameterValues.authenticationType ?? 'None'; + } + } catch { + // Fall back to empty values if connection lookup fails + } + } + + const inputs = { + Connection: { + McpServerUrl: mcpServerUrl, + Authentication: authenticationType, + }, + parameters: { + ...inputParameters.parameters, + }, + }; + + return { + type: type, + kind: kind, + ...optional('description', operationFromWorkflow.description), + ...optional('inputs', inputs), + }; +}; + const serializeSwaggerBasedOperation = async (rootState: RootState, operationId: string): Promise => { const idReplacements = rootState.workflow.idReplacements; const operationInfo = getRecordEntry(rootState.operations.operationInfo, operationId) as NodeOperation; @@ -606,29 +648,15 @@ export interface SerializedParameter extends ParameterInfo { export const getOperationInputsToSerialize = (rootState: RootState, operationId: string): SerializedParameter[] => { const idReplacements = rootState.workflow.idReplacements; const nodeInputs = getRecordEntry(rootState.operations.inputParameters, operationId) as NodeInputs; - const shouldEncode = shouldEncodeParameterValueForOperationBasedOnMetadata(rootState.operations.operationInfo[operationId] ?? {}); - - const currentParams = getOperationInputParameters(nodeInputs); - const serialized = currentParams.map((input) => ({ + return getOperationInputParameters(nodeInputs).map((input) => ({ ...input, - value: parameterValueToString(input, true /* isDefinitionValue */, idReplacements, shouldEncode), + value: parameterValueToString( + input, + true /* isDefinitionValue */, + idReplacements, + shouldEncodeParameterValueForOperationBasedOnMetadata(rootState.operations.operationInfo[operationId] ?? {}) + ), })); - - // If dynamic parameters were stashed (cleared during loading or after failure), - // include them as fallback so their values are not lost on save. - const stashed = nodeInputs?.stashedDynamicParameterValues; - if (stashed?.length) { - const currentKeys = new Set(currentParams.map((p) => p.parameterKey)); - const missingStashed = stashed.filter((p) => !currentKeys.has(p.parameterKey)); - for (const param of missingStashed) { - serialized.push({ - ...param, - value: parameterValueToString(param, true /* isDefinitionValue */, idReplacements, shouldEncode), - }); - } - } - - return serialized; }; const serializeParametersFromManifest = (inputs: SerializedParameter[], manifest: OperationManifest): Record => { @@ -997,11 +1025,15 @@ const serializeHost = ( }; const mergeHostWithInputs = (hostInfo: Record, inputs: any): any => { - const result = { ...inputs }; for (const [key, value] of Object.entries(hostInfo)) { - result[key] = result[key] ? { ...result[key], ...value } : value; + if (inputs[key]) { + inputs[key] = { ...inputs[key], ...value }; + } else { + inputs[key] = value; + } } - return result; + + return inputs; }; //#endregion diff --git a/libs/designer-v2/src/lib/core/state/workflow/helper.ts b/libs/designer-v2/src/lib/core/state/workflow/helper.ts index 74e7152cb8f..f3d67558759 100644 --- a/libs/designer-v2/src/lib/core/state/workflow/helper.ts +++ b/libs/designer-v2/src/lib/core/state/workflow/helper.ts @@ -135,6 +135,10 @@ export const isManagedMcpOperation = (operation: { type?: string; kind?: string return equals(operation?.type, Constants.NODE.TYPE.MCP_CLIENT) && equals(operation?.kind, Constants.NODE.KIND.MANAGED); }; +export const isBuiltInMcpOperation = (operation: { type?: string; kind?: string }) => { + return equals(operation?.type, Constants.NODE.TYPE.MCP_CLIENT) && !equals(operation?.kind, Constants.NODE.KIND.MANAGED); +}; + export const isA2AWorkflow = (state: WorkflowState): boolean => { const workflowKind = state.workflowKind; diff --git a/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts b/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts index 2430b15f452..e2846bf6478 100644 --- a/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts +++ b/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts @@ -26,6 +26,7 @@ import { LogEntryLevel, LoggerService, OperationManifestService, + ConnectionService, WorkflowService, getIntl, create, @@ -75,7 +76,7 @@ import type { import merge from 'lodash.merge'; import { createTokenValueSegment } from '../../utils/parameters/segment'; import { ConnectorManifest } from './agent'; -import { isA2AWorkflow, isManagedMcpOperation } from '../../../core/state/workflow/helper'; +import { isA2AWorkflow, isBuiltInMcpOperation, isManagedMcpOperation } from '../../../core/state/workflow/helper'; export interface SerializeOptions { skipValidation: boolean; @@ -284,9 +285,12 @@ export const serializeOperation = async ( let serializedOperation: LogicAppsV2.OperationDefinition; const isManagedMcpClient = isManagedMcpOperation(operation); + const isBuiltInMcpClient = isBuiltInMcpOperation(operation); if (isManagedMcpClient) { serializedOperation = await serializeManagedMcpOperation(rootState, operationId); + } else if (isBuiltInMcpClient) { + serializedOperation = await serializeBuiltInMcpOperation(rootState, operationId); } else if (OperationManifestService().isSupported(operation.type, operation.kind)) { serializedOperation = await serializeManifestBasedOperation(rootState, operationId); } else { @@ -501,6 +505,60 @@ const serializeManagedMcpOperation = async (rootState: RootState, nodeId: string }; }; +const serializeBuiltInMcpOperation = async (rootState: RootState, nodeId: string): Promise => { + const operationInfo = getRecordEntry(rootState.operations.operationInfo, nodeId) as NodeOperation; + if (!operationInfo) { + throw new AssertionException(AssertionErrorCode.OPERATION_NOT_FOUND, `Operation with id ${nodeId} not found`); + } + const { type, kind } = operationInfo; + + const inputsToSerialize = getOperationInputsToSerialize(rootState, nodeId); + + const nativeMcpOperationInfo = { connectorId: 'connectionProviders/mcpclient', operationId: 'nativemcpclient' }; + const manifest = await getOperationManifest(nativeMcpOperationInfo); + const inputParameters = serializeParametersFromManifest(inputsToSerialize, manifest); + + const operationFromWorkflow = getRecordEntry(rootState.workflow.operations, nodeId) as LogicAppsV2.OperationDefinition; + + // Look up the connection to get MCP server URL and authentication type + const referenceKey = getRecordEntry(rootState.connections.connectionsMapping, nodeId); + const connectionReference = referenceKey ? getRecordEntry(rootState.connections.connectionReferences, referenceKey) : undefined; + const connectionId = connectionReference?.connection?.id; + + let mcpServerUrl = ''; + let authenticationType = 'None'; + + if (connectionId) { + try { + const connection = await ConnectionService().getConnection(connectionId); + const parameterValues = (connection?.properties as any)?.parameterValues; + if (parameterValues) { + mcpServerUrl = parameterValues.mcpServerUrl ?? ''; + authenticationType = parameterValues.authenticationType ?? 'None'; + } + } catch { + // Fall back to empty values if connection lookup fails + } + } + + const inputs = { + Connection: { + McpServerUrl: mcpServerUrl, + Authentication: authenticationType, + }, + parameters: { + ...inputParameters.parameters, + }, + }; + + return { + type: type, + kind: kind, + ...optional('description', operationFromWorkflow.description), + ...optional('inputs', inputs), + }; +}; + const serializeSwaggerBasedOperation = async (rootState: RootState, operationId: string): Promise => { const idReplacements = rootState.workflow.idReplacements; const operationInfo = getRecordEntry(rootState.operations.operationInfo, operationId) as NodeOperation; @@ -903,7 +961,6 @@ const serializeHost = ( }, }; case ConnectionReferenceKeyFormat.OpenApiConnection: { - // eslint-disable-next-line no-case-declarations const connectorSegments = connectorId.split('/'); return { host: { @@ -966,10 +1023,8 @@ const serializeHost = ( const mergeHostWithInputs = (hostInfo: Record, inputs: any): any => { for (const [key, value] of Object.entries(hostInfo)) { if (inputs[key]) { - // eslint-disable-next-line no-param-reassign inputs[key] = { ...inputs[key], ...value }; } else { - // eslint-disable-next-line no-param-reassign inputs[key] = value; } } diff --git a/libs/designer/src/lib/core/state/workflow/helper.ts b/libs/designer/src/lib/core/state/workflow/helper.ts index 12a5bf2fc7c..8794b6095be 100644 --- a/libs/designer/src/lib/core/state/workflow/helper.ts +++ b/libs/designer/src/lib/core/state/workflow/helper.ts @@ -135,6 +135,10 @@ export const isManagedMcpOperation = (operation: { type?: string; kind?: string return equals(operation?.type, Constants.NODE.TYPE.MCP_CLIENT) && equals(operation?.kind, Constants.NODE.KIND.MANAGED); }; +export const isBuiltInMcpOperation = (operation: { type?: string; kind?: string }) => { + return equals(operation?.type, Constants.NODE.TYPE.MCP_CLIENT) && !equals(operation?.kind, Constants.NODE.KIND.MANAGED); +}; + export const isA2AWorkflow = (state: WorkflowState): boolean => { const workflowKind = state.workflowKind; From 4ab906f105e775e2d24322e448c0281bf68dc76b Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Mon, 16 Mar 2026 22:33:38 +0530 Subject: [PATCH 10/13] small fix --- .../lib/consumption/connector.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) 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 0852fd42b59..c8bf9844804 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 @@ -87,18 +87,11 @@ export class ConsumptionConnectorService extends BaseConnectorService { let content: any = {}; if (connection?.properties?.parameterValues) { // Built-in MCP connection — send connection info in the expected format - const { mcpServerUrl, authenticationType, ...authParams } = connection.properties.parameterValues; + const { mcpServerUrl, authenticationType } = connection.properties.parameterValues; content = { connection: { - inputs: { - Connection: { - McpServerUrl: mcpServerUrl, - Authentication: authenticationType || 'None', - ...authParams, - }, - }, - type: 'McpClientTool', - kind: 'BuiltIn', + McpServerUrl: mcpServerUrl, + Authentication: authenticationType || 'None', }, }; } From da0628670249797b102d5f8e4e20da6da787da4d Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Tue, 17 Mar 2026 00:08:08 +0530 Subject: [PATCH 11/13] validation error fix --- .../src/lib/core/actions/bjsworkflow/serializer.ts | 5 +++++ libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts index b911ec8bc41..721d220753f 100644 --- a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts +++ b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts @@ -161,6 +161,11 @@ export const serializeWorkflow = async (rootState: RootState, options?: Serializ return references; } + const operation = getRecordEntry(rootState.operations.operationInfo, nodeId); + if (operation && isBuiltInMcpOperation(operation)) { + return references; + } + references[referenceKey] = referencesObject[referenceKey]; return references; }, {}); diff --git a/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts b/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts index e2846bf6478..149a5d558d7 100644 --- a/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts +++ b/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts @@ -161,6 +161,11 @@ export const serializeWorkflow = async (rootState: RootState, options?: Serializ return references; } + const operation = getRecordEntry(rootState.operations.operationInfo, nodeId); + if (operation && isBuiltInMcpOperation(operation)) { + return references; + } + references[referenceKey] = referencesObject[referenceKey]; return references; }, {}); From cefe76ada32c344c40f81c641cf74e2712427c7a Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Tue, 17 Mar 2026 13:18:03 +0530 Subject: [PATCH 12/13] validation error fix --- .../ConsumptionSerializationHelpers.ts | 14 ++++++++- .../core/actions/bjsworkflow/serializer.ts | 31 ++++++++++++++----- .../core/actions/bjsworkflow/serializer.ts | 31 ++++++++++++++----- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/Services/ConsumptionSerializationHelpers.ts b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/Services/ConsumptionSerializationHelpers.ts index 35fab556607..f9049e98105 100644 --- a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/Services/ConsumptionSerializationHelpers.ts +++ b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/Services/ConsumptionSerializationHelpers.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-param-reassign */ import { isOpenApiSchemaVersion } from '@microsoft/logic-apps-designer'; import { clone } from '@microsoft/logic-apps-shared'; @@ -101,6 +100,19 @@ export const convertDesignerWorkflowToConsumptionWorkflow = async (_workflow: an // Remove connections from workflow root delete workflow?.connections; + // Defensive guard: built-in MCP pseudo IDs are not valid ARM connection resources. + // If any legacy/stale path adds one, strip it before save. + const connectionValues = workflow?.parameters?.$connections?.value; + if (connectionValues && typeof connectionValues === 'object') { + Object.keys(connectionValues).forEach((key) => { + const candidate = connectionValues[key]; + const connectionId = candidate?.connectionId; + if (typeof connectionId === 'string' && connectionId.toLowerCase().startsWith('/connectionproviders/mcpclient/connections/')) { + delete connectionValues[key]; + } + }); + } + return workflow; }; diff --git a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts index 721d220753f..bab2c5a0ae1 100644 --- a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts +++ b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts @@ -157,7 +157,12 @@ export const serializeWorkflow = async (rootState: RootState, options?: Serializ const { connectionsMapping, connectionReferences: referencesObject } = rootState.connections; const connectionReferences = Object.keys(connectionsMapping ?? {}).reduce((references: ConnectionReferences, nodeId: string) => { const referenceKey = getRecordEntry(connectionsMapping, nodeId); - if (!referenceKey || !referencesObject[referenceKey]) { + if (!referenceKey) { + return references; + } + + const reference = referencesObject[referenceKey]; + if (!reference) { return references; } @@ -166,7 +171,15 @@ export const serializeWorkflow = async (rootState: RootState, options?: Serializ return references; } - references[referenceKey] = referencesObject[referenceKey]; + const referenceConnectionId = reference.connection?.id; + if ( + referenceConnectionId?.startsWith('/connectionProviders/mcpclient/') || + referenceConnectionId?.startsWith('connectionProviders/mcpclient/') + ) { + return references; + } + + references[referenceKey] = reference; return references; }, {}); @@ -281,17 +294,19 @@ export const serializeOperation = async ( operationId: string, _options?: SerializeOptions ): Promise => { - const errors = getRecordEntry(rootState.operations.errors, operationId); - - if (errors?.[ErrorLevel.Critical]) { - return getRecordEntry(rootState.workflow.operations, operationId) ?? null; - } - const operation = getRecordEntry(rootState.operations.operationInfo, operationId); if (!operation) { return null; } + const errors = getRecordEntry(rootState.operations.errors, operationId); + + // Keep serializing built-in MCP operations even with critical errors so we don't fall back + // to stale workflow JSON that can still contain old connectionReference shapes. + if (errors?.[ErrorLevel.Critical] && !isBuiltInMcpOperation(operation)) { + return getRecordEntry(rootState.workflow.operations, operationId) ?? null; + } + let serializedOperation: LogicAppsV2.OperationDefinition; const isManagedMcpClient = operation.type?.toLowerCase() === 'mcpclienttool' && operation.kind?.toLowerCase() === 'managed'; const isBuiltInMcpClient = isBuiltInMcpOperation(operation); diff --git a/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts b/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts index 149a5d558d7..df089fd5c35 100644 --- a/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts +++ b/libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts @@ -157,7 +157,12 @@ export const serializeWorkflow = async (rootState: RootState, options?: Serializ const { connectionsMapping, connectionReferences: referencesObject } = rootState.connections; const connectionReferences = Object.keys(connectionsMapping ?? {}).reduce((references: ConnectionReferences, nodeId: string) => { const referenceKey = getRecordEntry(connectionsMapping, nodeId); - if (!referenceKey || !referencesObject[referenceKey]) { + if (!referenceKey) { + return references; + } + + const reference = referencesObject[referenceKey]; + if (!reference) { return references; } @@ -166,7 +171,15 @@ export const serializeWorkflow = async (rootState: RootState, options?: Serializ return references; } - references[referenceKey] = referencesObject[referenceKey]; + const referenceConnectionId = reference.connection?.id; + if ( + referenceConnectionId?.startsWith('/connectionProviders/mcpclient/') || + referenceConnectionId?.startsWith('connectionProviders/mcpclient/') + ) { + return references; + } + + references[referenceKey] = reference; return references; }, {}); @@ -277,17 +290,19 @@ export const serializeOperation = async ( operationId: string, _options?: SerializeOptions ): Promise => { - const errors = getRecordEntry(rootState.operations.errors, operationId); - - if (errors?.[ErrorLevel.Critical]) { - return getRecordEntry(rootState.workflow.operations, operationId) ?? null; - } - const operation = getRecordEntry(rootState.operations.operationInfo, operationId); if (!operation) { return null; } + const errors = getRecordEntry(rootState.operations.errors, operationId); + + // Keep serializing built-in MCP operations even with critical errors so we don't fall back + // to stale workflow JSON that can still contain old connectionReference shapes. + if (errors?.[ErrorLevel.Critical] && !isBuiltInMcpOperation(operation)) { + return getRecordEntry(rootState.workflow.operations, operationId) ?? null; + } + let serializedOperation: LogicAppsV2.OperationDefinition; const isManagedMcpClient = isManagedMcpOperation(operation); const isBuiltInMcpClient = isBuiltInMcpOperation(operation); From a20e3326d52c920d21e4ca25e14e8b8a6845f630 Mon Sep 17 00:00:00 2001 From: "Bhavya ." Date: Tue, 17 Mar 2026 15:35:22 +0530 Subject: [PATCH 13/13] Removed connection ref from built in mcp code view --- .../src/lib/core/templates/utils/createhelper.ts | 14 +++++++++++++- .../src/lib/core/templates/utils/createhelper.ts | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/libs/designer-v2/src/lib/core/templates/utils/createhelper.ts b/libs/designer-v2/src/lib/core/templates/utils/createhelper.ts index d4e63086c20..c1b3b77c881 100644 --- a/libs/designer-v2/src/lib/core/templates/utils/createhelper.ts +++ b/libs/designer-v2/src/lib/core/templates/utils/createhelper.ts @@ -11,6 +11,12 @@ interface WorkflowPayload { connectionReferences?: ConnectionReferences; } +const isBuiltInMcpConnectionReference = (connection: any): boolean => { + const apiId = connection?.api?.id?.toLowerCase?.() ?? ''; + const connectionId = connection?.connection?.id?.toLowerCase?.() ?? ''; + return apiId.includes('connectionproviders/mcpclient') || connectionId.includes('/connectionproviders/mcpclient/'); +}; + export const getConsumptionWorkflowPayloadForCreate = ( definition: LogicAppsV2.WorkflowDefinition, parameterDefinitions: Record, @@ -149,11 +155,14 @@ const convertDesignerWorkflowToConsumptionWorkflow = ( } else { // Move connection data to parameters if (workflow?.connections) { + const filteredConnections = Object.fromEntries( + Object.entries(workflow.connections).filter(([, connection]) => !isBuiltInMcpConnectionReference(connection)) + ); workflow.parameters = { ...workflow.parameters, $connections: { value: { - ...workflow.connections, + ...filteredConnections, }, } as WorkflowParameter, }; @@ -179,6 +188,9 @@ const convertDesignerWorkflowToConsumptionWorkflow = ( workflow.parameters.$connections = { type: 'Object', value: {} }; } Object.entries(workflow.connectionReferences ?? {}).forEach(([key, connection]: [key: string, value: any]) => { + if (isBuiltInMcpConnectionReference(connection)) { + return; + } workflow.parameters.$connections.value[key] = { id: connection.api.id, connectionId: connection.connection.id, diff --git a/libs/designer/src/lib/core/templates/utils/createhelper.ts b/libs/designer/src/lib/core/templates/utils/createhelper.ts index 797b35a0d2d..98d56574fbd 100644 --- a/libs/designer/src/lib/core/templates/utils/createhelper.ts +++ b/libs/designer/src/lib/core/templates/utils/createhelper.ts @@ -11,6 +11,12 @@ interface WorkflowPayload { connectionReferences?: ConnectionReferences; } +const isBuiltInMcpConnectionReference = (connection: any): boolean => { + const apiId = connection?.api?.id?.toLowerCase?.() ?? ''; + const connectionId = connection?.connection?.id?.toLowerCase?.() ?? ''; + return apiId.includes('connectionproviders/mcpclient') || connectionId.includes('/connectionproviders/mcpclient/'); +}; + export const getConsumptionWorkflowPayloadForCreate = ( definition: LogicAppsV2.WorkflowDefinition, parameterDefinitions: Record, @@ -149,11 +155,14 @@ const convertDesignerWorkflowToConsumptionWorkflow = ( } else { // Move connection data to parameters if (workflow?.connections) { + const filteredConnections = Object.fromEntries( + Object.entries(workflow.connections).filter(([, connection]) => !isBuiltInMcpConnectionReference(connection)) + ); workflow.parameters = { ...workflow.parameters, $connections: { value: { - ...workflow.connections, + ...filteredConnections, }, } as WorkflowParameter, }; @@ -179,6 +188,9 @@ const convertDesignerWorkflowToConsumptionWorkflow = ( workflow.parameters.$connections = { type: 'Object', value: {} }; } Object.entries(workflow.connectionReferences ?? {}).forEach(([key, connection]: [key: string, value: any]) => { + if (isBuiltInMcpConnectionReference(connection)) { + return; + } // For dynamic connections, pull runtimeSource out to root level const runtimeSource = connection?.connectionProperties?.runtimeSource; const remainingConnectionProperties = connection?.connectionProperties