{ "name": "mcp-capability-matrix", "type": "registry:ui", "title": "MCP Capability Matrix", "description": "Which server provides which capability, and where two of them collide. MCP names are not namespaced by the protocol, so two servers exposing `search` is decided by your client’s merge order — silently. A per-server list can never show that.", "category": "MCP", "dependencies": [ "lucide-react@^1.39.0" ], "registryDependencies": [ "badge", "lib-motion", "lib-styles", "lib-utils" ], "files": [ { "path": "components/ui/mcp-capability-matrix.tsx", "type": "registry:ui", "content": "import type { ComponentProps, ReactNode } from 'react'\nimport { Check, TriangleAlert } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { enterFade } from '@/lib/motion'\nimport { radius, surface } from '@/lib/styles'\nimport { cn } from '@/lib/utils'\n\n/**\n * Which server provides which capability, and where two of them collide.\n *\n * The problem this exists for: MCP capability names are not namespaced by the\n * protocol. Connect two servers that both expose `search`, and which one the\n * model reaches is decided by your client's merge order — silently. A per-server\n * list can never show that, because the collision only exists *between* lists.\n *\n * So the grid is capabilities down, servers across, and any row with more than\n * one provider is flagged. That flag is the entire reason to render a matrix\n * rather than a set of cards.\n *\n * Grid, not a table element: the header column is sticky while the server\n * columns scroll, which `` will not do without fighting it.\n */\nexport type CapabilityRow = {\n name: string\n kind?: 'tool' | 'resource' | 'prompt'\n /** Server ids that provide it. More than one is a collision. */\n providers: string[]\n description?: ReactNode\n}\n\ntype McpCapabilityMatrixProps = Omit, 'children'> & {\n /** Column order. Ids must match those in `providers`. */\n servers: { id: string; name: string }[]\n capabilities: CapabilityRow[]\n /** Which provider wins a collision. Usually your client's merge order. */\n resolve?: (row: CapabilityRow) => string\n conflictLabel?: string\n emptyLabel?: string\n label?: string\n}\n\nfunction McpCapabilityMatrix({\n servers,\n capabilities,\n resolve,\n conflictLabel = 'Shadowed',\n emptyLabel = 'Nothing exposed yet.',\n label = 'Capability matrix',\n className,\n ...props\n}: McpCapabilityMatrixProps) {\n if (capabilities.length === 0) {\n return (\n
\n

{emptyLabel}

\n
\n )\n }\n\n const conflicts = capabilities.filter((row) => row.providers.length > 1).length\n\n return (\n \n {conflicts > 0 && (\n

\n \n {conflicts} capability name{conflicts === 1 ? '' : 's'} provided by more than one server.\n

\n )}\n\n
\n \n
\n \n Capability\n \n {servers.map((server) => (\n \n {server.name}\n \n ))}\n
\n\n {capabilities.map((row) => {\n const clash = row.providers.length > 1\n const winner = clash && resolve ? resolve(row) : row.providers[0]\n\n return (\n
\n \n {row.name}\n {row.kind && (\n \n {row.kind}\n \n )}\n \n\n {servers.map((server) => {\n const provides = row.providers.includes(server.id)\n const shadowed = provides && clash && server.id !== winner\n\n return (\n \n {provides ? (\n shadowed ? (\n \n {conflictLabel}\n \n ) : (\n \n )\n ) : (\n \n —\n \n )}\n \n )\n })}\n
\n )\n })}\n
\n \n \n )\n}\n\nexport { McpCapabilityMatrix }\nexport type { McpCapabilityMatrixProps }\n" } ] }