MCP Capability Matrix

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.

src/components/ui/mcp-capability-matrix.tsx

About

From the component's own source, where it was written.

Which server provides which capability, and where two of them collide.

The problem this exists for: MCP capability names are not namespaced by the protocol. Connect two servers that both expose search, and which one the model reaches is decided by your client's merge order — silently. A per-server list can never show that, because the collision only exists *between* lists.

So the grid is capabilities down, servers across, and any row with more than one provider is flagged. That flag is the entire reason to render a matrix rather than a set of cards.

Grid, not a table element: the header column is sticky while the server columns scroll, which <table> will not do without fighting it.

Install

npx astralyx-ui add mcp-capability-matrix

Usage

import { McpCapabilityMatrix } from '@/components/ui/mcp-capability-matrix'

<McpCapabilityMatrix servers={servers} capabilities={caps} resolve={(row) => row.providers[0]} />

Composer

Change the props and the source below follows.

2 capability names provided by more than one server.

Capabilityfilesystemgitlinear
searchtoolShadowed
read_filetool
list_issuestool
difftool
statustoolShadowed
review_diffprompt
Props

No props to configure.

tsx
<McpCapabilityMatrix
  servers={servers}
  capabilities={caps}
  resolve={(row) => row.providers[0]}
/>

Examples

Two servers both exposing `search`

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

McpCapabilityMatrix

Which server provides which capability, and where two of them collide.

Extends Omit<ComponentProps<'div'>, 'children'>.

PropTypeDefaultDescription
servers{ id: string; name: string }[]requiredCapabilityRow is { name, kind?, providers, description? }. More than one provider is a collision.
capabilitiesCapabilityRow[]requiredCapabilityRow is { name, kind?, providers, description? }. More than one provider is a collision.
resolve(row: CapabilityRow) => string—Which provider wins — usually your client’s merge order. The losers are marked Shadowed rather than looking equally available.
conflictLabelstring'Shadowed'—
emptyLabelstring'Nothing exposed yet.'—
labelstring'Capability matrix'—

Types

CapabilityRow

FieldTypeDescription
namestring—
kind?'tool' | 'resource' | 'prompt'—
providersstring[]Server ids that provide it. More than one is a collision.
description?ReactNode—

Behaviour

TopicDetailDescription
why a gridcollisions live between listsThe clash only exists across servers, so no per-server view can show it. That is the entire reason this renders a matrix.