MCP Servers — Documentation Lookup in the Editor

MCP Servers — Documentation Lookup in the Editor

Article 7 · Series: Agentic Coding with Claude Code

v0.6 works. Three visualizations, one dataset, one switcher. At this point a different kind of problem emerges: every extension brings libraries into play that Claude Code knows from memory, but possibly not in their current version. That is not a criticism — it is a structural problem. Introducing shadcn/ui, a language model with a training cutoff might produce prop signatures from an older version. Using Tailwind 4, it might generate Tailwind 3 syntax. The tool for this is MCP.

What MCP Is and What It Is Not

MCP stands for Model Context Protocol. An MCP server is an external process that Claude Code can call at runtime to read data, invoke APIs, or execute tools. Unlike a skill, an MCP server does not define how to approach a task — it provides external capabilities.

The practical difference: a skill tells Claude Code “for a parser task, follow these steps”. An MCP server tells Claude Code “if you need current documentation for the D3 library, ask me”. The skill describes process and strategy. The MCP server delivers data or executes actions.

Two MCP servers are in use in Article 7:

  • Context7: fetches current library documentation for thousands of packages directly into the editor context. Claude can ask rather than guess.
  • Playwright: controls a real browser. Prepared for Article 8, only installed here.

Plan File for Article 7

Task 1 (sequential): set up .mcp.json
  Configure Context7 and Playwright as stdio servers.
  The file goes into the repo so that everyone who clones the project
  gets the same MCP configuration.

Task 2 (after Task 1): introduce shadcn/ui
  npx shadcn init, then add Tabs, Button and Card.
  Migrate App.tsx: replace the hand-built switcher with shadcn Tabs.

Task 3 (parallel to Task 2): chart-builder skill
  Distill the knowledge from the three existing charts.
  Skill calls Context7 for current D3 documentation.

Task 1: .mcp.json in the Repo

The first step is the simplest file:

Create a .mcp.json in the repo root.
Two servers: Context7 for library docs and Playwright for browser control.
The file should go into the repo, not into a global user configuration.

The result:

{
  "mcpServers": {
    "context7": {
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp"]
    },
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}

The file belongs in the repo. Anyone who stores MCP servers only in ~/.claude.json has them on their own machine, but other project contributors, new development environments, or CI see nothing. .mcp.json in the repo root is the right place.

.playwright-mcp/ goes into .gitignore, since the Playwright server stores session artifacts there that do not belong in version control.

What Context7 Actually Does

Before the shadcn/ui migration, we put Context7 to the test. The prompt:

I want to introduce shadcn/ui into a Vite project with Tailwind 4.
Please use Context7 to fetch the current installation guide.

Claude Code then calls mcp__context7__resolve-library-id, finds the current shadcn library ID, and fetches the relevant sections with mcp__context7__query-docs. What comes back is the documentation for the actually installed version, not what was valid at training time.

In our case the result: shadcn runs with Tailwind 4 using the -b radix flag, not a legacy configuration. Without the Context7 lookup, guessing would have been the alternative.

Context7 is not a replacement for IDE autocomplete on stable APIs. For Array.prototype.map or useState it is overkill. It becomes useful where Claude tends to guess: new major versions, libraries with frequent API breaks, plugin-specific configuration.

Task 2: Introducing shadcn/ui

shadcn/ui is not a conventional NPM library. There is no npm install @shadcn/ui. Instead, a CLI copies component source files directly into the project. The components are versioned, can be customized, and belong to the project.

Introduce shadcn/ui in web/: npx shadcn init.
Then add Tabs, Button, and Card.
Migrate App.tsx: the hand-built switcher with Tailwind buttons comes out,
shadcn Tabs come in.

After npx shadcn@latest init and adding the components:

npx shadcn@latest add button
npx shadcn@latest add tabs
npx shadcn@latest add card

three ready-to-use components sit in web/src/components/ui/, importable immediately. shadcn also creates src/lib/utils.ts with the cn() helper that combines clsx and tailwind-merge.

The migration of App.tsx is a direct swap. The previous hand-styled buttons:

<nav role="tablist" className="inline-flex rounded-md border ...">
  {VARIANTS.map((v) => (
    <button role="tab" aria-selected={variant === v.key}
      className={`px-4 py-1.5 ${variant === v.key ? "bg-stone-900 text-white" : "..."}`}
    >
      {v.label}
    </button>
  ))}
</nav>

are replaced by shadcn components:

<Tabs value={variant} onValueChange={(v) => setVariant(v as Variant)}>
  <TabsList>
    <TabsTrigger value="sunburst" data-testid="switch-sunburst">Sunburst</TabsTrigger>
    <TabsTrigger value="sankey"   data-testid="switch-sankey">Sankey</TabsTrigger>
    <TabsTrigger value="treemap"  data-testid="switch-treemap">Treemap</TabsTrigger>
  </TabsList>
  <TabsContent value="sunburst"><Sunburst titel={data.titel} /></TabsContent>
  <TabsContent value="sankey"><Sankey titel={data.titel} /></TabsContent>
  <TabsContent value="treemap"><Treemap titel={data.titel} /></TabsContent>
</Tabs>

The difference: shadcn Tabs bring keyboard navigation, correct ARIA attributes, and consistent styling without extra configuration. The overhead is minimal; the accessibility gain is immediate.

Task 3: The chart-builder Skill

After three visualizations, the repo contains implicit knowledge that should not have to be rediscovered. Which color conventions apply, how a drill-down with focusPath works, why <foreignObject> is needed for text wrapping in SVG, what smoke tests for D3 components look like. Distilling this into a skill means: for the next chart, nobody has to dig through existing files.

Create .claude/skills/chart-builder/SKILL.md.
The skill should trigger on chart requests and capture the following conventions:
props structure, color scheme, shortLabel pattern, focusPath mechanics, smoke test.
Also: for D3 API questions, the skill should call Context7.

The core workflow in the skill:

  1. Which chart type? Is buildHierarchy() the right data entry point, or does the chart need its own aggregation?
  2. Consult Context7 for D3 API when uncertain.
  3. Build the component following project conventions, including a smoke test.
  4. Integrate in the App.tsx switcher.

The skill references Sunburst.tsx for the drill-down pattern, Treemap.tsx for <foreignObject>, and Sankey.tsx for highlight behavior. Anyone building the next chart finds all patterns there.

Updating the Tests

The migration to shadcn Tabs brings an unexpected consequence for the tests. Radix Tabs, which shadcn builds on, does not render <TabsContent> of inactive tabs into the DOM by default. This means a test that clicks the Sankey tab and then searches for data-testid="sankey" finds nothing — not because something is broken, but because Radix only mounts the content when a tab is activated.

This is actually good behavior: unnecessary DOM weight stays out. For tests, though, it means the previous strategy of “click tab, check if content is there” reaches too deep into the rendering internals. It tests the shadcn implementation, not the actual app behavior.

The more robust alternative is to test what is semantically meaningful: are the right tab triggers present, and is the right one marked as active?

// Default tab: sunburst active
expect(screen.getByTestId("switch-sunburst")).toHaveAttribute("aria-selected", "true");
expect(screen.queryByTestId("sunburst")).toBeInTheDocument();

This is not a compromise but the better test. aria-selected is exactly what a screen reader or an accessibility audit would check. If the wrong tab is active, this test fails. If the visualization itself is broken, the smoke tests of the chart components take over.

3 tests passing, build clean.

Playwright MCP as Preparation

Playwright MCP is configured in .mcp.json but not yet used in Article 7. The MCP server starts a Chromium browser on demand, through which Claude Code can navigate, click, and take screenshots.

Pre-configuring it makes sense because the first Playwright start triggers a Chromium installation. Better to deal with that now than in the middle of an end-to-end test session in Article 8.

Status at the End of This Article

git clone https://codeberg.org/rotecodefraktion/byhaushalt.git
cd byhaushalt
git checkout v0.7
cd parser && uv run python -m parser.normalize
cd ../web && npm install && npm run dev

Full state at byhaushalt @ v0.7.

v0.7 contains: .mcp.json with Context7 and Playwright, shadcn/ui with Tabs, Button and Card, App.tsx migrated to shadcn Tabs, .claude/skills/chart-builder/SKILL.md. 22 tests passing, 3 xfailed.

cd parser && uv run pytest -v
cd web && npm run test
# 22 passed, 3 xfailed

What Comes Next

Article 8 covers end-to-end tests with Playwright MCP. Claude Code controls the browser directly, finds selectors, verifies that visualizations render correctly, and writes E2E specs from a Markdown description. The foundation for this is the Playwright MCP server already configured in Article 7.

How shadcn/ui was introduced with Context7 and what the chart-builder skill provides is documented here. The worktree setup is in Article 6. Subagents and the data model are in Article 5.