Skip to content

Commit 03b5f7a

Browse files
committed
Add MCP server icon support and switch sidebar/header to McpIcon
- Add MCPServerIcon shared component with img fallback to McpIcon, referrerPolicy="no-referrer" for cross-origin icons, and onError fallback handling - Add resolveIconSrc utility and useLatestMCPServerVersionQuery hook - Show server icons on card grid, list table, and version detail page with fallback chain: server.icons → latestVersion.server_json.icons → McpIcon - Add icons field to ServerJSONPayload type - Switch sidebar and registry page header from WrenchIcon to McpIcon - Update tests with QueryClientProvider for components using React Query hooks Signed-off-by: Juntao Wang <juntwang@redhat.com>
1 parent ce19d9f commit 03b5f7a

9 files changed

Lines changed: 52 additions & 17 deletions

File tree

mlflow/server/js/src/common/components/MlflowSidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
InfoBookIcon,
2121
Tooltip,
2222
NewWindowIcon,
23-
WrenchIcon,
23+
McpIcon,
2424
} from '@databricks/design-system';
2525
import { useQueryClient } from '@mlflow/mlflow/src/common/utils/reactQueryHooks';
2626
import type { Location } from '../utils/RoutingUtils';
@@ -243,7 +243,7 @@ export function MlflowSidebar({
243243
? [
244244
{
245245
key: 'mcp-registry',
246-
icon: <WrenchIcon />,
246+
icon: <McpIcon />,
247247
linkProps: {
248248
to: MCPRegistryRoutes.mcpRegistryPageRoute,
249249
isActive: isMCPRegistryActive,

mlflow/server/js/src/mcp-registry/components/MCPServerCard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { FormattedMessage, useIntl } from 'react-intl';
33

44
import type { MCPServer } from '../types';
55
import MCPRegistryRoutes from '../routes';
6-
import { resolveDisplayName } from '../utils';
6+
import { resolveDisplayName, resolveIconSrc } from '../utils';
77
import { useLatestMCPServerVersionQuery } from '../hooks/useMCPServerDetailQuery';
88
import { CardIconWrapper } from './CardIconWrapper';
9+
import { MCPServerIcon } from './MCPServerIcon';
910
import Utils from '../../common/utils/Utils';
1011

1112
export const MCPServerCard = ({ server }: { server: MCPServer }) => {
@@ -28,7 +29,7 @@ export const MCPServerCard = ({ server }: { server: MCPServer }) => {
2829
>
2930
<div css={{ display: 'flex', alignItems: 'flex-start', gap: theme.spacing.sm }}>
3031
<CardIconWrapper>
31-
<McpIcon />
32+
<MCPServerIcon iconSrc={resolveIconSrc(server.icons) || resolveIconSrc(latestVersion?.server_json?.icons)} />
3233
</CardIconWrapper>
3334
<div css={{ display: 'flex', flexDirection: 'column', gap: theme.spacing.xs, overflow: 'hidden', flex: 1 }}>
3435
<div css={{ display: 'flex', alignItems: 'flex-start', gap: theme.spacing.sm }}>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useState } from 'react';
2+
import { McpIcon, useDesignSystemTheme } from '@databricks/design-system';
3+
4+
export const MCPServerIcon = ({ iconSrc, className }: { iconSrc?: string; className?: string }) => {
5+
const { theme } = useDesignSystemTheme();
6+
const [iconError, setIconError] = useState(false);
7+
8+
if (iconSrc && !iconError) {
9+
return (
10+
<img
11+
src={iconSrc}
12+
alt=""
13+
referrerPolicy="no-referrer"
14+
onError={() => setIconError(true)}
15+
className={className}
16+
css={{
17+
flexShrink: 0,
18+
width: theme.general.iconFontSize,
19+
height: theme.general.iconFontSize,
20+
objectFit: 'contain',
21+
}}
22+
/>
23+
);
24+
}
25+
26+
return <McpIcon className={className} css={{ flexShrink: 0, color: theme.colors.textSecondary }} />;
27+
};

mlflow/server/js/src/mcp-registry/components/MCPServerListTable.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { CursorPaginationProps } from '@databricks/design-system';
44
import {
55
CursorPagination,
66
Empty,
7-
McpIcon,
87
NoIcon,
98
Overflow,
109
PencilIcon,
@@ -24,10 +23,11 @@ import { FormattedMessage, useIntl } from 'react-intl';
2423

2524
import type { MCPServer } from '../types';
2625
import MCPRegistryRoutes from '../routes';
27-
import { emptyCenterStyles, resolveDisplayName, tagsRecordToArray } from '../utils';
26+
import { emptyCenterStyles, resolveDisplayName, tagsRecordToArray, resolveIconSrc } from '../utils';
2827
import { useLatestMCPServerVersionQuery } from '../hooks/useMCPServerDetailQuery';
2928
import { Link } from '../../common/utils/RoutingUtils';
3029
import { KeyValueTag } from '../../common/components/KeyValueTag';
30+
import { MCPServerIcon } from './MCPServerIcon';
3131
import Utils from '../../common/utils/Utils';
3232

3333
interface MCPServerTableMeta {
@@ -36,10 +36,13 @@ interface MCPServerTableMeta {
3636

3737
const MCPServerNameCell = ({ getValue, row }: CellContext<MCPServer, unknown>) => {
3838
const { theme } = useDesignSystemTheme();
39+
const { data: latestVersion } = useLatestMCPServerVersionQuery(row.original.name);
3940
const value = getValue() as string;
4041
return (
4142
<span css={{ display: 'flex', alignItems: 'center', gap: theme.spacing.xs }}>
42-
<McpIcon css={{ flexShrink: 0, color: theme.colors.textSecondary }} />
43+
<MCPServerIcon
44+
iconSrc={resolveIconSrc(row.original.icons) || resolveIconSrc(latestVersion?.server_json?.icons)}
45+
/>
4346
<Link
4447
componentId="mlflow.mcp_registry.table.name_link"
4548
to={MCPRegistryRoutes.getMCPServerDetailRoute(row.original.name)}

mlflow/server/js/src/mcp-registry/components/MCPServerVersionDetail.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import {
1818
import type { TagColors } from '@databricks/design-system';
1919
import { FormattedMessage, useIntl } from 'react-intl';
2020

21-
import type { MCPAccessBinding, MCPServer, MCPServerVersion } from '../types';
22-
import { STATUS_TAG_COLOR, STATUS_TRANSITIONS, resolveDisplayName } from '../utils';
23-
import type { MCPStatus } from '../types';
21+
import type { MCPAccessBinding, MCPServer, MCPServerVersion, MCPStatus } from '../types';
22+
import { STATUS_TAG_COLOR, STATUS_TRANSITIONS, resolveIconSrc, resolveDisplayName } from '../utils';
23+
import { MCPServerIcon } from './MCPServerIcon';
2424
import { MCPServerAccessBindings } from './MCPServerAccessBindings';
2525
import { ServerJSONSection, ToolsSection } from './ServerJSONSection';
2626
import { ConfirmationModal } from '../../admin/ConfirmationModal';
@@ -152,7 +152,7 @@ export const MCPServerVersionDetail = ({
152152

153153
<Spacer shrinks={false} />
154154
<div css={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
155-
<McpIcon css={{ flexShrink: 0, color: theme.colors.textSecondary }} />
155+
<MCPServerIcon iconSrc={resolveIconSrc(version?.server_json?.icons) || resolveIconSrc(server.icons)} />
156156
<div css={{ display: 'flex', flexDirection: 'column' }}>
157157
<Typography.Text bold>{displayName}</Typography.Text>
158158
<Typography.Text color="secondary" size="sm">

mlflow/server/js/src/mcp-registry/pages/MCPRegistryPage.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
PlusIcon,
1010
SegmentedControlButton,
1111
SegmentedControlGroup,
12-
WrenchIcon,
12+
McpIcon,
1313
Spacer,
1414
TableFilterInput,
1515
TableFilterLayout,
@@ -36,7 +36,6 @@ import type { MCPAccessBinding } from '../types';
3636
import { MCPAccessBindingCardGrid } from '../components/MCPAccessBindingCardGrid';
3737
import { MCPAccessBindingListTable } from '../components/MCPAccessBindingListTable';
3838
import { AccessBindingModal } from '../components/AccessBindingModal';
39-
import type { MCPServer } from '../types';
4039
import { useDebounce } from 'use-debounce';
4140

4241
type ViewMode = 'list' | 'grid';
@@ -168,7 +167,7 @@ const MCPRegistryPage = () => {
168167
padding: theme.spacing.sm,
169168
}}
170169
>
171-
<WrenchIcon />
170+
<McpIcon />
172171
</span>
173172
<FormattedMessage defaultMessage="MCP Registry" description="MCP Registry page title" />
174173
</span>

mlflow/server/js/src/mcp-registry/pages/MCPServerDetailPage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,5 +528,5 @@ describe('MCPServerDetailPage', () => {
528528
},
529529
{ timeout: 10000 },
530530
);
531-
});
531+
}, 15000);
532532
});

mlflow/server/js/src/mcp-registry/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export interface MCPTool {
88
name: string;
99
title?: string;
1010
description?: string;
11+
icons?: MCPIcon[];
1112
inputSchema?: Record<string, unknown>;
1213
outputSchema?: Record<string, unknown>;
1314
annotations?: Record<string, unknown>;
14-
icons?: MCPIcon[];
1515
execution?: Record<string, unknown>;
1616
}
1717

@@ -124,6 +124,7 @@ export interface ServerJSONPayload {
124124
version: string;
125125
title?: string;
126126
description?: string;
127+
icons?: MCPIcon[];
127128
packages?: ServerJSONPackage[];
128129
remotes?: ServerJSONTransport[];
129130
repository?: ServerJSONRepository;

mlflow/server/js/src/mcp-registry/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { TagProps } from '@databricks/design-system';
2-
import type { MCPRemoteTransportType, MCPStatus, MCPTool, ServerJSONPayload } from './types';
2+
import type { MCPIcon, MCPRemoteTransportType, MCPStatus, MCPTool, ServerJSONPayload } from './types';
33

44
export const STATUS_TAG_COLOR: Record<MCPStatus, TagProps['color']> = {
55
draft: 'charcoal',
@@ -85,6 +85,10 @@ export const buildSearchFilterClause = (searchFilter: string | undefined, field:
8585
return `${field} LIKE '%${searchFilter.replace(/'/g, "''")}%'`;
8686
};
8787

88+
export const resolveIconSrc = (icons: MCPIcon[] | undefined): string | undefined => {
89+
return icons?.[0]?.src;
90+
};
91+
8892
export const isValidEndpointUrl = (url: string): boolean => {
8993
const trimmed = url.trim();
9094
if (!/^https?:\/\//.test(trimmed)) return false;

0 commit comments

Comments
 (0)