From db1e3e1968c9484b10f989a3f3b1bdf762d718fc Mon Sep 17 00:00:00 2001 From: Priti Sambandam Date: Thu, 5 Feb 2026 14:29:33 -0800 Subject: [PATCH 1/3] fix(mcp): Adding error handling for generate keys panel --- Localize/lang/strings.json | 4 +- .../app/AzureLogicAppsDesigner/laDesigner.tsx | 1 - apps/Standalone/src/mcp/app/McpServer.tsx | 4 +- .../designer/src/lib/core/mcp/utils/server.ts | 42 ++++++++++--------- .../server/__test__/generatekeys.spec.tsx | 32 ++++++++++++++ .../src/lib/ui/mcp/panel/server/create.tsx | 4 +- .../lib/ui/mcp/panel/server/generatekeys.tsx | 26 +++++++++--- 7 files changed, 82 insertions(+), 31 deletions(-) diff --git a/Localize/lang/strings.json b/Localize/lang/strings.json index b73e0750af3..52f06cc79b7 100644 --- a/Localize/lang/strings.json +++ b/Localize/lang/strings.json @@ -1205,7 +1205,7 @@ "Q13J5V": "Create deployment model", "Q1LEiE": "Previous", "Q2X3qQ": "Actions need to be triggered by another node, e.g. at regular intervals with the Schedule node", - "Q2p4Zh": "An error occurred while generating keys. Error details: {error}", + "Q2p4Zh": "An error occurred while generating keys. Error details: {errorMessage}", "Q4TUFX": "Discard", "Q5Fh2R": "Required. The string to calculate UTF-16 length from.", "Q5w4Do": "Add dynamic data or expressions by inserting a /", @@ -1616,7 +1616,7 @@ "YxH2JT": "When a message is received", "Yz9o1k": "Not connected.", "Yzw97z": "Choose a connection to use for this MCP server", - "Z1p3Yh": "An error occurred while updating authentication settings. Error details: {error}", + "Z1p3Yh": "An error occurred while updating authentication settings. Error details: {errorMessage}", "Z3Ak88": "Add optional prompts or questions for the agent. For better results, focus each item on a single specific prompt or question.", "Z8BOCl": "No identities available", "Z8tBFS": "The workflow assistant is designed only to provide help and doesn't support workflow creation or editing.", diff --git a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx index 82eb3bd382f..e749912d55f 100644 --- a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx +++ b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx @@ -651,7 +651,6 @@ const getDesignerServices = ( ...defaultServiceParams, workflowName, clientSupportedOperations: [ - ['connectionProviders/localWorkflowOperation', 'invokeWorkflow'], ['connectionProviders/xmlOperations', 'xmlValidation'], ['connectionProviders/xmlOperations', 'xmlTransform'], ['connectionProviders/liquidOperations', 'liquidJsonToJson'], diff --git a/apps/Standalone/src/mcp/app/McpServer.tsx b/apps/Standalone/src/mcp/app/McpServer.tsx index daf9ab9f846..b25dbd033ba 100644 --- a/apps/Standalone/src/mcp/app/McpServer.tsx +++ b/apps/Standalone/src/mcp/app/McpServer.tsx @@ -17,14 +17,14 @@ export const McpServer = () => { theme: state.workflowLoader.theme, })); - const appId = '/subscriptions/f34b22a3-2202-4fb1-b040-1332bd928c84/resourceGroups/TestACSRG/providers/Microsoft.Web/sites/pritimcpserver'; + const appId = '/subscriptions/f34b22a3-2202-4fb1-b040-1332bd928c84/resourceGroups/TestACSRG/providers/Microsoft.Web/sites/prititemplates'; const resourceDetails = useMemo(() => { const parser = new ArmParser(appId); return { subscriptionId: parser.subscriptionId || '', resourceGroup: parser.resourceGroup || '', logicAppName: parser.resourceName || '', - location: 'westus', + location: 'westus2', }; }, []); diff --git a/libs/designer/src/lib/core/mcp/utils/server.ts b/libs/designer/src/lib/core/mcp/utils/server.ts index 16b02420741..88a05459c59 100644 --- a/libs/designer/src/lib/core/mcp/utils/server.ts +++ b/libs/designer/src/lib/core/mcp/utils/server.ts @@ -1,4 +1,4 @@ -import { equals, getIntl, getPropertyValue, isNullOrEmpty, ResourceService } from '@microsoft/logic-apps-shared'; +import { equals, getIntl, getObjectPropertyValue, getPropertyValue, isNullOrEmpty, ResourceService } from '@microsoft/logic-apps-shared'; import { getHostConfig, resetQueriesOnServerAuthUpdate } from './queries'; export const validateMcpServerName = (serverName: string): string | undefined => { @@ -86,16 +86,18 @@ export const updateAuthSettings = async (siteResourceId: string, options: string { 'api-version': '2020-06-01' }, { files: { 'host.json': updatedConfig } } ); - } catch (error) { - const errorMessage = getIntl().formatMessage( - { - defaultMessage: 'An error occurred while updating authentication settings. Error details: {error}', - id: 'Z1p3Yh', - description: 'General error message for authentication settings update failure', - }, - { error: (error as any).message } + } catch (error: any) { + const errorMessage = error?.error?.message ?? error?.message ?? undefined; + throw new Error( + getIntl().formatMessage( + { + defaultMessage: 'An error occurred while updating authentication settings. Error details: {errorMessage}', + id: 'Z1p3Yh', + description: 'General error message for authentication settings update failure', + }, + { errorMessage } + ) ); - throw new Error(errorMessage); } finally { resetQueriesOnServerAuthUpdate(siteResourceId); } @@ -112,16 +114,18 @@ export const generateKeys = async (siteResourceId: string, duration: string, acc ); return getPropertyValue(response.headers, 'X-API-Key') as string; - } catch (error) { - const errorMessage = getIntl().formatMessage( - { - defaultMessage: 'An error occurred while generating keys. Error details: {error}', - id: 'Q2p4Zh', - description: 'General error message for key generation failure', - }, - { error: (error as any).message } + } catch (error: any) { + const errorMessage = getObjectPropertyValue(error, ['error', 'message']) ?? getObjectPropertyValue(error, ['message']) ?? undefined; + throw new Error( + getIntl().formatMessage( + { + defaultMessage: 'An error occurred while generating keys. Error details: {errorMessage}', + id: 'Q2p4Zh', + description: 'General error message for key generation failure', + }, + { errorMessage } + ) ); - throw new Error(errorMessage); } }; diff --git a/libs/designer/src/lib/ui/mcp/panel/server/__test__/generatekeys.spec.tsx b/libs/designer/src/lib/ui/mcp/panel/server/__test__/generatekeys.spec.tsx index ab5c1bbca50..357280a46e7 100644 --- a/libs/designer/src/lib/ui/mcp/panel/server/__test__/generatekeys.spec.tsx +++ b/libs/designer/src/lib/ui/mcp/panel/server/__test__/generatekeys.spec.tsx @@ -10,6 +10,7 @@ import { IntlProvider } from 'react-intl'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import userEvent from '@testing-library/user-event'; import { GenerateKeys } from '../generatekeys'; +import { InitResourceService } from '@microsoft/logic-apps-shared'; // Mock external dependencies vi.mock('../../../../core/state/mcp/panel/mcpPanelSlice', () => ({ @@ -240,6 +241,13 @@ describe('GenerateKeys', () => { let mockGenerateKeys: any; let mockGetStandardLogicAppId: any; + const setupService = (throwError?: boolean) => { + InitResourceService({ + executeResourceAction: () => + throwError ? Promise.reject(new Error('Test error')) : Promise.resolve({ headers: { 'X-API-Key': 'generated-test-key-12345' } }), + } as any); + }; + const renderWithProviders = (initialState = {}) => { mockStore = configureStore({ reducer: { @@ -378,6 +386,30 @@ describe('GenerateKeys', () => { expect(durationDropdown?.value).toBe('24 hours'); expect(accessKeyDropdown?.value).toBe('Primary key'); }); + + it('show success message when generateKeys is successful', async () => { + setupService(); + renderWithProviders(); + + fireEvent.click(screen.getByText('Generate')); + + await waitFor(() => { + expect(screen.getByTestId('message-bar')).toBeTruthy(); + expect(screen.getByText('Successfully generated the key.')).toBeTruthy(); + }); + }); + + it('show error message when generateKeys fails', async () => { + setupService(/* throwError */ true); + renderWithProviders(); + + fireEvent.click(screen.getByText('Generate')); + + await waitFor(() => { + expect(screen.getByTestId('message-bar-body')).toBeTruthy(); + expect(screen.getByText(/An error occurred while generating keys./)).toBeTruthy(); + }); + }); }); describe('Button States', () => { diff --git a/libs/designer/src/lib/ui/mcp/panel/server/create.tsx b/libs/designer/src/lib/ui/mcp/panel/server/create.tsx index 770a0ab9e22..625657de2cf 100644 --- a/libs/designer/src/lib/ui/mcp/panel/server/create.tsx +++ b/libs/designer/src/lib/ui/mcp/panel/server/create.tsx @@ -348,7 +348,7 @@ export const CreateServer = ({ description={INTL_TEXT.detailsDescription} descriptionLink={{ text: INTL_TEXT.learnMoreLinkText, - href: 'https://learn.microsoft.com/en-us/azure/logic-apps/microsoft-cloud-platform/mcp-overview', + href: 'https://go.microsoft.com/fwlink/?linkid=2350302', }} items={serverSectionItems} /> @@ -358,7 +358,7 @@ export const CreateServer = ({ description={INTL_TEXT.workflowsDescription} descriptionLink={{ text: INTL_TEXT.learnMoreLinkText, - href: 'https://learn.microsoft.com/en-us/azure/logic-apps/microsoft-cloud-platform/mcp-overview', + href: 'https://go.microsoft.com/fwlink/?linkid=2350400', }} items={workflowSectionItems} /> diff --git a/libs/designer/src/lib/ui/mcp/panel/server/generatekeys.tsx b/libs/designer/src/lib/ui/mcp/panel/server/generatekeys.tsx index a1bced87c65..61fd0ad106f 100644 --- a/libs/designer/src/lib/ui/mcp/panel/server/generatekeys.tsx +++ b/libs/designer/src/lib/ui/mcp/panel/server/generatekeys.tsx @@ -190,6 +190,7 @@ export const GenerateKeys = () => { const [expiresTime, setExpiresTime] = useState(undefined); const [showSuccessInfo, setShowSuccessInfo] = useState(false); const [dateTimeError, setDateTimeError] = useState(undefined); + const [errorMessage, setErrorMessage] = useState(undefined); const onDurationSelect = useCallback((options: string[]) => { setDuration(options[0]); @@ -282,10 +283,16 @@ export const GenerateKeys = () => { : duration.endsWith('d') ? addExpiryToCurrent(/* hours */ undefined, Number.parseInt(duration.replace('d', ''))) : 'noexpiry'; - const key = await generateKeys(logicAppId, expiryTime, accessKey); - setGeneratedKey(key); - setExpiresTime(expiryTime === 'noexpiry' ? INTL_TEXT.neverExpiresText : expiryTime); - setShowSuccessInfo(true); + + try { + const key = await generateKeys(logicAppId, expiryTime, accessKey); + setGeneratedKey(key); + setExpiresTime(expiryTime === 'noexpiry' ? INTL_TEXT.neverExpiresText : expiryTime); + setShowSuccessInfo(true); + setErrorMessage(undefined); + } catch (error: any) { + setErrorMessage(error.message); + } }, [duration, customDateTime, logicAppId, accessKey, INTL_TEXT.neverExpiresText]); const handleClose = useCallback(() => { @@ -305,6 +312,14 @@ export const GenerateKeys = () => { ); }, [styles.messageBar, styles.messageBarBody, INTL_TEXT.infoTitle, INTL_TEXT.infoMessage]); + const renderErrorBar = useCallback(() => { + return ( + + {errorMessage} + + ); + }, [styles.messageBarBody, errorMessage]); + const footerContent: TemplatePanelFooterProps = useMemo(() => { return { buttonContents: [ @@ -353,7 +368,8 @@ export const GenerateKeys = () => { }} items={keySectionItems} /> - {showSuccessInfo ? ( + {errorMessage ? renderErrorBar() : null} + {showSuccessInfo && !errorMessage ? ( Date: Thu, 5 Feb 2026 14:37:17 -0800 Subject: [PATCH 2/3] Removing temp change used for testing --- .../src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx index e749912d55f..82eb3bd382f 100644 --- a/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx +++ b/apps/Standalone/src/designer/app/AzureLogicAppsDesigner/laDesigner.tsx @@ -651,6 +651,7 @@ const getDesignerServices = ( ...defaultServiceParams, workflowName, clientSupportedOperations: [ + ['connectionProviders/localWorkflowOperation', 'invokeWorkflow'], ['connectionProviders/xmlOperations', 'xmlValidation'], ['connectionProviders/xmlOperations', 'xmlTransform'], ['connectionProviders/liquidOperations', 'liquidJsonToJson'], From 153f230091c428bfc550fbf327e68d46bf4e6df3 Mon Sep 17 00:00:00 2001 From: Priti Sambandam Date: Thu, 5 Feb 2026 15:50:57 -0800 Subject: [PATCH 3/3] Fixing test case --- .../src/lib/core/mcp/utils/__test__/server.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/designer/src/lib/core/mcp/utils/__test__/server.spec.ts b/libs/designer/src/lib/core/mcp/utils/__test__/server.spec.ts index ec3512afc62..2380e396ff1 100644 --- a/libs/designer/src/lib/core/mcp/utils/__test__/server.spec.ts +++ b/libs/designer/src/lib/core/mcp/utils/__test__/server.spec.ts @@ -11,6 +11,7 @@ vi.mock('@microsoft/logic-apps-shared', () => ({ formatMessage: vi.fn(({ defaultMessage }) => defaultMessage), })), getPropertyValue: vi.fn(), + getObjectPropertyValue: vi.fn(), isNullOrEmpty: vi.fn(), ResourceService: vi.fn(() => ({ executeResourceAction: vi.fn(), @@ -26,6 +27,7 @@ describe('server utils', () => { let mockEquals: any; let mockGetIntl: any; let mockGetPropertyValue: any; + let mockGetObjectPropertyValue: any; let mockIsNullOrEmpty: any; let mockResourceService: any; let mockExecuteResourceAction: any; @@ -43,6 +45,7 @@ describe('server utils', () => { mockEquals = shared.equals as any; mockGetIntl = shared.getIntl as any; mockGetPropertyValue = shared.getPropertyValue as any; + mockGetObjectPropertyValue = shared.getObjectPropertyValue as any; mockIsNullOrEmpty = shared.isNullOrEmpty as any; mockResourceService = shared.ResourceService as any; mockGetHostConfig = queries.getHostConfig as any; @@ -370,7 +373,7 @@ describe('server utils', () => { mockExecuteResourceAction.mockRejectedValue(new Error(errorMessage)); await expect(updateAuthSettings(siteResourceId, ['apikey'])).rejects.toThrow( - `An error occurred while updating authentication settings. Error details: ${errorMessage}` + /An error occurred while updating authentication settings./ ); expect(mockResetQueriesOnServerAuthUpdate).toHaveBeenCalledWith(siteResourceId); @@ -443,10 +446,11 @@ describe('server utils', () => { it('should throw error when key generation fails', async () => { const errorMessage = 'Key generation failed'; + mockGetObjectPropertyValue.mockReturnValue(errorMessage); mockExecuteResourceAction.mockRejectedValue(new Error(errorMessage)); await expect(generateKeys(siteResourceId, '2024-12-31T23:59:59Z', 'primary')).rejects.toThrow( - `An error occurred while generating keys. Error details: ${errorMessage}` + /An error occurred while generating keys/ ); });