/** * Copyright (c) 2026-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useCallback, useEffect, useRef } from 'react'; import { observer } from 'mobx-react-lite'; import { flowResult } from 'mobx'; import { useAuth } from 'react-oidc-context'; import { useParams } from '@finos/legend-application/browser'; import { guaranteeNonNullable } from '@finos/legend-shared'; import { ArrowLeftIcon, ChevronDownIcon, clsx, CopyIcon, MarkdownTextViewer, } from '@finos/legend-art'; import { Accordion, AccordionDetails, AccordionSummary, Chip, CircularProgress, Container, IconButton, Tooltip, Typography, } from '@mui/material'; import type { McpServer, McpServerTool, } from '@finos/legend-server-marketplace'; import { LegendMarketplacePage } from '../LegendMarketplacePage.js'; import { useIntelligenceCatalogStore } from '../../application/providers/IntelligenceCatalogStoreProvider.js'; import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js'; import { type McpServerPathParams, LEGEND_MARKETPLACE_ROUTE_PATTERN, LEGEND_MARKETPLACE_ROUTE_PATTERN_TOKEN, } from '../../__lib__/LegendMarketplaceNavigation.js'; import { formatMcpTimestamp, hasMeaningfulMcpDescription, NO_DESCRIPTION_PLACEHOLDER, NO_VALUE_PLACEHOLDER, } from '../../stores/intelligence/IntelligenceCatalogUtils.js'; import { parseMcpToolDocumentation } from '../../stores/intelligence/McpToolDocumentation.js'; import { LegendMarketplaceTelemetryHelper } from '../../__lib__/LegendMarketplaceTelemetryHelper.js'; enum MCP_SUPPORT_LINK_TYPE { DOCUMENTATION = 'documentation', DISTRIBUTION_LIST = 'distribution-list', } const displayFieldValue = (value: string | undefined): string => value ?? NO_VALUE_PLACEHOLDER; type McpServerViewerAccent = | 'connection' | 'security' | 'ownership' | 'registry'; const McpServerViewerPanel = (props: { title: string; accent: McpServerViewerAccent; children: React.ReactNode; }): React.ReactNode => { const { title, accent, children } = props; return (

{title}

{children}
); }; const McpServerViewerField = (props: { label: string; children: React.ReactNode; }): React.ReactNode => { const { label, children } = props; return ( <>
{label}
{children}
); }; const McpServerViewerSection = (props: { title: string; count?: number | undefined; scrollable: boolean; children: React.ReactNode; }): React.ReactNode => { const { title, count, scrollable, children } = props; return (
{title} {count !== undefined && ( {count} )}
{children}
); }; const McpServerViewerChipList = (props: { values: string[] | undefined; }): React.ReactNode => { const { values } = props; if (values === undefined || values.length === 0) { return NO_VALUE_PLACEHOLDER; } return ( {values.map((value) => ( ))} ); }; const McpServerCopyButton = observer( (props: { mcpServerName: string; label: string; value: string; }): React.ReactNode => { const { mcpServerName, label, value } = props; const applicationStore = useLegendMarketplaceBaseStore().applicationStore; const copyValue = (event: React.MouseEvent): void => { event.stopPropagation(); LegendMarketplaceTelemetryHelper.logEvent_ClickMcpServerCopy( applicationStore.telemetryService, mcpServerName, label, ); applicationStore.clipboardService .copyTextToClipboard(value) .then(() => applicationStore.notificationService.notifySuccess( `Copied ${label} to clipboard`, ), ) .catch(applicationStore.alertUnhandledError); }; return ( ); }, ); const McpServerDistributionList = observer( (props: { mcpServerName: string; address: string | undefined; }): React.ReactNode => { const { mcpServerName, address } = props; const applicationStore = useLegendMarketplaceBaseStore().applicationStore; if (address === undefined) { return NO_VALUE_PLACEHOLDER; } return ( LegendMarketplaceTelemetryHelper.logEvent_ClickMcpServerSupportLink( applicationStore.telemetryService, mcpServerName, MCP_SUPPORT_LINK_TYPE.DISTRIBUTION_LIST, ) } > {address} ); }, ); const McpServerViewerHeader = observer( (props: { mcpServer: McpServer }): React.ReactNode => { const { mcpServer } = props; const intelligenceCatalogStore = useIntelligenceCatalogStore(); return (
{intelligenceCatalogStore.getVendorForServer(mcpServer)} {mcpServer.displayName}
{mcpServer.active && ( )} {mcpServer.requireApproval && ( )} {mcpServer.category?.map((category) => ( ))}

{hasMeaningfulMcpDescription(mcpServer) ? mcpServer.description : NO_DESCRIPTION_PLACEHOLDER}

); }, ); const McpServerConnectionPanel = (props: { mcpServer: McpServer; }): React.ReactNode => { const { mcpServer } = props; return ( {mcpServer.url} {mcpServer.type} {displayFieldValue(mcpServer.tokenType)} ); }; const McpServerClassificationPanel = (props: { mcpServer: McpServer; }): React.ReactNode => { const { mcpServer } = props; return ( {displayFieldValue(mcpServer.securityDetail?.dataPrivacyClassification)} {displayFieldValue( mcpServer.securityDetail?.dataSensitivityClassification, )} ); }; const McpServerOwnershipPanel = (props: { mcpServer: McpServer; }): React.ReactNode => { const { mcpServer } = props; return ( {displayFieldValue(mcpServer.ownershipInfo?.ownerDid)} ); }; const McpServerRegistrationPanel = (props: { mcpServer: McpServer; }): React.ReactNode => { const { mcpServer } = props; return ( {mcpServer.name} {displayFieldValue(formatMcpTimestamp(mcpServer.createdAt))} {displayFieldValue(formatMcpTimestamp(mcpServer.updatedAt))} ); }; const McpServerSampleQuestionList = (props: { mcpServerName: string; questions: string[]; }): React.ReactNode => { const { mcpServerName, questions } = props; return ( ); }; const McpServerToolAccordion = (props: { mcpServerName: string; tool: McpServerTool; }): React.ReactNode => { const { mcpServerName, tool } = props; const applicationStore = useLegendMarketplaceBaseStore().applicationStore; const { servicePattern, documentation, groundingRules } = parseMcpToolDocumentation(tool.description ?? ''); return ( { if (expanded) { LegendMarketplaceTelemetryHelper.logEvent_ExpandMcpServerTool( applicationStore.telemetryService, mcpServerName, tool.name, ); } }} > } className="marketplace-mcp-server-viewer__tool-summary" > {tool.title ?? tool.name} {servicePattern !== undefined && ( )} {groundingRules !== undefined && ( { if (expanded) { LegendMarketplaceTelemetryHelper.logEvent_ExpandMcpServerGroundingRules( applicationStore.telemetryService, mcpServerName, tool.name, ); } }} > } className="marketplace-mcp-server-viewer__tool-grounding-summary" > LLM grounding rules )} ); }; const McpServerSupportSection = observer( (props: { mcpServer: McpServer }): React.ReactNode => { const { mcpServer } = props; const applicationStore = useLegendMarketplaceBaseStore().applicationStore; const supportInfo = mcpServer.supportInfo; if (supportInfo === undefined || supportInfo.length === 0) { return null; } return (
{supportInfo.map((support) => ( {support.message ?? NO_VALUE_PLACEHOLDER} {support.documentationLink !== undefined && ( LegendMarketplaceTelemetryHelper.logEvent_ClickMcpServerSupportLink( applicationStore.telemetryService, mcpServer.name, MCP_SUPPORT_LINK_TYPE.DOCUMENTATION, ) } > Documentation )} ))}
); }, ); const McpServerToolsSection = observer( (props: { mcpServer: McpServer }): React.ReactNode => { const { mcpServer } = props; const intelligenceCatalogStore = useIntelligenceCatalogStore(); const tools = intelligenceCatalogStore.toolsByServerName.get( mcpServer.name, ); if (intelligenceCatalogStore.isFetchingToolsFor(mcpServer.name)) { return ( ); } return ( {tools === undefined || tools.tools.length === 0 ? (
This server exposes no tools
) : ( tools.tools.map((tool) => ( )) )}
); }, ); const McpServerViewerContent = observer( (props: { mcpServer: McpServer }): React.ReactNode => { const { mcpServer } = props; const intelligenceCatalogStore = useIntelligenceCatalogStore(); const applicationStore = useLegendMarketplaceBaseStore().applicationStore; const auth = useAuth(); const token = auth.user?.access_token; const tokenRef = useRef(token); tokenRef.current = token; // the registry does not guarantee distinct questions, and a repeated one would // both render twice and collide as a React key const sampleQuestions = mcpServer.sampleQuestions ? Array.from(new Set(mcpServer.sampleQuestions)) : undefined; useEffect(() => { const currentToken = tokenRef.current; if (currentToken) { flowResult( intelligenceCatalogStore.fetchMcpServerTools( mcpServer.name, currentToken, ), ).catch(applicationStore.alertUnhandledError); } }, [intelligenceCatalogStore, mcpServer.name, token, applicationStore]); return (
{sampleQuestions !== undefined && sampleQuestions.length > 0 && ( )}
); }, ); export const McpServerViewer = observer(() => { const intelligenceCatalogStore = useIntelligenceCatalogStore(); const applicationStore = useLegendMarketplaceBaseStore().applicationStore; const auth = useAuth(); const token = auth.user?.access_token; const tokenRef = useRef(token); tokenRef.current = token; const params = useParams(); const mcpServerName = decodeURIComponent( guaranteeNonNullable( params[LEGEND_MARKETPLACE_ROUTE_PATTERN_TOKEN.MCP_SERVER_NAME], ), ); const mcpServer = intelligenceCatalogStore.findLegendMcpServer(mcpServerName); const fetchMcpServers = useCallback((): void => { const currentToken = tokenRef.current; if (currentToken) { flowResult(intelligenceCatalogStore.fetchMcpServers(currentToken)).catch( applicationStore.alertUnhandledError, ); } }, [intelligenceCatalogStore, applicationStore]); useEffect(() => { fetchMcpServers(); }, [fetchMcpServers, token]); const renderContent = (): React.ReactNode => { if (mcpServer) { return ; } if (intelligenceCatalogStore.fetchingServersState.hasFailed) { return (
Could not load MCP servers from the registry
); } if (intelligenceCatalogStore.fetchingServersState.hasSucceeded) { return (
MCP server '{mcpServerName}' not found
); } return ; }; return ( {renderContent()} ); });