From 65f3067c7def55d133517041b1b544a2c1ef2978 Mon Sep 17 00:00:00 2001 From: Pratik Gandhi Date: Fri, 14 Aug 2026 12:02:54 +0100 Subject: [PATCH] feat: surface catalog entries in header search The header quick-search never returned MCP server catalog entries because it never requested them. Add catalog as a searchable entity type, route its results to the catalog page, and label the group with the existing sidebar string. No change is needed on the catalog page: it already reads ?search= from the URL on load, so a result click deep-links into a filtered grid. Inert until IBM/mcp-context-forge#6212 adds catalog to the backend's supported entity types. Unrecognised types are dropped server-side, so this cannot affect search before then. Closes #20 Signed-off-by: Pratik Gandhi --- src/api/search.ts | 10 ++- src/components/layout/HeaderQuickNav.test.tsx | 71 ++++++++++++++++++- src/components/layout/HeaderQuickNav.tsx | 3 + 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/api/search.ts b/src/api/search.ts index bc6218b..c5e7903 100644 --- a/src/api/search.ts +++ b/src/api/search.ts @@ -1,7 +1,15 @@ import { api } from "./client"; export type SearchEntityType = - "servers" | "gateways" | "tools" | "resources" | "prompts" | "agents" | "teams" | "users"; + | "servers" + | "gateways" + | "tools" + | "resources" + | "prompts" + | "agents" + | "teams" + | "users" + | "catalog"; export interface GlobalSearchItem { id?: string; diff --git a/src/components/layout/HeaderQuickNav.test.tsx b/src/components/layout/HeaderQuickNav.test.tsx index 7f20cb5..2a9ef75 100644 --- a/src/components/layout/HeaderQuickNav.test.tsx +++ b/src/components/layout/HeaderQuickNav.test.tsx @@ -314,7 +314,16 @@ describe("HeaderQuickNav", () => { expect(searchEntities).toHaveBeenCalledWith( expect.objectContaining({ query: "server", - entityTypes: ["servers", "gateways", "tools", "resources", "prompts", "agents", "teams"], + entityTypes: [ + "servers", + "gateways", + "tools", + "resources", + "prompts", + "agents", + "teams", + "catalog", + ], limitPerType: 8, teamId: null, }), @@ -392,6 +401,66 @@ describe("HeaderQuickNav", () => { expect(mockNavigate).toHaveBeenCalledWith("/app/tools?selected=tool-1&search=tool"); }); + it("groups catalog results under the server catalog label", async () => { + vi.mocked(searchEntities).mockResolvedValue({ + query: "cloudflare", + entity_types: ["catalog"], + limit_per_type: 8, + results: {}, + groups: [ + { + entity_type: "catalog", + count: 1, + items: [ + { id: "cloudflare-docs", name: "Cloudflare Docs", description: "Cloudflare docs MCP" }, + ], + }, + ], + items: [], + count: 1, + }); + + renderQuickNav(); + + const input = screen.getByRole("searchbox", { name: "Search" }); + fireEvent.focus(input); + fireEvent.change(input, { target: { value: "cloudflare" } }); + + expect(await screen.findByText("Server catalog")).toBeInTheDocument(); + expect(screen.getByText("Cloudflare Docs")).toBeInTheDocument(); + expect(screen.getByText("Cloudflare docs MCP")).toBeInTheDocument(); + }); + + it("deep-links a selected catalog result into the catalog search", async () => { + const user = userEvent.setup(); + vi.mocked(searchEntities).mockResolvedValue({ + query: "cloudflare", + entity_types: ["catalog"], + limit_per_type: 8, + results: {}, + groups: [ + { + entity_type: "catalog", + count: 1, + items: [{ id: "cloudflare-docs", name: "Cloudflare Docs" }], + }, + ], + items: [], + count: 1, + }); + + renderQuickNav(); + + const input = screen.getByRole("searchbox", { name: "Search" }); + fireEvent.focus(input); + fireEvent.change(input, { target: { value: "cloudflare" } }); + await user.click(await screen.findByText("Cloudflare Docs")); + + expect(mockNavigate).toHaveBeenCalledWith( + "/app/server-catalog?selected=cloudflare-docs&search=cloudflare", + ); + }); + it("navigates to the first result when the form is submitted", async () => { vi.mocked(searchEntities).mockResolvedValue({ query: "resource", diff --git a/src/components/layout/HeaderQuickNav.tsx b/src/components/layout/HeaderQuickNav.tsx index eea3f26..1227fbc 100644 --- a/src/components/layout/HeaderQuickNav.tsx +++ b/src/components/layout/HeaderQuickNav.tsx @@ -25,6 +25,7 @@ const BASE_SEARCH_ENTITY_TYPES: SearchEntityType[] = [ "prompts", "agents", "teams", + "catalog", ]; const ENTITY_ROUTE: Record = { @@ -36,6 +37,7 @@ const ENTITY_ROUTE: Record = { agents: "/app/agents", teams: "/app/settings/teams", users: "/app/settings/users", + catalog: "/app/server-catalog", }; const ENTITY_LABEL_KEY: Record = { @@ -47,6 +49,7 @@ const ENTITY_LABEL_KEY: Record = { agents: "navigation.agents", teams: "navigation.teams", users: "navigation.users", + catalog: "navigation.serverCatalog", }; type SearchStatus = "idle" | "loading" | "success" | "error";