Skip to content

feat: Add MCP server deployments list page#6907

Open
YuliaKrimerman wants to merge 3 commits intoopendatahub-io:mainfrom
YuliaKrimerman:RHOAIENG-53379
Open

feat: Add MCP server deployments list page#6907
YuliaKrimerman wants to merge 3 commits intoopendatahub-io:mainfrom
YuliaKrimerman:RHOAIENG-53379

Conversation

@YuliaKrimerman
Copy link
Contributor

https://redhat.atlassian.net/browse/RHOAIENG-53379

Description

Adds the MCP Deployments list page — the primary view for managing deployed MCP servers. This includes navigation registration, the page shell, the deployments table, and all supporting components.

How Has This Been Tested?

  • Manual testing: Ran full ODH Dashboard locally (backend + frontend + model-registry federated plugin) and verified:
    • "MCP servers > Deployments" appears in sidebar when mcpCatalog feature flag is enabled
    • Page renders with correct title, description, and project selector
    • Empty state displays when no deployments exist for the selected namespace
    • Table renders 3 mock deployments (kubernetes-mcp, slack-mcp, jira-mcp) from BFF mock data with correct Server names, status labels, and Service column
    • Columns are sortable
    • Filter input filters by name and server name
    • Status labels show correct colors and tooltips
    • Kebab menu shows Edit (disabled) and Delete actions
  • Unit tests: 29 tests added and passing (npx jest --testPathPattern=mcpDeployments)
  • API verification: Verified BFF endpoint returns correct mock data via curl
Screenshot 2026-03-26 at 9 53 09 AM

Test Impact

Added 3 new test files with 29 tests:

  • __tests__/utils.spec.ts — Tests for getServerDisplayName, getStatusInfo, and getConnectionUrl utility functions (edge cases, all phase mappings)
  • __tests__/McpDeploymentsTableRow.spec.tsx — Tests for row rendering (server name, deployment name, date, status labels for all phases, Service View/dash for available/unavailable)
  • __tests__/McpDeploymentsTable.spec.tsx — Tests for table rendering (all rows present, column headers, empty state)

Request review criteria:

Self checklist (all need to be checked):

  • The developer has manually tested the changes and verified that the changes work
  • Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has added tests or explained why testing cannot be added (unit or cypress tests for related changes)
  • The code follows our Best Practices (React coding standards, PatternFly usage, performance considerations)

If you have UI changes:

  • Included any necessary screenshots or gifs if it was a UI change.
  • Included tags to the UX team if it was a UI/UX change.

After the PR is posted & before it merges:

  • The developer has tested their solution on a cluster by using the image produced by the PR to main

@openshift-ci openshift-ci bot requested review from mturley and ppadti March 26, 2026 13:57
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 26, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (19)
  • packages/model-registry/upstream/frontend/src/app/api/mcpDeploymentService.ts is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/mcpDeploymentTypes.ts is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentServicePopover.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentStatusLabel.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentsEmptyState.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentsPage.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentsRoutes.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentsTable.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentsTableColumns.ts is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentsTableRow.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/McpDeploymentsToolbar.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/__tests__/McpDeploymentsTable.spec.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/__tests__/McpDeploymentsTableRow.spec.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/__tests__/mcpDeploymentTestUtils.ts is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/__tests__/utils.spec.ts is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/useMcpDeployments.ts is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/utils.ts is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/odh/McpDeploymentsWrapper.tsx is excluded by !**/upstream/**
  • packages/model-registry/upstream/frontend/src/odh/extensions.ts is excluded by !**/upstream/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 9ccdd624-a648-49b5-8ff6-73dcb486df23

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

},
);

export const deleteMcpDeployment =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not used anywhere - should be cleaned up/edited

@@ -0,0 +1,60 @@
import React from 'react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

McpDeploymentsWrapper is same as McpCatalogWrapper - is this intentional ?

required: [SupportedArea.MCP_CATALOG],
},
properties: {
path: '/ai-hub/mcp-deployments/*',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will conflict with #6771 based on whichever goes in first - FYI no action needed until we see merge conflicts

};

const McpDeploymentsWrapper: React.FC = () => {
const modularArchConfig: ModularArchConfig = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The highest priority fix is adding useFetchDscStatus and passing mandatoryNamespace to the config — without it, the namespace selector behavior will be incorrect in production environments. Is issing this intentional ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it was not intentional — it was a local testing ovveride to actually be able to see the table. Restored useFetchDscStatus and mandatoryNamespace to match the McpCatalogWrapper pattern.

import { McpDeployment, McpDeploymentPhase } from '~/app/mcpDeploymentTypes';
import { getConnectionUrl, getServerDisplayName, getStatusInfo } from '../utils';

const createMockDeployment = (overrides: Partial<McpDeployment> = {}): McpDeployment => ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated: packages/model-registry/upstream/frontend/src/app/pages/mcpDeployments/__tests__/McpDeploymentsTableRow.spec.tsx

Copy link
Member

@manaswinidas manaswinidas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging tautological / repetitive tests — see inline comments.

Comment on lines +63 to +75
it('should render the table element', () => {
render(
<McpDeploymentsTable
deployments={mockDeployments}
toolbarContent={<div>toolbar</div>}
onClearFilters={onClearFilters}
onDeleteClick={onDeleteClick}
/>,
{ wrapper },
);

expect(screen.getByTestId('mcp-deployments-table')).toBeInTheDocument();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant — if "should render all deployment rows" passes (rows render), the table element necessarily exists. This only verifies PatternFly renders a <table>, not any application logic.

Comment on lines +77 to +89
it('should show empty table view when no deployments match filter', () => {
render(
<McpDeploymentsTable
deployments={[]}
toolbarContent={<div>toolbar</div>}
onClearFilters={onClearFilters}
onDeleteClick={onDeleteClick}
/>,
{ wrapper },
);

expect(screen.queryByTestId('mcp-deployment-row-kubernetes-mcp')).not.toBeInTheDocument();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never asserts the empty-state view is actually present — it only checks a specific row is absent, which is trivially true when deployments={[]}. Consider asserting that DashboardEmptyTableView renders.

Comment on lines +91 to +107
it('should render column headers', () => {
render(
<McpDeploymentsTable
deployments={mockDeployments}
toolbarContent={<div>toolbar</div>}
onClearFilters={onClearFilters}
onDeleteClick={onDeleteClick}
/>,
{ wrapper },
);

expect(screen.getByText('Server')).toBeInTheDocument();
expect(screen.getByText('Name')).toBeInTheDocument();
expect(screen.getByText('Created')).toBeInTheDocument();
expect(screen.getByText('Status')).toBeInTheDocument();
expect(screen.getByText('Service')).toBeInTheDocument();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low-value — tests that PatternFly renders column labels from a static config array. There's no branching logic under test; the labels come verbatim from the mcpDeploymentColumns constant.

Comment on lines +38 to +41
it('should render formatted creation date', () => {
renderRow(createMockDeployment());
expect(screen.getByTestId('mcp-deployment-created')).toBeInTheDocument();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vacuous assertion — toBeInTheDocument() only checks the <Td> cell exists, not that the date is formatted correctly. Every other row test uses toHaveTextContent(...). Consider asserting the rendered date value.

Comment on lines +43 to +56
it('should render available status for Running phase', () => {
renderRow(createMockDeployment({ phase: McpDeploymentPhase.RUNNING }));
expect(screen.getByTestId('mcp-deployment-status-label')).toHaveTextContent('Available');
});

it('should render unavailable status for Failed phase', () => {
renderRow(createMockDeployment({ phase: McpDeploymentPhase.FAILED }));
expect(screen.getByTestId('mcp-deployment-status-label')).toHaveTextContent('Unavailable');
});

it('should render pending status for Pending phase', () => {
renderRow(createMockDeployment({ phase: McpDeploymentPhase.PENDING }));
expect(screen.getByTestId('mcp-deployment-status-label')).toHaveTextContent('Pending');
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three status-label tests duplicate the getStatusInfo coverage in utils.spec.ts, which already verifies the label text for each phase. These only add that McpDeploymentStatusLabel passes the value through to a <Label> — a single test would suffice for that wiring check.

Comment on lines +58 to +61
it('should have a row with the correct test id', () => {
renderRow(createMockDeployment());
expect(screen.getByTestId('mcp-deployment-row-kubernetes-mcp')).toBeInTheDocument();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant — every other test in this file calls renderRow(createMockDeployment()) and queries elements inside the row; if the row didn't render they'd all fail. Also overlaps with "should render all deployment rows" in the table spec.

Comment on lines +63 to +76
it('should render View link for Running deployment', () => {
renderRow(createMockDeployment({ phase: McpDeploymentPhase.RUNNING }));
expect(screen.getByTestId('mcp-deployment-service-view')).toBeInTheDocument();
});

it('should render View link with address URL when provided', () => {
renderRow(
createMockDeployment({
phase: McpDeploymentPhase.RUNNING,
address: { url: 'kubernetes-test:8080' },
}),
);
expect(screen.getByTestId('mcp-deployment-service-view')).toBeInTheDocument();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests have identical assertions (getByTestId('mcp-deployment-service-view').toBeInTheDocument()). The second test provides an address URL but never verifies the URL value — consider opening the popover and asserting the ClipboardCopy content to make the second test meaningful.

Comment on lines +78 to +86
it('should render dash for Failed deployment without address', () => {
renderRow(createMockDeployment({ phase: McpDeploymentPhase.FAILED }));
expect(screen.getByTestId('mcp-deployment-service-unavailable')).toBeInTheDocument();
});

it('should render dash for Pending deployment without address', () => {
renderRow(createMockDeployment({ phase: McpDeploymentPhase.PENDING }));
expect(screen.getByTestId('mcp-deployment-service-unavailable')).toBeInTheDocument();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same logic path: getConnectionUrl returns undefined for any non-Running phase without an address → renders dash. Consider collapsing into it.each([FAILED, PENDING]). This is also already covered by getConnectionUrl tests in utils.spec.ts.

Comment on lines +52 to +65
it('should return address URL when provided', () => {
const deployment = createMockDeployment({
phase: McpDeploymentPhase.RUNNING,
address: { url: 'https://kubernetes-mcp.example.com:8080' },
});
expect(getConnectionUrl(deployment)).toBe('https://kubernetes-mcp.example.com:8080');
});

it('should prefer address URL over name:port fallback', () => {
const deployment = createMockDeployment({
phase: McpDeploymentPhase.RUNNING,
address: { url: 'custom-url:9090' },
});
expect(getConnectionUrl(deployment)).toBe('custom-url:9090');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests hit the same code branch (deployment.address?.url is truthy → return early). The name "should prefer address URL over name:port fallback" implies a precedence test, but there's no competing branch — the if short-circuits. One of these is sufficient.

const result = getStatusInfo(McpDeploymentPhase.RUNNING);
expect(result.label).toBe('Available');
expect(result.status).toBe('success');
expect(result.tooltip).toBeTruthy();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weak assertion (repeated on lines 103, 110, 117) — toBeTruthy() passes for any non-empty string. Since getStatusInfo is a pure mapping with known outputs, assert the actual tooltip string.

Copy link
Member

@manaswinidas manaswinidas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging missing Cypress mock test coverage.

);
};

export default McpDeploymentsPage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Cypress mock tests — this can be a follow-up, but flagging for visibility.

The MCP Catalog (sibling feature under the same mcp-servers nav section) has a full Cypress suite with page object, test utils, and intercepts (mcpCatalog.cy.ts, mcpCatalogTestUtils.ts, mcpCatalog.ts). The Deployments page has comparable interactive surface area but zero Cypress coverage.

Suggested coverage for a follow-up:

  1. Navigation & routing — nav item gated by mcpCatalog flag, route renders page, unknown sub-routes redirect
  2. Project selector — shows namespaces, switching re-fetches, disabled when mandatoryNamespace is set
  3. Table rendering — rows from BFF response with correct server name, status labels, dates
  4. Service popover — "View" link opens popover with connection URL for Running; dash for Failed/Pending
  5. Filtering — filter by name/server name, clear filters restores list, no-match shows DashboardEmptyTableView
  6. Empty state — zero deployments shows McpDeploymentsEmptyState
  7. Error & loading states — BFF 500 shows error, pending request shows loading
  8. Kebab actions — Edit disabled with tooltip, Delete clickable
  9. Accessibilitycy.testA11y() on main view and empty state

Test artifacts needed:

  • Page object (mcpDeployments.ts) with visit(), wait() + cy.testA11y(), finder methods
  • Test utils (mcpDeploymentsTestUtils.ts) with initMcpDeploymentsIntercepts()
  • Test file (mcpDeployments.cy.ts)

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 26, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from manaswinidas. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@codecov
Copy link

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 64.46%. Comparing base (ca13601) to head (b7000b6).
⚠️ Report is 22 commits behind head on main.

Files with missing lines Patch % Lines
...l-registry/upstream/frontend/src/odh/extensions.ts 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #6907      +/-   ##
==========================================
+ Coverage   64.09%   64.46%   +0.36%     
==========================================
  Files        2530     2497      -33     
  Lines       76695    76992     +297     
  Branches    19202    19127      -75     
==========================================
+ Hits        49161    49635     +474     
+ Misses      27534    27357     -177     
Files with missing lines Coverage Δ
...l-registry/upstream/frontend/src/odh/extensions.ts 63.63% <0.00%> (-6.37%) ⬇️

... and 118 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ed7e436...b7000b6. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants