Skip to content

Commit 5f4c34d

Browse files
preetriti1Priti Sambandamccastrotrejo
authored
fix(mcp): update MCP Server UI copy, add dark-mode icon, add copy-URL action and tests (#8755)
* fix(mcp): update MCP server UI copy/text, add dark-mode icon support, copy-URL action, and tests (#8752) fix(mcp): Updating styling and content changes from review and minor design updates Co-authored-by: Priti Sambandam <psamband@microsoft.com> * chore(test): Remove obsolete getAgentBaseUrl tests from config-parser.spec.ts (#8736) fix: remove getAgentBaseUrl tests from config-parser.spec.ts --------- Co-authored-by: Priti Sambandam <psamband@microsoft.com> Co-authored-by: Carlos Castro Trejo <102700317+ccastrotrejo@users.noreply.github.com>
1 parent 8c129eb commit 5f4c34d

File tree

30 files changed

+522
-247
lines changed

30 files changed

+522
-247
lines changed

Localize/lang/strings.json

Lines changed: 46 additions & 42 deletions
Large diffs are not rendered by default.

apps/Standalone/src/designer/app/AzureLogicAppsDesigner/Services/WorkflowAndArtifacts.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ export const updateMcpServers = async (
848848
let updatedHostConfig: any;
849849
const queryClient = getReactQueryClient();
850850

851-
if (!areAllServersDeleted && hostConfig?.extensions?.workflow?.McpServerEndpoints?.enabled === false) {
851+
if (!areAllServersDeleted && !hostConfig?.extensions?.workflow?.McpServerEndpoints?.enabled) {
852852
updatedHostConfig = {
853853
...(hostConfig.properties ?? {}),
854854
extensions: {

apps/Standalone/src/mcp/app/McpServer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const McpServer = () => {
5252
<div className={styles.wizardContainer}>
5353
<div className={styles.wizardContent}>
5454
<div className={styles.wizardWrapper}>
55-
<McpServerDataProvider resourceDetails={resourceDetails} services={services}>
55+
<McpServerDataProvider resourceDetails={resourceDetails} services={services} isDarkMode={theme === 'dark'}>
5656
<McpServersWizard
5757
onUpdateServers={handleUpdateServers}
5858
onOpenWorkflow={handleOpenWorkflow}

apps/iframe-app/src/lib/utils/__tests__/config-parser.spec.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2-
import { parseIframeConfig, getAgentBaseUrl, parseIdentityProviders } from '../config-parser';
2+
import { parseIframeConfig, parseIdentityProviders } from '../config-parser';
33

44
describe('config-parser', () => {
55
const originalLocation = window.location;
@@ -180,32 +180,6 @@ describe('config-parser', () => {
180180
});
181181
});
182182

183-
describe('getAgentBaseUrl', () => {
184-
it('should remove .well-known/agent-card.json from URL', () => {
185-
const result = getAgentBaseUrl('https://example.com/api/agents/myAgent/.well-known/agent-card.json');
186-
187-
expect(result).toBe('https://example.com/api/agents/myAgent');
188-
});
189-
190-
it('should preserve query params when removing agent-card.json', () => {
191-
const result = getAgentBaseUrl('https://example.com/api/agents/myAgent/.well-known/agent-card.json?param=value');
192-
193-
expect(result).toBe('https://example.com/api/agents/myAgent?param=value');
194-
});
195-
196-
it('should return empty string for undefined input', () => {
197-
const result = getAgentBaseUrl(undefined);
198-
199-
expect(result).toBe('');
200-
});
201-
202-
it('should return URL unchanged if no agent-card.json pattern', () => {
203-
const result = getAgentBaseUrl('https://example.com/api/agents/myAgent');
204-
205-
expect(result).toBe('https://example.com/api/agents/myAgent');
206-
});
207-
});
208-
209183
describe('parseIdentityProviders', () => {
210184
afterEach(() => {
211185
delete window.IDENTITY_PROVIDERS;

libs/designer-ui/src/lib/templates/fieldsectionitem.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,44 +12,53 @@ import {
1212
Switch,
1313
Text,
1414
Textarea,
15+
mergeClasses,
1516
} from '@fluentui/react-components';
1617
import type { BaseFieldItem, TemplatesSectionItem } from './templatesSectionModel';
1718
import { useTemplatesStyles } from './styles';
1819

1920
interface FieldSectionItemProps {
2021
item: TemplatesSectionItem;
22+
cssOverrides?: Record<string, string>;
2123
}
2224

23-
export const FieldSectionItem = ({ item }: FieldSectionItemProps) => {
25+
export const FieldSectionItem = ({ item, cssOverrides }: FieldSectionItemProps) => {
2426
const styles = useTemplatesStyles();
2527

2628
return (
27-
<div className={styles.fieldSectionItem}>
28-
<SectionLabel item={item} />
29+
<div className={mergeClasses(styles.fieldSectionItem, cssOverrides?.['sectionItem'])}>
30+
<SectionLabel item={item} cssOverrides={cssOverrides} />
2931
<div className={styles.fieldSectionItemValue}>
3032
<SectionItemInner item={item} />
3133
</div>
3234
</div>
3335
);
3436
};
3537

36-
const SectionLabel = ({ item }: { item: TemplatesSectionItem }) => {
38+
const SectionLabel = ({ item, cssOverrides }: { item: TemplatesSectionItem; cssOverrides?: Record<string, string> }) => {
3739
const styles = useTemplatesStyles();
3840

3941
if (!item.label) {
4042
return null;
4143
}
4244

4345
if (typeof item.label !== 'string') {
44-
return <div className={styles.fieldSectionItemLabel}>{item.label}</div>;
46+
return <div className={mergeClasses(styles.fieldSectionItemLabel, cssOverrides?.['itemLabel'])}>{item.label}</div>;
4547
}
4648

4749
return item.description ? (
48-
<InfoLabel info={item.description} className={styles.fieldSectionItemLabel} required={(item as BaseFieldItem)?.required ?? false}>
50+
<InfoLabel
51+
info={item.description}
52+
className={mergeClasses(styles.fieldSectionItemLabel, cssOverrides?.['itemLabel'])}
53+
required={(item as BaseFieldItem)?.required ?? false}
54+
>
4955
{item.label}
5056
</InfoLabel>
5157
) : (
52-
<Label className={styles.fieldSectionItemLabel} required={(item as BaseFieldItem)?.required ?? false}>
58+
<Label
59+
className={mergeClasses(styles.fieldSectionItemLabel, cssOverrides?.['itemLabel'])}
60+
required={(item as BaseFieldItem)?.required ?? false}
61+
>
5362
{item.label}
5463
</Label>
5564
);

libs/designer-ui/src/lib/templates/templatesSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const TemplatesSection = ({
3737
<div className={css('msla-templates-section-items', cssOverrides?.['sectionItems'])}>
3838
{items
3939
? items.map((item, index) => {
40-
return <FieldSectionItem key={index} item={item} />;
40+
return <FieldSectionItem key={index} item={item} cssOverrides={cssOverrides} />;
4141
})
4242
: children}
4343
</div>
Lines changed: 16 additions & 0 deletions
Loading
Lines changed: 16 additions & 0 deletions
Loading

libs/designer/src/lib/core/mcp/McpServerDataProvider.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ import type { AppDispatch, RootState } from '../state/mcp/store';
44
import { setInitialData } from '../state/mcp/resourceSlice';
55
import { initializeMcpData } from '../actions/bjsworkflow/mcp';
66
import type { McpDataProviderProps } from './McpDataProvider';
7+
import { setDarkMode } from '../state/mcp/mcpOptions/mcpOptionsSlice';
78

8-
export const McpServerDataProvider = ({ resourceDetails, services, children }: McpDataProviderProps) => {
9+
export const McpServerDataProvider = ({
10+
resourceDetails,
11+
services,
12+
children,
13+
isDarkMode,
14+
}: McpDataProviderProps & { isDarkMode: boolean }) => {
915
const dispatch = useDispatch<AppDispatch>();
1016

1117
const { servicesInitialized } = useSelector((state: RootState) => ({
@@ -29,5 +35,11 @@ export const McpServerDataProvider = ({ resourceDetails, services, children }: M
2935
);
3036
}, [dispatch, resourceDetails]);
3137

38+
useEffect(() => {
39+
if (isDarkMode !== undefined) {
40+
dispatch(setDarkMode(isDarkMode));
41+
}
42+
}, [dispatch, isDarkMode]);
43+
3244
return <>{children}</>;
3345
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { isHttpRequestTrigger } from '../helper';
3+
4+
describe('isHttpRequestTrigger', () => {
5+
describe('when trigger is an HTTP request trigger', () => {
6+
it('should return true for request type with Http kind', () => {
7+
const trigger = {
8+
type: 'Request',
9+
kind: 'Http',
10+
inputs: {},
11+
};
12+
expect(isHttpRequestTrigger(trigger)).toBe(true);
13+
});
14+
15+
it('should return true for request type without kind (defaults to Http)', () => {
16+
const trigger = {
17+
type: 'Request',
18+
inputs: {},
19+
};
20+
expect(isHttpRequestTrigger(trigger)).toBe(true);
21+
});
22+
23+
it('should return true for request type with undefined kind', () => {
24+
const trigger = {
25+
type: 'Request',
26+
kind: undefined,
27+
inputs: {},
28+
};
29+
expect(isHttpRequestTrigger(trigger)).toBe(true);
30+
});
31+
32+
it('should be case-insensitive for type', () => {
33+
const trigger = {
34+
type: 'Request',
35+
kind: 'Http',
36+
inputs: {},
37+
};
38+
expect(isHttpRequestTrigger(trigger)).toBe(true);
39+
});
40+
41+
it('should be case-insensitive for kind', () => {
42+
const trigger = {
43+
type: 'Request',
44+
kind: 'HTTP',
45+
inputs: {},
46+
};
47+
expect(isHttpRequestTrigger(trigger)).toBe(true);
48+
});
49+
50+
it('should be case-insensitive for both type and kind', () => {
51+
const trigger = {
52+
type: 'REQUEST',
53+
kind: 'http',
54+
inputs: {},
55+
};
56+
expect(isHttpRequestTrigger(trigger)).toBe(true);
57+
});
58+
});
59+
60+
describe('when trigger is not an HTTP request trigger', () => {
61+
it('should return false for non-request type', () => {
62+
const trigger = {
63+
type: 'recurrence',
64+
inputs: {},
65+
};
66+
expect(isHttpRequestTrigger(trigger)).toBe(false);
67+
});
68+
69+
it('should return false for request type with non-Http kind', () => {
70+
const trigger = {
71+
type: 'Request',
72+
kind: 'PowerAppV2',
73+
inputs: {},
74+
};
75+
expect(isHttpRequestTrigger(trigger)).toBe(false);
76+
});
77+
78+
it('should return false for http type (not request)', () => {
79+
const trigger = {
80+
type: 'http',
81+
inputs: {},
82+
};
83+
expect(isHttpRequestTrigger(trigger)).toBe(false);
84+
});
85+
86+
it('should return false for ApiConnection type', () => {
87+
const trigger = {
88+
type: 'ApiConnection',
89+
inputs: {},
90+
};
91+
expect(isHttpRequestTrigger(trigger)).toBe(false);
92+
});
93+
94+
it('should return false for request type with Button kind', () => {
95+
const trigger = {
96+
type: 'Request',
97+
kind: 'Button',
98+
inputs: {},
99+
};
100+
expect(isHttpRequestTrigger(trigger)).toBe(false);
101+
});
102+
});
103+
});

0 commit comments

Comments
 (0)