Skip to content

feat(MCP UI) plugins nav#2053

Open
dimetron wants to merge 4 commits into
kagent-dev:mainfrom
dimetron:feature/mcp-ui-plugins-nav
Open

feat(MCP UI) plugins nav#2053
dimetron wants to merge 4 commits into
kagent-dev:mainfrom
dimetron:feature/mcp-ui-plugins-nav

Conversation

@dimetron

Copy link
Copy Markdown
Contributor

MCP UI plugins nav (#2047)

This pull request introduces a new, fully declarative mechanism for registering and surfacing MCP server web UIs as first-class plugins in the kagent UI. By extending the RemoteMCPServer CRD with a spec.ui block, MCP servers can now expose their own web interfaces directly within the kagent console, benefiting from theme, namespace, and navigation integration—all without requiring a new CRD or controller changes. The backend lists and proxies these plugins, and the UI displays them in a dedicated sidebar section, framing each plugin in an iframe and supporting a host↔plugin postMessage protocol.

Key changes:

CRD and API Extensions

  • Added an optional ui block (RemoteMCPServerUI) to the RemoteMCPServer CRD (remotemcpserver_types.go, CRD YAML), allowing servers to declare UI plugin metadata such as enabled, pathPrefix, displayName, icon, section, defaultPath, and injectCSS. This enables declarative plugin registration and configuration. [1] [2]
  • Added deep copy methods for the new RemoteMCPServerUI struct to support Kubernetes CRD operations. [1] [2]
  • Introduced a new PluginResponse API type to represent plugin metadata for the UI sidebar.

Backend Implementation

  • Implemented a new PluginsHandler with two main endpoints:
    • GET /api/plugins: Lists all enabled UI plugins across watched namespaces, projecting them to PluginResponse and applying defaults.
    • /_p/{pathPrefix}/*: Reverse-proxies requests to the plugin's web root, injecting custom CSS if specified and ensuring proper authorization and header resolution.
  • Registered the new handler and routes in the server's handler wiring. [1] [2]

Documentation

  • Added a comprehensive design document (EP-2047-mcp-ui-plugin-registration.md) summarizing motivation, goals, implementation details, backend and UI contracts, and test plan for the UI plugin registration feature.

dimetron added 2 commits June 18, 2026 19:14
…ver.spec.ui (kagent-dev#2047)

Add an optional spec.ui block to the RemoteMCPServer v1alpha2 CRD so any MCP
server shipping a web UI can be surfaced inside the kagent console: listed in a
"Plugins" sidebar section and reverse-proxied same-origin under /_p/{pathPrefix}/
with theme/namespace propagation via a kagent:* postMessage protocol.

Backend: GET /api/plugins registry + /_p/{pathPrefix} reverse proxy with CSS
injection (plugins hunks of the shared server.go/handlers.go/httpapi types).
UI: AppSidebar nav, plugin frame page, namespace/sidebar-status providers.

Depends on kagent-dev#2045 only conceptually (SSO); the UserMenu sidebar variant ships here.

Includes design/EP-2047-mcp-ui-plugin-registration.md and specs/mcp-ui-registration.

Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
…gent-dev#2047)

Add jest tests for getPlugins (success, backend error, 404 => empty list,
unexpected failure), getBackendRoot (/api suffix stripping), SidebarStatusProvider
(load ok, plugins-failed, retry re-fetch), and NamespaceProvider (default,
update, out-of-provider guard).

Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
@github-actions github-actions Bot added the enhancement-proposal Indicates that this PR is for an enhancement proposal label Jun 18, 2026
@dimetron dimetron changed the title feature/mcp UI plugins nav feat(MCP UI) plugins nav Jun 18, 2026
dimetron added 2 commits June 18, 2026 19:54
…gent-dev#2047)

- Validate event.origin on host<->plugin postMessage so foreign frames cannot
  drive navigation/resize/title/etc.
- Restrict kagent:navigate to same-origin URLs (blocks javascript:/data: and
  external open-redirects) and use window.location.assign.
- Post the kagent:context message to window.location.origin instead of "*".
- Add tests covering same-origin vs foreign-origin message handling.
- Remove specs/mcp-ui-registration/ design dump; EP-2047 is the design of record.

Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
@dimetron dimetron marked this pull request as ready for review June 19, 2026 20:56
@dimetron dimetron requested a review from supreme-gg-gg as a code owner June 19, 2026 20:56
Copilot AI review requested due to automatic review settings June 19, 2026 20:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class “UI plugins” to the kagent console by letting RemoteMCPServer resources declaratively expose embedded web UIs via spec.ui, with backend registry + reverse-proxy support and new UI navigation/iframe framing.

Changes:

  • Extends the RemoteMCPServer v1alpha2 API/CRD with an optional spec.ui block and generated deepcopy/CRD manifests.
  • Implements backend plugin listing (GET /api/plugins) and same-origin reverse-proxying under /_p/{pathPrefix}/…, including optional HTML CSS injection.
  • Updates the Next.js UI to render a dedicated sidebar experience (status, namespace selector, plugin nav) and a plugin iframe host page + supporting tests/docs.

Reviewed changes

Copilot reviewed 30 out of 31 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
ui/src/lib/utils.ts Adds getBackendRoot() helper for building non-/api root URLs (used for /_p/...).
ui/src/lib/sidebar-status-context.tsx Adds client context for plugin list loading + status/retry.
ui/src/lib/namespace-context.tsx Adds client namespace context used by sidebar + plugin messaging.
ui/src/lib/tests/sidebar-status-context.test.tsx Unit tests for plugin list loading/retry status behavior.
ui/src/lib/tests/namespace-context.test.tsx Unit tests for namespace context default + provider enforcement.
ui/src/lib/tests/getBackendRoot.test.ts Unit tests for stripping /api suffix from backend URL.
ui/src/components/UserMenu.tsx Adds a sidebar-specific rendering variant for user menu.
ui/src/components/sidebars/StatusIndicator.tsx Adds sidebar footer status indicator for plugin load health + retry.
ui/src/components/sidebars/SidebarCollapseButton.tsx Adds desktop-only sidebar collapse/expand toggle in footer.
ui/src/components/sidebars/NamespaceSelector.tsx Adds namespace selector UI integrated into the new sidebar header.
ui/src/components/sidebars/AppSidebarNav.tsx Implements sectioned sidebar navigation and merges in plugin links + badges.
ui/src/components/sidebars/AppSidebar.tsx New sidebar shell (header/nav/footer) wiring status + namespace + user menu.
ui/src/components/MobileTopBar.tsx Adds a mobile-only top bar with sidebar trigger to match sidebar sheet behavior.
ui/src/app/providers.tsx Centralizes provider ordering in a single client boundary, including Sidebar/Namespace.
ui/src/app/plugins/page.tsx Adds “Plugins status” internal page for viewing registry + backend health checks.
ui/src/app/plugins/[name]/[[...path]]/page.tsx Adds iframe host page + host↔plugin postMessage protocol handling.
ui/src/app/plugins/[name]/[[...path]]/tests/page.test.tsx Unit tests for same-origin filtering + resize/title message handling.
ui/src/app/layout.tsx Refactors root layout to server component reading sidebar cookie + rendering sidebar/inset.
ui/src/app/api/plugins/route.ts Adds Next.js BFF route proxying GET /api/plugins to the Go backend.
ui/src/app/actions/plugins.ts Adds server actions for listing plugins and probing plugin backend health.
ui/src/app/actions/tests/plugins.test.ts Unit tests for plugin list server action behavior.
helm/kagent-crds/templates/kagent.dev_remotemcpservers.yaml Publishes CRD schema changes for spec.ui via Helm chart CRDs.
go/core/internal/httpserver/server.go Wires new /api/plugins and /_p/{pathPrefix} routes into the HTTP server.
go/core/internal/httpserver/handlers/plugins.go Implements plugin listing + reverse-proxy with optional CSS injection.
go/core/internal/httpserver/handlers/plugins_test.go Unit tests for list filtering/defaulting + proxying/CSS injection/502 handling.
go/core/internal/httpserver/handlers/handlers.go Registers the new Plugins handler in the handler bundle.
go/api/v1alpha2/zz_generated.deepcopy.go Regenerates deepcopy support for new RemoteMCPServerUI type.
go/api/v1alpha2/remotemcpserver_types.go Adds RemoteMCPServerUI to the API type and kubebuilder validations/defaults.
go/api/httpapi/types.go Introduces PluginResponse type used by the UI registry endpoint.
go/api/config/crd/bases/kagent.dev_remotemcpservers.yaml Updates generated CRD base manifest with spec.ui schema.
design/EP-2047-mcp-ui-plugin-registration.md Adds design EP documenting the feature, contracts, and test plan.
Files not reviewed (1)
  • go/api/v1alpha2/zz_generated.deepcopy.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ui/src/lib/utils.ts
Comment on lines +47 to +50
export function getBackendRoot(): string {
const url = getBackendUrl();
return url.replace(/\/api\/?$/, "") || url;
}
Comment on lines +80 to +96
if (isCollapsed) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 shrink-0"
aria-label={`Namespace: ${value || "none"}`}
>
{loading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Network className="h-4 w-4" />
)}
</Button>
Comment on lines +134 to +139
// Add PLUGINS section for plugins that specify a section not in NAV_SECTIONS
const pluginsSection = plugins.filter((p) => !knownSections.includes(p.section));
if (pluginsSection.length > 0) {
sections.push({
label: "ADMIN",
items: pluginsSection.map((p) => ({
Comment on lines +112 to +117
const pluginItems: NavItemWithBadge[] = plugins
.filter((p) => p.section === section.label)
.map((p) => ({
label: p.displayName,
href: `/plugins/${p.pathPrefix}`,
icon: getIconByName(p.icon),
Comment on lines +8 to +14
export interface PluginItem {
name: string;
pathPrefix: string;
displayName: string;
icon: string;
section: string;
}
Comment on lines +12 to +18
export interface SidebarPluginNav {
name: string;
pathPrefix: string;
displayName: string;
icon: string;
section: string;
}
Comment on lines +27 to +29
const pathParams = useParams<{ path?: string[] }>();
const subPath = pathParams.path ? "/" + pathParams.path.join("/") : "/";
const iframeSrc = `/_p/${name}${subPath}`;
Comment on lines +135 to +145
Director: func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host
req.URL.Path = remainder
// Request an uncompressed response so injectCSS can be spliced reliably.
req.Header.Set("Accept-Encoding", "identity")
for k, v := range headers {
req.Header.Set(k, v)
}
},
Comment on lines +108 to +114
// PathPrefix is the URL path segment used for routing: /_p/{pathPrefix}/
// Must be a valid URL path segment (lowercase alphanumeric + hyphens).
// Defaults to the RemoteMCPServer name if not specified.
// +optional
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Pattern=`^[a-z0-9][a-z0-9-]*[a-z0-9]$`
PathPrefix string `json:"pathPrefix,omitempty"`
Comment on lines +49 to +56
| Field | Type | Default | Validation | Purpose |
|-------|------|---------|------------|---------|
| `enabled` | bool | `false` | — | Opt-in: this server provides a web UI. |
| `pathPrefix` | string | `<name>` | `maxLength=63`, `^[a-z0-9][a-z0-9-]*[a-z0-9]$` | URL segment for `/_p/{pathPrefix}/`. |
| `displayName` | string | `<name>` | — | Sidebar label. |
| `icon` | string | `puzzle` | — | `lucide-react` icon name. |
| `section` | enum | `PLUGINS` | `OVERVIEW\|AGENTS\|RESOURCES\|ADMIN\|PLUGINS` | Sidebar section. |
| `defaultPath` | string | `/` | — | Initial sub-path at plugin root. |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement-proposal Indicates that this PR is for an enhancement proposal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants