<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mayur Rawte</title>
    <description>The latest articles on DEV Community by Mayur Rawte (@mayurrawte).</description>
    <link>https://dev.to/mayurrawte</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4019646%2F8a326910-aab3-4482-8eda-f0f75c63df49.jpg</url>
      <title>DEV Community: Mayur Rawte</title>
      <link>https://dev.to/mayurrawte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mayurrawte"/>
    <language>en</language>
    <item>
      <title>Every MCP client rebuilds the same seven screens</title>
      <dc:creator>Mayur Rawte</dc:creator>
      <pubDate>Tue, 07 Jul 2026 13:51:16 +0000</pubDate>
      <link>https://dev.to/mayurrawte/every-mcp-client-rebuilds-the-same-seven-screens-1764</link>
      <guid>https://dev.to/mayurrawte/every-mcp-client-rebuilds-the-same-seven-screens-1764</guid>
      <description>&lt;p&gt;MCP went from 2M to 97M monthly SDK downloads in a year. There are over 9,400 servers in the registry. A lot of people are suddenly building MCP clients — and if you've built one, you already know where this is going, because you've built these screens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A card that shows a tool call: name, arguments, a spinner, then a result or an error with a retry button.&lt;/li&gt;
&lt;li&gt;A consent dialog that lists OAuth scopes in language a human can approve.&lt;/li&gt;
&lt;li&gt;A form generated from a tool's JSON Schema, with validation.&lt;/li&gt;
&lt;li&gt;A scope inspector for "what exactly can this server touch?"&lt;/li&gt;
&lt;li&gt;A resource browser for whatever the server exposes.&lt;/li&gt;
&lt;li&gt;A connection status badge.&lt;/li&gt;
&lt;li&gt;And if you're on the MCP Apps spec: a sandboxed iframe with a postMessage bridge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are hard the first time you describe them. All of them are annoying the third time you build them. The tool-call card alone is a state machine with six states (idle, pending, running, done, error, cancelled), retry logic, theme tokens, and accessibility work — call it a week if you do it properly, which is exactly why most clients don't.&lt;/p&gt;

&lt;p&gt;The chat side of this stack is well served. Vercel's AI Elements, assistant-ui, CopilotKit — good projects. But they're React-only (CopilotKit also does Angular), and none of them ship the MCP-specific pieces. The one multi-framework option, Deep Chat, is a single monolithic widget, not primitives you can compose. When I went looking for a consent dialog I could copy into an Angular app, there wasn't one. So I built the set.&lt;/p&gt;

&lt;h2&gt;
  
  
  What mcp-elements is
&lt;/h2&gt;

&lt;p&gt;38 components you copy into your project with a CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mcp-elements add mcp-tool-call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That drops the component source, its CSS, and the core state machine it uses into your repo, with imports rewritten to relative paths. There is no runtime dependency to upgrade or get broken by. If you want to change how the retry button works, you edit the file. It's the shadcn model, applied to MCP.&lt;/p&gt;

&lt;p&gt;Seven components are MCP-specific — the list above. The other 31 are the base and AI pieces you need around them: chat bubble, streaming text, prompt input, source card, buttons, dialogs, the usual.&lt;/p&gt;

&lt;p&gt;Here's a full MCP flow in three components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServerStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;McpToolCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;McpConsentDialog&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcp-elements/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createToolState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcp-elements/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Connection state&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;McpServerStatus&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;serverName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github-mcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// 2. OAuth consent&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;McpConsentDialog&lt;/span&gt;
  &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showConsent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;serverName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"GitHub MCP"&lt;/span&gt;
  &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repo:read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user.email:read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onApprove&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleApprove&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onDeny&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDeny&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Tool execution&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createToolState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;toolState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_repos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mcp-elements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;McpToolCall&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toolState&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onRetry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;toolState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it works in React, Angular, and Vue
&lt;/h2&gt;

&lt;p&gt;The architecture is three layers, and the layering is the whole trick:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CSS&lt;/strong&gt; — plain stylesheets with OKLCH design tokens. No CSS-in-JS, no build coupling. Override the tokens, get a theme.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core&lt;/strong&gt; — plain TypeScript state machines: tool lifecycle, consent flow, JSON-Schema-to-form. No framework imports anywhere in the package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adapters&lt;/strong&gt; — thin React, Angular, and Vue components that subscribe to the core machines and apply the CSS classes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the state machine and the stylesheet are shared, the React and Angular versions of &lt;code&gt;McpToolCall&lt;/code&gt; don't just look the same — they &lt;em&gt;behave&lt;/em&gt; the same, transition for transition. When I fixed a focus-management bug in the dialog last week, the fix was the same fix in all three frameworks, because the bug lived in one place.&lt;/p&gt;

&lt;p&gt;This is also my answer to a complaint I kept seeing in issue trackers: the Svelte request on vercel/ai-elements has been open since September 2025. Framework-agnostic core plus thin adapters means adding a framework is a bounded amount of work, not a rewrite. Svelte and Lit adapters are on the roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unglamorous parts
&lt;/h2&gt;

&lt;p&gt;Things that took longer than the happy path, and that you get for free by copying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus management.&lt;/strong&gt; The dialog moves focus into itself on open, traps Tab, closes on Escape, and restores focus to the trigger on close. Most hand-rolled dialogs get at least one of those wrong; mine did too, for a while.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen readers during streaming.&lt;/strong&gt; Connection status uses &lt;code&gt;aria-live&lt;/code&gt; so state changes are announced. Almost no AI component library handles this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrast.&lt;/strong&gt; Both themes pass WCAG AA. The dark theme's muted text is 9.4:1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The six states.&lt;/strong&gt; Tool calls fail, get cancelled, and get retried. The card renders all of it, not just the demo path.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it isn't
&lt;/h2&gt;

&lt;p&gt;It's not an MCP client SDK — you bring your own connection and wire its events into the state machines. It's not a chat framework; there's no runtime, no provider, no cloud anything. And it's v0.1: all seven MCP components ship for all three frameworks, the base set is complete in React and Angular, and Vue has ten of the base components so far, with the rest in progress.&lt;/p&gt;

&lt;p&gt;The MCP Apps frame tracks the spec as published in January 2026. The spec is young. If it moves, the component moves — and since you own the copy in your repo, you decide when to take that update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The docs site renders every component live on the page — the playground demos are the actual components running, not screenshots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs and playground: &lt;a href="https://mcp-elements.wearesnx.studio" rel="noopener noreferrer"&gt;https://mcp-elements.wearesnx.studio&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/mcp-elements/ui" rel="noopener noreferrer"&gt;https://github.com/mcp-elements/ui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx mcp-elements add mcp-tool-call&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT licensed. If you've built an MCP client and something you needed isn't in the set, open an issue — the next batch of components gets picked from real gaps, not my guesses.&lt;/p&gt;

</description>
      <category>react</category>
      <category>opensource</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
