<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://joaojmendesblog.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://joaojmendesblog.com/" rel="alternate" type="text/html" /><updated>2026-10-18T21:00:55+00:00</updated><id>https://joaojmendesblog.com/feed.xml</id><title type="html">João Mendes</title><subtitle>M365 Development MVP sharing insights on SharePoint Framework, React, TypeScript, Microsoft Graph, MCP, and modern Microsoft 365 development.</subtitle><author><name>João Mendes</name><email></email></author><entry><title type="html">One Business Application, Many Front Doors: Rethinking App Development with MCP Apps</title><link href="https://joaojmendesblog.com/blog/2026/10/18/mcp-ui-apps-one-business-app-everywhere/" rel="alternate" type="text/html" title="One Business Application, Many Front Doors: Rethinking App Development with MCP Apps" /><published>2026-10-18T13:00:00+00:00</published><updated>2026-10-18T13:00:00+00:00</updated><id>https://joaojmendesblog.com/blog/2026/10/18/mcp-ui-apps-one-business-app-everywhere</id><content type="html" xml:base="https://joaojmendesblog.com/blog/2026/10/18/mcp-ui-apps-one-business-app-everywhere/"><![CDATA[<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/header.svg" alt="Conceptual illustration of one business application running as a standalone web app or inside an AI assistant, connected to shared MCP tools and IBM i data" /></p>

<p>What if people could use the same business application in the place that suits them?</p>

<p>One person might open a familiar standalone web app. Another might ask an assistant to find something and work with the application right inside the conversation. A third might use a company’s Microsoft Copilot experience. And, where the client supports it, the person might speak instead of type.</p>

<p>This is the direction I am exploring with <strong>MCP Apps</strong> and my <strong>IBM i Sales Workspace</strong>. It is more than adding a chatbot to an existing application: it changes how we can think about the relationship between business capabilities, user interfaces, and the host applications people already use.</p>

<h2 id="from-one-application-per-platform-to-one-capability-many-experiences">From one application per platform to one capability, many experiences</h2>

<p>Traditionally, a business system’s interface is tightly coupled to where it runs. A browser gets a web app; a collaboration platform gets its own component; a mobile client may need another implementation. Each can end up duplicating UI and integration logic, even when all of them are trying to work with the same business data.</p>

<p>The <strong>Model Context Protocol (MCP)</strong> provides a standard way for an MCP client to discover and call tools exposed by an MCP server. <strong>MCP Apps</strong> build on that model by allowing a server to offer an interactive user interface associated with its tools. A compatible host can render that UI and connect it to the server through the MCP Apps bridge.</p>

<p>The important shift is that an application can be more than a page that owns its API calls. It can expose:</p>

<ul>
  <li><strong>Business capabilities</strong> as typed, discoverable MCP tools.</li>
  <li><strong>An interactive interface</strong> as an MCP App resource that a compatible host can display.</li>
  <li><strong>A standalone experience</strong> that uses the same capabilities and can render the same workspace outside a conversation.</li>
</ul>

<p>That creates a useful separation: the business application provides its functionality, while the user can choose a supported place to interact with it. The host still matters, though. MCP Apps do not mean every UI bundle runs unchanged in every assistant or platform. A host must support the relevant MCP Apps behavior, and integrations may need host-specific work.</p>

<h2 id="the-ibm-i-sales-workspace-one-service-three-ways-in">The IBM i Sales Workspace: one service, three ways in</h2>

<p>My IBM i sample makes the idea concrete. It connects a React sales workspace to IBM i Db2 through a Node.js MCP server and Mapepire. The server registers the business tools and serves the workspace as an MCP App resource. The interface is built with React and Fluent UI.</p>

<p>The current solution has three entry points:</p>

<ol>
  <li><strong>Standalone web application.</strong> A Vite/React web host connects to the MCP endpoint through a server-side proxy and displays the workspace with the official AppBridge. It is a normal web experience; it does not need an AI model or an API key.</li>
  <li><strong>MCP Apps host.</strong> A compatible MCP client can discover the workspace resource and render it alongside the conversation and tools. The exact experience depends on that client’s MCP Apps support and host policies.</li>
  <li><strong>Microsoft Copilot experience.</strong> An SPFx Copilot component provides a Copilot-facing entry point, routes typed requests to the existing sales tools, and reuses the workspace. This is a platform-specific integration—not a claim that every Copilot surface automatically supports MCP Apps resources. The current SharePoint Copilot component is preview functionality and should be validated in a development tenant.</li>
</ol>

<p>The shared path looks like this:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Standalone web host ───────┐
MCP Apps-compatible client ─┼── MCP tools + UI resource ── Mapepire ── IBM i Db2
SharePoint Copilot component┘
</code></pre></div></div>

<p>The standalone host is available at <a href="https://ibmisalesworkspace.spteckapps.com">ibmisalesworkspace.spteckapps.com</a>, and the MCP endpoint is <a href="https://ibmisales.spteckapps.com/mcp">ibmisales.spteckapps.com/mcp</a>. The server publishes the workspace under a versioned <code class="language-plaintext highlighter-rouge">ui://</code> resource URI. Tools advertise which resource belongs with them; clients can discover the resource instead of each client hardcoding a particular UI bundle version.</p>

<p>The workspace includes customer, order, product, sales, sales representative, and sales-analysis views. It can page and filter lists, show details, and present a dashboard over the available sales data. The same React workspace can appear in a full browser window or inside a supported host, while the server remains responsible for data access.</p>

<h2 id="one-workspace-in-three-environments">One workspace in three environments</h2>

<p>These screenshots show the IBM i Sales Workspace running as a standalone web app, inside Microsoft Copilot, and in Claude Desktop. The host framing changes, but the core sales workspace and its read-only data operations remain familiar.</p>

<h3 id="standalone-web-app">Standalone web app</h3>

<p>The web app gives the workspace the full browser window: users can move between the dashboard and records, inspect orders, and work with filters without starting from a conversation.</p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/web-sales-dashboard.webp" alt="IBM i Sales Workspace sales dashboard in the standalone web app" /></p>

<p><em>Sales analysis dashboard in the standalone web app.</em></p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/web-orders-list.webp" alt="Orders list in the standalone IBM i Sales Workspace" /></p>

<p><em>The orders list with customer, date, and representative data.</em></p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/web-orders-filter-panel.webp" alt="Order filters open beside the standalone IBM i Sales Workspace orders list" /></p>

<p><em>The filter panel lets users refine the order view before applying their choices.</em></p>

<h3 id="microsoft-copilot">Microsoft Copilot</h3>

<p>In Copilot, a person can start with a natural-language request such as “Show my sales dashboard.” The Copilot experience opens the interactive workspace alongside the conversation, where the person can continue exploring the dashboard or navigate to orders.</p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/copilot-agent-entry.webp" alt="Microsoft Copilot agent entry screen for IBM Sales Workspace" /></p>

<p><em>The Copilot entry point offers workspace and sales-task prompts.</em></p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/copilot-sales-dashboard.webp" alt="Sales dashboard opened inside a Microsoft Copilot conversation" /></p>

<p><em>The request and assistant response sit beside the interactive sales dashboard.</em></p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/copilot-orders.webp" alt="Orders displayed in the IBM i Sales Workspace inside Microsoft Copilot" /></p>

<p><em>The same workspace can move from a dashboard request to browsing orders.</em></p>

<h3 id="claude-desktop">Claude Desktop</h3>

<p>Claude Desktop can also host the MCP App. The conversation and the embedded interface complement one another: the assistant helps express the task, and the workspace presents structured records and analysis for further exploration.</p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/claude-sales-dashboard.webp" alt="IBM i sales analysis dashboard rendered in Claude Desktop" /></p>

<p><em>The sales dashboard rendered inside Claude Desktop.</em></p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/claude-customers.webp" alt="Customer list rendered in the IBM i Sales Workspace inside Claude Desktop" /></p>

<p><em>Customers remain available in the same interactive workspace.</em></p>

<p><img src="/assets/images/posts/mcp-ui-apps-ibmi-sales/screenshots/claude-sign-in.webp" alt="IBM i Sales Workspace sign-in screen rendered in Claude Desktop" /></p>

<p><em>The workspace presents its sign-in flow within the host rather than asking users to send credentials in chat.</em></p>

<h2 id="what-does-natural-language-interaction-look-like">What does natural-language interaction look like?</h2>

<p>Imagine asking:</p>

<blockquote>
  <p>“Show me the orders for Acme in January 2026.”</p>
</blockquote>

<p>An assistant can interpret the request, resolve the customer with a customer-search tool, and then call the order-list tool with the resolved customer ID and date range. The response can include structured data for the assistant and a workspace view for the person to inspect. They can then use the UI to filter, select a row, and open order details.</p>

<p>The conversation and the interface work together. Natural language is useful for expressing intent; a dedicated UI is often better for exploring a result set, checking exact values, or applying several filters. The person does not have to choose one interaction style forever.</p>

<p>Voice follows the same principle, with one important distinction: <strong>voice is a capability of the host experience, not something MCP Apps automatically adds</strong>. If an assistant or device supports speech input, it can turn spoken language into a request and invoke the same tools. The workspace then presents the response as UI. Voice input, transcription, permissions, and confirmation behavior vary by host; the MCP App itself should not assume that it can access a microphone.</p>

<h2 id="be-precise-about-what-the-sample-does-today">Be precise about what the sample does today</h2>

<p>The current IBM i workspace is <strong>read-only</strong>. Its tools support searching and reading customers, orders, products, sales, and sales representatives, plus sales summaries and a dashboard. It does not create or update orders. The demo data does not provide a verified order-status history, so the application does not invent one.</p>

<p>That constraint is intentional. Natural language can make a request easier to express, but it does not replace authorization, validation, or a confirmation step. If write operations are added later, they should be exposed as explicit server-side capabilities with appropriate identity checks and safeguards—not implemented as unrestricted SQL or assumed from a conversational instruction.</p>

<p>The sample also keeps the IBM i connection and database access on the server side. The embedded UI talks to the host through the MCP bridge rather than reaching directly into the database. Sign-in is handled in the workspace; the browser encrypts credentials before sending them to the server, and tool operations are scoped to the authenticated connection. The server remains the trust boundary.</p>

<h2 id="what-changes-for-application-architecture">What changes for application architecture?</h2>

<p>MCP Apps encourage us to treat the UI as a portable surface over business capabilities, rather than the only place where those capabilities can be reached.</p>

<h3 id="1-design-tools-around-business-tasks">1. Design tools around business tasks</h3>

<p>Tools should represent clear operations such as “search customers,” “list orders,” or “summarize sales.” Give them typed inputs and outputs, useful descriptions, bounded results, and explicit constraints. Tool design affects both what an assistant can reliably request and how the UI can retrieve data.</p>

<p>For example, when a customer name is ambiguous, the sales tools return candidates rather than silently choosing one. Date ranges require explicit years. For paginated data, the next-page cursor must be used with the original filters and sort order. These rules matter whether a request starts in a chat or in a filter panel.</p>

<h3 id="2-keep-business-rules-and-data-access-on-the-server">2. Keep business rules and data access on the server</h3>

<p>The server validates arguments, enforces the available access, and talks to IBM i. That gives conversational and graphical interactions a shared set of business operations. It also avoids trusting a client-side component to enforce the real security boundary.</p>

<h3 id="3-treat-the-ui-as-a-host-rendered-experience">3. Treat the UI as a host-rendered experience</h3>

<p>An MCP App can use host-provided context and communicate through the MCP Apps bridge. It should not assume it owns the entire browser window, controls the host’s theme, or can make arbitrary network requests from its embedded frame. Layout, sizing, authentication, and permissions need testing inside each target host.</p>

<h3 id="4-build-host-adapters-where-they-add-value">4. Build host adapters where they add value</h3>

<p>The standalone web host and the SPFx Copilot component are not redundant. They provide different entry points and have different hosting responsibilities, even while they reuse the MCP tools and workspace. A native MCP Apps host may provide another entry point without requiring the application team to build a complete chat client.</p>

<p>The goal is not to eliminate host-specific code. It is to keep that code at the integration boundary and avoid rewriting the business application for every surface.</p>

<h2 id="a-practical-development-path">A practical development path</h2>

<p>For a new or modernized business application, I would approach the work in this order:</p>

<ol>
  <li>Identify the real user tasks and the business systems that own the data.</li>
  <li>Expose a small set of typed MCP tools with clear descriptions and explicit read/write boundaries.</li>
  <li>Build a responsive MCP App UI that uses those tools through the host bridge.</li>
  <li>Add a standalone host when users need a direct, familiar application outside an assistant.</li>
  <li>Add platform-specific integrations—such as a Copilot component—when they fit the organization’s workflow.</li>
  <li>Test the same user journeys across the actual target hosts, including identity, permissions, viewport sizing, errors, and data boundaries.</li>
</ol>

<p>This is not a shortcut around good application design. It makes the contracts between the server, the UI, and each host more visible—and gives us another way to reuse the application.</p>

<h2 id="the-bigger-idea-users-choose-the-front-door">The bigger idea: users choose the front door</h2>

<p>Business applications have traditionally asked users to navigate to the application first and then figure out how to complete a task. With MCP tools and UI Apps, a user can start from the application, or from a compatible assistant that understands the request and opens the relevant business experience.</p>

<p>That is the paradigm shift I find exciting: <strong>one set of business capabilities, an interactive UI that can travel with them, and multiple supported ways for people to work</strong>. Not “one UI magically works everywhere,” but a shared application core that can meet users in different environments without pretending that host differences do not exist.</p>

<p>The IBM i Sales Workspace is an experiment in that direction. The standalone app and Copilot integration are already separate, testable entry points over shared MCP capabilities, and Claude Desktop demonstrates the same MCP App resource in a compatible assistant host.</p>

<p>For more detail, see the <a href="https://modelcontextprotocol.io/extensions/apps/overview">MCP Apps documentation</a>, the official <a href="https://github.com/modelcontextprotocol/ext-apps"><code class="language-plaintext highlighter-rouge">modelcontextprotocol/ext-apps</code> project</a>, and my earlier post on <a href="/blog/2026/02/22/use-mcp-servers-in-react-application/">using MCP servers in a React application</a>.</p>]]></content><author><name>João Mendes</name></author><category term="mcp" /><category term="software-architecture" /><category term="MCP" /><category term="MCP Apps" /><category term="Model Context Protocol" /><category term="React" /><category term="IBM i" /><category term="Copilot" /><category term="AI" /><summary type="html"><![CDATA[MCP Apps let a business application bring its interactive UI into compatible AI hosts while keeping a standalone experience. Here is how my IBM i Sales Workspace explores that model.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://joaojmendesblog.com/assets/images/posts/mcp-ui-apps-ibmi-sales/header.svg" /><media:content medium="image" url="https://joaojmendesblog.com/assets/images/posts/mcp-ui-apps-ibmi-sales/header.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Welcome to My Blog!</title><link href="https://joaojmendesblog.com/blog/2026/02/27/welcome-to-my-blog/" rel="alternate" type="text/html" title="Welcome to My Blog!" /><published>2026-02-27T10:00:00+00:00</published><updated>2026-02-27T10:00:00+00:00</updated><id>https://joaojmendesblog.com/blog/2026/02/27/welcome-to-my-blog</id><content type="html" xml:base="https://joaojmendesblog.com/blog/2026/02/27/welcome-to-my-blog/"><![CDATA[<p>Hello world! Welcome to my blog!</p>

<p>I’m excited to start this journey of sharing my thoughts, experiences, and knowledge about technology and software development.</p>

<h2 id="what-to-expect">What to Expect</h2>

<p>Here you’ll find posts about:</p>

<ul>
  <li><strong>Web Development</strong> — Tips, tricks, and tutorials</li>
  <li><strong>Software Architecture</strong> — Design patterns and best practices</li>
  <li><strong>New Technologies</strong> — Reviews and explorations of the latest tools</li>
  <li><strong>Personal Projects</strong> — Deep dives into things I’m building</li>
</ul>

<h2 id="why-blog">Why Blog?</h2>

<p>Writing helps me organize my thoughts and solidify my understanding of complex topics. If my posts help even one person solve a problem or learn something new, that’s a win!</p>

<h2 id="stay-tuned">Stay Tuned</h2>

<p>I’ll be publishing new content regularly. Feel free to explore the site and check back often for new posts.</p>

<p>Thanks for reading, and welcome aboard!</p>]]></content><author><name>João Mendes</name></author><category term="general" /><category term="welcome" /><category term="first-post" /><summary type="html"><![CDATA[Hello world! This is my first blog post. Welcome to my personal space where I'll share thoughts on technology, development, and more.]]></summary></entry><entry><title type="html">Using MCP Servers in a React Application — A Real-World Example with Microsoft Graph</title><link href="https://joaojmendesblog.com/blog/2026/02/22/use-mcp-servers-in-react-application/" rel="alternate" type="text/html" title="Using MCP Servers in a React Application — A Real-World Example with Microsoft Graph" /><published>2026-02-22T18:40:00+00:00</published><updated>2026-02-22T18:40:00+00:00</updated><id>https://joaojmendesblog.com/blog/2026/02/22/use-mcp-servers-in-react-application</id><content type="html" xml:base="https://joaojmendesblog.com/blog/2026/02/22/use-mcp-servers-in-react-application/"><![CDATA[<p><img src="/assets/images/posts/use-mcp-servers-in-react-application/thumbnail.png" alt="Using MCP Servers in React Applications — React logo connected to MCP Server connected to Microsoft 365" /></p>

<h2 id="introduction">Introduction</h2>

<p>The <strong>Model Context Protocol (MCP)</strong> is an open protocol introduced by Anthropic that standardises how AI models — and any client application — connect to external tools and data sources. While the protocol gained widespread attention in the AI assistant world, its real power is as a <strong>universal, typed, and discoverable API layer</strong> that you can call from <em>any</em> front-end application, including React.</p>

<p>In this post I will walk you through a production-grade React component called <strong>My Team</strong> that uses an MCP server as its sole data source. The server wraps Microsoft Graph API calls and exposes them as typed MCP tools. The React app authenticates with Microsoft Entra ID (via MSAL), acquires a bearer token, connects to the MCP server over HTTP, and calls tools such as <code class="language-plaintext highlighter-rouge">get_user</code>, <code class="language-plaintext highlighter-rouge">get_manager</code>, and <code class="language-plaintext highlighter-rouge">get_direct_reports</code>.</p>

<p>The finished UI looks like this:</p>

<p><img src="/assets/images/posts/use-mcp-servers-in-react-application/MyTeam.png" alt="My Team component showing João Mendes as top manger with a grid of 16 direct reports" /></p>

<p>By the end of this post you will understand:</p>

<ul>
  <li>How the <strong>MCP client</strong> connects to a server from the browser</li>
  <li>How custom React hooks abstract authentication, transport, and logging</li>
  <li>Which <strong>React hooks</strong> are used and <em>why</em></li>
  <li>How <strong>infinite scroll pagination</strong> and <strong>dynamic page sizing</strong> are implemented</li>
  <li>The full architecture from the browser to Microsoft Graph</li>
</ul>

<hr />

<h2 id="architecture-overview">Architecture Overview</h2>

<p>The diagram below shows every layer of the solution, from the React component tree down to Microsoft 365.</p>

<p><img src="/assets/images/posts/use-mcp-servers-in-react-application/architecture.png" alt="Architecture diagram showing the flow from the MyTeam React component through BrowserHTTPTransport to the MCP Server tools, then to Microsoft Graph API endpoints, and finally to Microsoft 365" /></p>

<blockquote>
  <p><strong>Key insight:</strong> The React app never calls Microsoft Graph directly. All data access is delegated to the MCP server. The browser only needs to know the MCP server URL and how to obtain a bearer token — the tool schema, pagination tokens, and Graph API details stay on the server side.</p>
</blockquote>

<hr />

<h2 id="project-structure">Project Structure</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>src/
├── atoms/                    # Jotai atoms (global state slices)
│   ├── authStateAtom.ts
│   └── mcpStateAtom.ts
├── config/
│   └── authConfig.ts         # MSAL config &amp; loginRequest scopes
├── hooks/
│   ├── useAuth.ts            # MSAL authentication hook
│   ├── useMCPClient.ts       # MCP client hook
│   ├── useLogging.ts         # Structured logging hook
│   ├── useIndexedDBCache.ts  # IndexedDB caching hook
│   └── BrowserHTTPTransport.ts  # Custom MCP transport
└── components/
    └── myTeam/
        ├── IMyTeamProps.ts   # Props interface
        ├── MyTeam.tsx        # Main component
        ├── MyTeamSkeleton.tsx # Loading skeleton
        ├── useMyTeamStyles.ts # Emotion CSS-in-JS styles
        └── index.ts          # Public exports
</code></pre></div></div>

<hr />

<h2 id="custom-hooks-deep-dive">Custom Hooks Deep Dive</h2>

<h3 id="usemcpclient--the-mcp-transport-layer"><code class="language-plaintext highlighter-rouge">useMCPClient</code> — The MCP Transport Layer</h3>

<p>This is the most important hook in the solution. It wraps the official <code class="language-plaintext highlighter-rouge">@modelcontextprotocol/sdk</code> client and exposes a clean API to React components.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useCallback</span><span class="p">,</span> <span class="nx">useRef</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useAtom</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">jotai</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Client</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@modelcontextprotocol/sdk/client/index.js</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">mcpStateAtom</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../atoms</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useIndexedDBCache</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./useIndexedDBCache</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">BrowserHTTPTransport</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./BrowserHTTPTransport</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">useMCPClient</span><span class="p">():</span> <span class="nx">IUseMCPClientReturn</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">mcpState</span><span class="p">,</span> <span class="nx">setMcpState</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useAtom</span><span class="p">(</span><span class="nx">mcpStateAtom</span><span class="p">);</span>

  <span class="c1">// 1-hour TTL for the tools list (avoids repeated /tools requests)</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">getData</span><span class="p">,</span> <span class="nx">setData</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useIndexedDBCache</span><span class="o">&lt;</span><span class="nx">IMCPTool</span><span class="p">[]</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>

  <span class="c1">// Persist the SDK Client and Transport across re-renders without</span>
  <span class="c1">// triggering re-renders when they change</span>
  <span class="kd">const</span> <span class="nx">clientRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o">&lt;</span><span class="nx">Client</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">transportRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o">&lt;</span><span class="nx">BrowserHTTPTransport</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>

  <span class="c1">// Deduplicate in-flight requests that arrive concurrently</span>
  <span class="kd">const</span> <span class="nx">pendingRequestsRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o">&lt;</span><span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">unknown</span><span class="o">&gt;&gt;&gt;</span><span class="p">(</span><span class="k">new</span> <span class="nb">Map</span><span class="p">());</span>

  <span class="kd">const</span> <span class="nx">connect</span> <span class="o">=</span> <span class="nx">useCallback</span><span class="p">(</span><span class="k">async</span> <span class="p">(</span><span class="nx">options</span><span class="p">:</span> <span class="nx">IMCPConnectOptions</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">transport</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BrowserHTTPTransport</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">url</span><span class="p">,</span>
      <span class="na">headers</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">headers</span> <span class="o">??</span> <span class="p">{},</span>
      <span class="na">timeout</span><span class="p">:</span> <span class="mi">30</span><span class="nx">_000</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span>
      <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">browser-mcp-client</span><span class="dl">'</span><span class="p">,</span> <span class="na">version</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1.0.0</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">capabilities</span><span class="p">:</span> <span class="p">{}</span> <span class="p">}</span>
    <span class="p">);</span>

    <span class="nx">transportRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="nx">transport</span><span class="p">;</span>
    <span class="nx">clientRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="nx">client</span><span class="p">;</span>

    <span class="c1">// Performs the MCP initialisation handshake</span>
    <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">transport</span><span class="p">);</span>

    <span class="nx">setMcpState</span><span class="p">(</span><span class="nx">prev</span> <span class="o">=&gt;</span> <span class="p">({</span>
      <span class="p">...</span><span class="nx">prev</span><span class="p">,</span>
      <span class="na">isConnected</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="na">isConnecting</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
      <span class="na">sessionId</span><span class="p">:</span> <span class="nx">transport</span><span class="p">.</span><span class="nx">getSessionId</span><span class="p">()</span> <span class="o">??</span> <span class="dl">''</span><span class="p">,</span>
      <span class="na">baseUrl</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">url</span><span class="p">,</span>
      <span class="na">headers</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">headers</span> <span class="o">??</span> <span class="p">{},</span>
    <span class="p">}));</span>
  <span class="p">},</span> <span class="p">[</span><span class="nx">setMcpState</span><span class="p">]);</span>

  <span class="kd">const</span> <span class="nx">callTool</span> <span class="o">=</span> <span class="nx">useCallback</span><span class="p">(</span>
    <span class="k">async</span> <span class="p">(</span><span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">args</span><span class="p">:</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nx">unknown</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">clientRef</span><span class="p">.</span><span class="nx">current</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Not connected. Call connect() first.</span><span class="dl">'</span><span class="p">);</span>

      <span class="k">return</span> <span class="nx">clientRef</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">callTool</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="nx">toolName</span><span class="p">,</span> <span class="na">arguments</span><span class="p">:</span> <span class="nx">args</span> <span class="p">});</span>
    <span class="p">},</span>
    <span class="p">[]</span>
  <span class="p">);</span>

  <span class="c1">// Clean up the SDK client when the component unmounts</span>
  <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">clientRef</span><span class="p">.</span><span class="nx">current</span><span class="p">?.</span><span class="nx">close</span><span class="p">();</span>
      <span class="nx">transportRef</span><span class="p">.</span><span class="nx">current</span><span class="p">?.</span><span class="nx">close</span><span class="p">();</span>
    <span class="p">};</span>
  <span class="p">},</span> <span class="p">[</span><span class="nx">disconnect</span><span class="p">]);</span>

  <span class="k">return</span> <span class="p">{</span> <span class="na">isConnected</span><span class="p">:</span> <span class="nx">mcpState</span><span class="p">.</span><span class="nx">isConnected</span><span class="p">,</span> <span class="nx">connect</span><span class="p">,</span> <span class="nx">callTool</span><span class="p">,</span> <span class="nx">listTools</span><span class="p">,</span> <span class="nx">disconnect</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>React hooks used in <code class="language-plaintext highlighter-rouge">useMCPClient</code>:</strong></p>

<ul>
  <li><strong>useRef</strong> — Hold the <code class="language-plaintext highlighter-rouge">Client</code> and <code class="language-plaintext highlighter-rouge">BrowserHTTPTransport</code> instances without causing re-renders when they are assigned</li>
  <li><strong>useRef</strong> (second) — In-memory Map for concurrent request deduplication</li>
  <li><strong>useCallback</strong> — Stable references for <code class="language-plaintext highlighter-rouge">connect</code>, <code class="language-plaintext highlighter-rouge">callTool</code>, <code class="language-plaintext highlighter-rouge">disconnect</code>, and <code class="language-plaintext highlighter-rouge">listTools</code> so they can safely appear in dependency arrays</li>
  <li><strong>useEffect</strong> — Cleanup — close the SDK client and transport when the hook’s owner component unmounts</li>
  <li><strong>useAtom</strong> (Jotai) — Read and write the global <code class="language-plaintext highlighter-rouge">mcpState</code> (connection status, session ID, tools list, errors)</li>
</ul>

<hr />

<h3 id="useauth--msal-token-acquisition"><code class="language-plaintext highlighter-rouge">useAuth</code> — MSAL Token Acquisition</h3>

<p><code class="language-plaintext highlighter-rouge">useAuth</code> wraps <code class="language-plaintext highlighter-rouge">@azure/msal-react</code> and adds automatic silent token refresh so the rest of the application never has to think about expiry.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span><span class="p">,</span> <span class="nx">useCallback</span><span class="p">,</span> <span class="nx">useRef</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useMsal</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@azure/msal-react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useAtom</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">jotai</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">authStateAtom</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../atoms</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">TOKEN_EXPIRY_BUFFER</span> <span class="o">=</span> <span class="mi">5</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">;</span> <span class="c1">// refresh 5 min before real expiry</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">useAuth</span><span class="p">():</span> <span class="nx">IUseAuthReturn</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">instance</span><span class="p">,</span> <span class="nx">accounts</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useMsal</span><span class="p">();</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">authState</span><span class="p">,</span> <span class="nx">setAuthState</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useAtom</span><span class="p">(</span><span class="nx">authStateAtom</span><span class="p">);</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">isLoading</span><span class="p">,</span> <span class="nx">setIsLoading</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">tokenExpiryRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o">&lt;</span><span class="kr">number</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">isRefreshingRef</span>  <span class="o">=</span> <span class="nx">useRef</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">acquireToken</span> <span class="o">=</span> <span class="nx">useCallback</span><span class="p">(</span>
    <span class="k">async</span> <span class="p">(</span><span class="nx">request</span><span class="p">:</span> <span class="nx">RedirectRequest</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">string</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">account</span> <span class="o">=</span> <span class="nx">accounts</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">account</span><span class="p">)</span> <span class="k">return</span> <span class="kc">undefined</span><span class="p">;</span>

      <span class="k">try</span> <span class="p">{</span>
        <span class="c1">// Silent-first: use cached token from MSAL token cache</span>
        <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">instance</span><span class="p">.</span><span class="nx">acquireTokenSilent</span><span class="p">({</span> <span class="p">...</span><span class="nx">request</span><span class="p">,</span> <span class="nx">account</span> <span class="p">});</span>
        <span class="nx">tokenExpiryRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">expiresOn</span><span class="p">?.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">??</span> <span class="kc">null</span><span class="p">;</span>
        <span class="k">return</span> <span class="nx">result</span><span class="p">.</span><span class="nx">accessToken</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="c1">// Fallback: interactive redirect when silent fails (e.g. consent required)</span>
        <span class="k">await</span> <span class="nx">instance</span><span class="p">.</span><span class="nx">acquireTokenRedirect</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">},</span>
    <span class="p">[</span><span class="nx">instance</span><span class="p">,</span> <span class="nx">accounts</span><span class="p">]</span>
  <span class="p">);</span>

  <span class="c1">// Poll every 5 minutes to refresh the token proactively</span>
  <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">interval</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isRefreshingRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">&amp;&amp;</span> <span class="nx">isTokenExpiringSoon</span><span class="p">())</span> <span class="p">{</span>
        <span class="nx">isRefreshingRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
        <span class="k">await</span> <span class="nx">acquireToken</span><span class="p">({</span> <span class="na">scopes</span><span class="p">:</span> <span class="nx">loginRequest</span><span class="p">.</span><span class="nx">scopes</span><span class="p">,</span> <span class="na">forceRefresh</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
        <span class="nx">isRefreshingRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">},</span> <span class="nx">TOKEN_EXPIRY_BUFFER</span><span class="p">);</span>

    <span class="k">return</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">interval</span><span class="p">);</span>
  <span class="p">},</span> <span class="p">[</span><span class="nx">acquireToken</span><span class="p">]);</span>

  <span class="k">return</span> <span class="p">{</span> <span class="na">isAuthenticated</span><span class="p">:</span> <span class="o">!!</span><span class="nx">accounts</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">acquireToken</span><span class="p">,</span> <span class="nx">isLoading</span><span class="p">,</span> <span class="na">user</span><span class="p">:</span> <span class="nx">authState</span><span class="p">.</span><span class="nx">user</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>React hooks used in <code class="language-plaintext highlighter-rouge">useAuth</code>:</strong></p>

<ul>
  <li><strong>useState</strong> — Track the <code class="language-plaintext highlighter-rouge">isLoading</code> flag for the login/logout UI</li>
  <li><strong>useRef</strong> — Store <code class="language-plaintext highlighter-rouge">tokenExpiryRef</code> (expiry timestamp) and <code class="language-plaintext highlighter-rouge">isRefreshingRef</code> (guard against parallel refreshes) without triggering renders</li>
  <li><strong>useCallback</strong> — Stable references for <code class="language-plaintext highlighter-rouge">acquireToken</code>, <code class="language-plaintext highlighter-rouge">login</code>, <code class="language-plaintext highlighter-rouge">logout</code>, and <code class="language-plaintext highlighter-rouge">isTokenExpiringSoon</code></li>
  <li><strong>useEffect</strong> — Start the background interval that proactively refreshes the token; clean up with <code class="language-plaintext highlighter-rouge">clearInterval</code> on unmount</li>
  <li><strong>useAtom</strong> (Jotai) — Sync authenticated user information into global <code class="language-plaintext highlighter-rouge">authStateAtom</code></li>
</ul>

<hr />

<h3 id="uselogging--structured-logging"><code class="language-plaintext highlighter-rouge">useLogging</code> — Structured Logging</h3>

<p><code class="language-plaintext highlighter-rouge">useLogging</code> provides levelled logging (<code class="language-plaintext highlighter-rouge">DEBUG</code>, <code class="language-plaintext highlighter-rouge">INFO</code>, <code class="language-plaintext highlighter-rouge">WARN</code>, <code class="language-plaintext highlighter-rouge">ERROR</code>, <code class="language-plaintext highlighter-rouge">FATAL</code>), an in-memory log history, and an optional remote endpoint. Every log entry is enriched with a timestamp, function name, and contextual data.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useCallback</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">useLogging</span><span class="p">(</span><span class="nx">config</span><span class="p">:</span> <span class="nx">ILoggingConfig</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">logInfo</span> <span class="o">=</span> <span class="nx">useCallback</span><span class="p">(</span>
    <span class="p">(</span><span class="nx">functionName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">additionalData</span><span class="p">?:</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nx">unknown</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="na">entry</span><span class="p">:</span> <span class="nx">ILogEntry</span> <span class="o">=</span> <span class="p">{</span>
        <span class="na">timestamp</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
        <span class="na">level</span><span class="p">:</span> <span class="nx">LogLevel</span><span class="p">.</span><span class="nx">INFO</span><span class="p">,</span>
        <span class="nx">functionName</span><span class="p">,</span>
        <span class="nx">message</span><span class="p">,</span>
        <span class="nx">additionalData</span><span class="p">,</span>
      <span class="p">};</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">`[INFO] [</span><span class="p">${</span><span class="nx">functionName</span><span class="p">}</span><span class="s2">] </span><span class="p">${</span><span class="nx">message</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">additionalData</span> <span class="o">??</span> <span class="dl">''</span><span class="p">);</span>
      <span class="nx">logHistory</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">entry</span><span class="p">);</span>
    <span class="p">},</span>
    <span class="p">[]</span>
  <span class="p">);</span>

  <span class="c1">// logDebug / logWarning / logError / logFatal follow the same pattern</span>
  <span class="k">return</span> <span class="p">{</span> <span class="nx">logDebug</span><span class="p">,</span> <span class="nx">logInfo</span><span class="p">,</span> <span class="nx">logWarning</span><span class="p">,</span> <span class="nx">logError</span><span class="p">,</span> <span class="nx">logFatal</span><span class="p">,</span> <span class="nx">getLogHistory</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>React hooks used in <code class="language-plaintext highlighter-rouge">useLogging</code>:</strong></p>

<ul>
  <li><strong>useCallback</strong> — Every <code class="language-plaintext highlighter-rouge">logX</code> function is memoised so that components that spread them into dependency arrays do not re-render unnecessarily</li>
</ul>

<hr />

<h2 id="the-myteam-component">The <code class="language-plaintext highlighter-rouge">MyTeam</code> Component</h2>

<p>Now that the hooks are clear, let’s look at how they are composed inside a single React component.</p>

<h3 id="props--state">Props &amp; State</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">IMyTeamProps</span> <span class="p">{</span>
  <span class="nl">userId</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span> <span class="c1">// UPN or object ID of the user to show</span>
<span class="p">}</span>

<span class="c1">// Inside the component</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">isLoading</span><span class="p">,</span> <span class="nx">setIsLoading</span><span class="p">]</span>           <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">boolean</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">currentUser</span><span class="p">,</span> <span class="nx">setCurrentUser</span><span class="p">]</span>       <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">ITeamMember</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">directReports</span><span class="p">,</span> <span class="nx">setDirectReports</span><span class="p">]</span>   <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">ITeamMember</span><span class="p">[]</span><span class="o">&gt;</span><span class="p">([]);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">skipToken</span><span class="p">,</span> <span class="nx">setSkipToken</span><span class="p">]</span>           <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">undefined</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">hasNextPage</span><span class="p">,</span> <span class="nx">setHasNextPage</span><span class="p">]</span>       <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">isLoadingMore</span><span class="p">,</span> <span class="nx">setIsLoadingMore</span><span class="p">]</span>   <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">isFetching</span><span class="p">,</span> <span class="nx">setIsFetching</span><span class="p">]</span>         <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">containerHeight</span><span class="p">,</span> <span class="nx">setContainerHeight</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="mi">600</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">error</span><span class="p">,</span> <span class="nx">setError</span><span class="p">]</span>                   <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="dynamic-page-size-with-usememo">Dynamic Page Size with <code class="language-plaintext highlighter-rouge">useMemo</code></h3>

<p>Instead of hard-coding a <code class="language-plaintext highlighter-rouge">$top</code> value, the component measures its own height and calculates how many rows are visible:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ITEM_HEIGHT</span>    <span class="o">=</span> <span class="mi">200</span><span class="p">;</span> <span class="c1">// approximate card height in px</span>
<span class="kd">const</span> <span class="nx">HEADER_OFFSET</span>  <span class="o">=</span> <span class="mi">250</span><span class="p">;</span> <span class="c1">// space taken by the current-user card + header</span>

<span class="kd">const</span> <span class="nx">pageSize</span> <span class="o">=</span> <span class="nx">useMemo</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">visibleRows</span>  <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">((</span><span class="nx">containerHeight</span> <span class="o">-</span> <span class="nx">HEADER_OFFSET</span><span class="p">)</span> <span class="o">/</span> <span class="nx">ITEM_HEIGHT</span><span class="p">);</span>
  <span class="c1">// Pre-load 2× the visible area for smooth scrolling</span>
  <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="nx">visibleRows</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
<span class="p">},</span> <span class="p">[</span><span class="nx">containerHeight</span><span class="p">]);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">useMemo</code> ensures the calculation only runs when <code class="language-plaintext highlighter-rouge">containerHeight</code> changes — not on every render.</p>

<h3 id="observing-container-size-with-useeffect--resizeobserver">Observing Container Size with <code class="language-plaintext highlighter-rouge">useEffect</code> + <code class="language-plaintext highlighter-rouge">ResizeObserver</code></h3>

<p>Because the component can live inside a resizable dashboard tile, it listens to dimension changes:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">containerRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o">&lt;</span><span class="nx">HTMLDivElement</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>

<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">container</span> <span class="o">=</span> <span class="nx">containerRef</span><span class="p">.</span><span class="nx">current</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">container</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ResizeObserver</span><span class="p">(</span><span class="nx">entries</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">entry</span> <span class="k">of</span> <span class="nx">entries</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">height</span> <span class="o">=</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">contentRect</span><span class="p">.</span><span class="nx">height</span><span class="p">;</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">height</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="nx">setContainerHeight</span><span class="p">(</span><span class="nx">height</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">container</span><span class="p">);</span>

  <span class="c1">// Cleanup: always disconnect the observer to avoid memory leaks</span>
  <span class="k">return</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">observer</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
<span class="p">},</span> <span class="p">[]);</span> <span class="c1">// Empty array = set up once on mount</span>
</code></pre></div></div>

<h3 id="preventing-double-fetch-with-useref">Preventing Double-Fetch with <code class="language-plaintext highlighter-rouge">useRef</code></h3>

<p>React 18’s Strict Mode mounts components twice in development. A <code class="language-plaintext highlighter-rouge">useRef</code> flag guards against fetching twice without introducing complex state:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">hasFetchedRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>

<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">userId</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">hasFetchedRef</span><span class="p">.</span><span class="nx">current</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">hasFetchedRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="nx">loadTeamData</span><span class="p">();</span>
  <span class="p">}</span>
<span class="p">},</span> <span class="p">[</span><span class="nx">userId</span><span class="p">]);</span>
</code></pre></div></div>

<h3 id="initial-data-load--connecting-to-the-mcp-server">Initial Data Load — Connecting to the MCP Server</h3>

<p>The initial load sequence follows four steps:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">loadTeamData</span> <span class="o">=</span> <span class="k">async</span> <span class="p">():</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">setIsLoading</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>

  <span class="c1">// 1. Acquire bearer token via MSAL (silent → interactive fallback)</span>
  <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">acquireToken</span><span class="p">(</span><span class="nx">loginRequest</span><span class="p">);</span>

  <span class="c1">// 2. Connect to the MCP server, passing the token in the Authorization header</span>
  <span class="k">await</span> <span class="nx">connect</span><span class="p">({</span>
    <span class="na">url</span><span class="p">:</span> <span class="nx">MCP_SERVER_URL</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">},</span>
  <span class="p">});</span>

  <span class="c1">// 3. Call the get_user tool to resolve the current user's details</span>
  <span class="kd">const</span> <span class="nx">userResult</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">callTool</span><span class="p">(</span><span class="dl">'</span><span class="s1">get_user</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">userId</span> <span class="p">});</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">user</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">userResult</span><span class="p">.</span><span class="nx">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">text</span><span class="p">);</span>
  <span class="nx">setCurrentUser</span><span class="p">(</span><span class="nx">user</span><span class="p">);</span>

  <span class="c1">// 4. Call get_manager (optional — user may be at the top of the org)</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">managerResult</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">callTool</span><span class="p">(</span><span class="dl">'</span><span class="s1">get_manager</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">userId</span> <span class="p">});</span>
    <span class="kd">const</span> <span class="nx">manager</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">managerResult</span><span class="p">.</span><span class="nx">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">text</span><span class="p">);</span>
    <span class="c1">// Use manager.id to fetch peers (direct reports of the manager)</span>
    <span class="k">await</span> <span class="nx">fetchPeers</span><span class="p">(</span><span class="nx">manager</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
    <span class="nx">logInfo</span><span class="p">(</span><span class="dl">'</span><span class="s1">loadTeamData</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">No manager found — user is at top level</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="c1">// 5. Fetch first page of direct reports</span>
  <span class="k">await</span> <span class="nx">fetchDirectReports</span><span class="p">();</span>

  <span class="nx">setIsLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="infinite-scroll-pagination">Infinite Scroll Pagination</h3>

<p>The MCP server returns an OData-style <code class="language-plaintext highlighter-rouge">@odata.nextLink</code> or a <code class="language-plaintext highlighter-rouge">skipToken</code> property when there are more pages. The component appends pages as the user scrolls:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">handleScroll</span> <span class="o">=</span> <span class="nx">useCallback</span><span class="p">(</span>
  <span class="p">(</span><span class="nx">e</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">UIEvent</span><span class="o">&lt;</span><span class="nx">HTMLDivElement</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">{</span> <span class="nx">scrollTop</span><span class="p">,</span> <span class="nx">scrollHeight</span><span class="p">,</span> <span class="nx">clientHeight</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">scrollRatio</span> <span class="o">=</span> <span class="p">(</span><span class="nx">scrollTop</span> <span class="o">+</span> <span class="nx">clientHeight</span><span class="p">)</span> <span class="o">/</span> <span class="nx">scrollHeight</span><span class="p">;</span>

    <span class="c1">// Trigger the next page load when 80 % of the content is visible</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">scrollRatio</span> <span class="o">&gt;=</span> <span class="mf">0.8</span> <span class="o">&amp;&amp;</span> <span class="nx">hasNextPage</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">isLoadingMore</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">isFetching</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">fetchDirectReports</span><span class="p">(</span><span class="nx">skipToken</span><span class="p">,</span> <span class="cm">/* isLoadMore */</span> <span class="kc">true</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">},</span>
  <span class="p">[</span><span class="nx">hasNextPage</span><span class="p">,</span> <span class="nx">isLoadingMore</span><span class="p">,</span> <span class="nx">isFetching</span><span class="p">,</span> <span class="nx">skipToken</span><span class="p">,</span> <span class="nx">fetchDirectReports</span><span class="p">]</span>
<span class="p">);</span>
</code></pre></div></div>

<p>When <code class="language-plaintext highlighter-rouge">isLoadMore</code> is <code class="language-plaintext highlighter-rouge">true</code>, new results are appended rather than replacing the list:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">isLoadMore</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">setDirectReports</span><span class="p">(</span><span class="nx">prev</span> <span class="o">=&gt;</span> <span class="p">[...</span><span class="nx">prev</span><span class="p">,</span> <span class="p">...</span><span class="nx">parseResult</span><span class="p">.</span><span class="nx">reports</span><span class="p">]);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  <span class="nx">setDirectReports</span><span class="p">(</span><span class="nx">parseResult</span><span class="p">.</span><span class="nx">reports</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="loading-skeleton">Loading Skeleton</h3>

<p>While the initial fetch is in progress, <code class="language-plaintext highlighter-rouge">MyTeamSkeleton</code> renders Fluent UI skeleton cards instead of a spinner, providing a content-aware loading state:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">isLoading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nc">MyTeamSkeleton</span> <span class="na">count</span><span class="p">=</span><span class="si">{</span><span class="mi">6</span><span class="si">}</span> <span class="p">/&gt;;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">MyTeamSkeleton</code> uses the same <code class="language-plaintext highlighter-rouge">useMyTeamV2Styles</code> hook so the layout is consistent during and after loading.</p>

<hr />

<h2 id="all-react-hooks-at-a-glance">All React Hooks at a Glance</h2>

<h3 id="myteam-component">MyTeam component</h3>

<ul>
  <li><strong>useState</strong> — <code class="language-plaintext highlighter-rouge">currentUser</code>, <code class="language-plaintext highlighter-rouge">directReports</code>, <code class="language-plaintext highlighter-rouge">skipToken</code>, <code class="language-plaintext highlighter-rouge">hasNextPage</code>, <code class="language-plaintext highlighter-rouge">isLoading</code>, <code class="language-plaintext highlighter-rouge">isLoadingMore</code>, <code class="language-plaintext highlighter-rouge">isFetching</code>, <code class="language-plaintext highlighter-rouge">containerHeight</code>, <code class="language-plaintext highlighter-rouge">error</code></li>
  <li><strong>useEffect</strong> — Mount the <code class="language-plaintext highlighter-rouge">ResizeObserver</code>; trigger initial data load once</li>
  <li><strong>useCallback</strong> — Stable references for <code class="language-plaintext highlighter-rouge">parseDirectReports</code>, <code class="language-plaintext highlighter-rouge">fetchDirectReports</code>, <code class="language-plaintext highlighter-rouge">handleLoadMore</code>, <code class="language-plaintext highlighter-rouge">handleScroll</code>, and action handlers</li>
  <li><strong>useMemo</strong> — Calculate <code class="language-plaintext highlighter-rouge">pageSize</code> from <code class="language-plaintext highlighter-rouge">containerHeight</code></li>
  <li><strong>useRef</strong> — <code class="language-plaintext highlighter-rouge">containerRef</code> (DOM node), <code class="language-plaintext highlighter-rouge">hasFetchedRef</code> (double-fetch guard)</li>
  <li><strong>React.memo</strong> — Skip re-renders when props have not changed</li>
</ul>

<h3 id="usemcpclient-hook">useMCPClient hook</h3>

<ul>
  <li><strong>useRef</strong> — <code class="language-plaintext highlighter-rouge">clientRef</code>, <code class="language-plaintext highlighter-rouge">transportRef</code>, <code class="language-plaintext highlighter-rouge">pendingRequestsRef</code></li>
  <li><strong>useCallback</strong> — <code class="language-plaintext highlighter-rouge">connect</code>, <code class="language-plaintext highlighter-rouge">disconnect</code>, <code class="language-plaintext highlighter-rouge">listTools</code>, <code class="language-plaintext highlighter-rouge">callTool</code>, <code class="language-plaintext highlighter-rouge">selectTool</code>, <code class="language-plaintext highlighter-rouge">clearCache</code></li>
  <li><strong>useEffect</strong> — Disconnect and clean up on unmount</li>
</ul>

<h3 id="useauth-hook">useAuth hook</h3>

<ul>
  <li><strong>useCallback</strong> — <code class="language-plaintext highlighter-rouge">acquireToken</code>, <code class="language-plaintext highlighter-rouge">login</code>, <code class="language-plaintext highlighter-rouge">logout</code>, <code class="language-plaintext highlighter-rouge">isTokenExpiringSoon</code></li>
  <li><strong>useRef</strong> — <code class="language-plaintext highlighter-rouge">tokenExpiryRef</code>, <code class="language-plaintext highlighter-rouge">isRefreshingRef</code></li>
  <li><strong>useEffect</strong> — Background token refresh interval</li>
</ul>

<h3 id="uselogging-hook">useLogging hook</h3>

<ul>
  <li><strong>useCallback</strong> — All <code class="language-plaintext highlighter-rouge">logX</code> functions</li>
</ul>

<hr />

<h2 id="setting-up-the-mcp-client-in-your-own-react-app">Setting Up the MCP Client in Your Own React App</h2>

<p>Follow these steps to embed an MCP server in a React project:</p>

<h3 id="1-install-the-sdk-and-msal">1. Install the SDK and MSAL</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span> @modelcontextprotocol/sdk @azure/msal-browser @azure/msal-react jotai
</code></pre></div></div>

<h3 id="2-configure-msal">2. Configure MSAL</h3>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/config/authConfig.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Configuration</span><span class="p">,</span> <span class="nx">LogLevel</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@azure/msal-browser</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">msalConfig</span><span class="p">:</span> <span class="nx">Configuration</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">auth</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">clientId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">&lt;your-app-client-id&gt;</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">authority</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://login.microsoftonline.com/&lt;your-tenant-id&gt;</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">redirectUri</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span><span class="p">,</span>
  <span class="p">},</span>
  <span class="na">cache</span><span class="p">:</span> <span class="p">{</span> <span class="na">cacheLocation</span><span class="p">:</span> <span class="dl">'</span><span class="s1">sessionStorage</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">};</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">loginRequest</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">scopes</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">api://&lt;your-mcp-server-app-id&gt;/user_impressonation</span><span class="dl">'</span><span class="p">],</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="3-implement-browserhttptransport">3. Implement <code class="language-plaintext highlighter-rouge">BrowserHTTPTransport</code></h3>

<p>The official MCP SDK ships a Node.js <code class="language-plaintext highlighter-rouge">StreamableHTTPClientTransport</code>. For the browser you need a fetch-based equivalent:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/hooks/BrowserHTTPTransport.ts</span>
<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">Transport</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@modelcontextprotocol/sdk/shared/transport.js</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">JSONRPCMessage</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@modelcontextprotocol/sdk/types.js</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">BrowserHTTPTransport</span> <span class="k">implements</span> <span class="nx">Transport</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">_url</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="nx">_headers</span><span class="p">:</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">;</span>
  <span class="k">private</span> <span class="nx">_sessionId</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">options</span><span class="p">:</span> <span class="p">{</span> <span class="nl">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">headers</span><span class="p">:</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">;</span> <span class="nl">timeout</span><span class="p">?:</span> <span class="kr">number</span> <span class="p">})</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">_url</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">url</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">_headers</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">headers</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">async</span> <span class="nx">send</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="nx">JSONRPCMessage</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_url</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
        <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">mcp-session-id</span><span class="dl">'</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">_sessionId</span> <span class="o">??</span> <span class="dl">''</span><span class="p">,</span>
        <span class="p">...</span><span class="k">this</span><span class="p">.</span><span class="nx">_headers</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">message</span><span class="p">),</span>
    <span class="p">});</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">sessionHeader</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">mcp-session-id</span><span class="dl">'</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">sessionHeader</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="nx">_sessionId</span> <span class="o">=</span> <span class="nx">sessionHeader</span><span class="p">;</span>

      <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">onmessage</span><span class="p">?.(</span><span class="nx">data</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">getSessionId</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">_sessionId</span><span class="p">;</span> <span class="p">}</span>

  <span class="nl">onessage</span><span class="p">?:</span> <span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="nx">JSONRPCMessage</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
  <span class="nl">onerror</span><span class="p">?:</span>   <span class="p">(</span><span class="nx">error</span><span class="p">:</span> <span class="nb">Error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
  <span class="nl">onclose</span><span class="p">?:</span>   <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>

  <span class="k">async</span> <span class="nx">start</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/* no-op for HTTP */</span> <span class="p">}</span>
  <span class="k">async</span> <span class="nx">close</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">_sessionId</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">onclose</span><span class="p">?.();</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="4-create-the-jotai-atom">4. Create the Jotai Atom</h3>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/atoms/mcpStateAtom.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">atom</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">jotai</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kr">interface</span> <span class="nx">IMCPState</span> <span class="p">{</span>
  <span class="nl">isConnected</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>
  <span class="nl">isConnecting</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>
  <span class="nl">sessionId</span><span class="p">?:</span>  <span class="kr">string</span><span class="p">;</span>
  <span class="nl">tools</span><span class="p">:</span>       <span class="nx">IMCPTool</span><span class="p">[];</span>
  <span class="nl">error</span><span class="p">?:</span>      <span class="kr">string</span><span class="p">;</span>
  <span class="nl">baseUrl</span><span class="p">?:</span>    <span class="kr">string</span><span class="p">;</span>
  <span class="nl">headers</span><span class="p">?:</span>    <span class="nb">Record</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">mcpStateAtom</span> <span class="o">=</span> <span class="nx">atom</span><span class="o">&lt;</span><span class="nx">IMCPState</span><span class="o">&gt;</span><span class="p">({</span>
  <span class="na">isConnected</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
  <span class="na">isConnecting</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
  <span class="na">tools</span><span class="p">:</span> <span class="p">[],</span>
<span class="p">});</span>
</code></pre></div></div>

<h3 id="5-wrap-your-application">5. Wrap Your Application</h3>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/index.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">MsalProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@azure/msal-react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Provider</span> <span class="k">as</span> <span class="nx">JotaiProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">jotai</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PublicClientApplication</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@azure/msal-browser</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">msalConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./config/authConfig</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">msalInstance</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PublicClientApplication</span><span class="p">(</span><span class="nx">msalConfig</span><span class="p">);</span>

<span class="nx">root</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span>
  <span class="p">&lt;</span><span class="nc">MsalProvider</span> <span class="na">instance</span><span class="p">=</span><span class="si">{</span><span class="nx">msalInstance</span><span class="si">}</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">JotaiProvider</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">App</span> <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nc">JotaiProvider</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nc">MsalProvider</span><span class="p">&gt;</span>
<span class="p">);</span>
</code></pre></div></div>

<h3 id="6-use-the-hook">6. Use the Hook</h3>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useAuth</span> <span class="p">}</span>      <span class="k">from</span> <span class="dl">'</span><span class="s1">./hooks/useAuth</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useMCPClient</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./hooks/useMCPClient</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">loginRequest</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./config/authConfig</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">MY_MCP_SERVER</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">https://my-mcp-server/mcp</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">MyComponent</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FC</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">acquireToken</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useAuth</span><span class="p">();</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">connect</span><span class="p">,</span> <span class="nx">callTool</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useMCPClient</span><span class="p">();</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">unknown</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>

  <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">acquireToken</span><span class="p">(</span><span class="nx">loginRequest</span><span class="p">);</span>
      <span class="k">await</span> <span class="nx">connect</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="nx">MY_MCP_SERVER</span><span class="p">,</span> <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">}</span> <span class="p">});</span>

      <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">callTool</span><span class="p">(</span><span class="dl">'</span><span class="s1">get_user</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">userId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">me</span><span class="dl">'</span> <span class="p">});</span>
      <span class="nx">setData</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">text</span><span class="p">));</span>
    <span class="p">})();</span>
  <span class="p">},</span> <span class="p">[]);</span>

  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">pre</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">pre</span><span class="p">&gt;;</span>
<span class="p">};</span>
</code></pre></div></div>

<hr />

<h2 id="key-takeaways">Key Takeaways</h2>

<ul>
  <li><strong>MCP as a universal API layer</strong> — your React component never imports any Graph SDK. Tools are discoverable, typed, and versioned on the server.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">useRef</code> for mutable values that must not trigger renders</strong> — the SDK <code class="language-plaintext highlighter-rouge">Client</code> instance, transport, and request-deduplication map all live in refs.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">useMemo</code> for derived state</strong> — the page size is computed from container dimensions, cached by <code class="language-plaintext highlighter-rouge">useMemo</code>, and passed as a tool argument.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">useCallback</code> everywhere</strong> — all async handlers are wrapped in <code class="language-plaintext highlighter-rouge">useCallback</code> so they can appear in <code class="language-plaintext highlighter-rouge">useEffect</code> dependency arrays without causing infinite loops.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">ResizeObserver</code> in a <code class="language-plaintext highlighter-rouge">useEffect</code></strong> — a clean teardown pattern (return <code class="language-plaintext highlighter-rouge">() =&gt; observer.disconnect()</code>) prevents memory leaks.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">React.memo</code></strong> — wrapping both <code class="language-plaintext highlighter-rouge">MyTeamV2</code> and <code class="language-plaintext highlighter-rouge">MyTeamSkeleton</code> prevents unnecessary re-renders when parent state changes.</li>
  <li><strong>Skeleton loading</strong> — matching the layout of real cards during load eliminates Cumulative Layout Shift (CLS).</li>
  <li><strong>IndexedDB caching</strong> — the tools list is cached for one hour. The bearer token changes per session; the tool schema does not.</li>
</ul>

<hr />

<h2 id="resources">Resources</h2>

<ul>
  <li><a href="https://modelcontextprotocol.io">Model Context Protocol — Official Documentation</a></li>
  <li><a href="https://www.npmjs.com/package/@modelcontextprotocol/sdk">@modelcontextprotocol/sdk on npm</a></li>
  <li><a href="https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react">MSAL React — Microsoft Authentication Library</a></li>
  <li><a href="https://jotai.org">Jotai — Primitive and flexible state management for React</a></li>
  <li><a href="https://react.fluentui.dev">Fluent UI React Components</a></li>
  <li><a href="https://learn.microsoft.com/graph/api/resources/user">Microsoft Graph API — Users resource type</a></li>
</ul>]]></content><author><name>João Mendes</name></author><category term="react" /><category term="mcp" /><category term="React" /><category term="MCP" /><category term="Model Context Protocol" /><category term="Microsoft Graph" /><category term="TypeScript" /><summary type="html"><![CDATA[Learn how to connect a React app to an MCP server that wraps Microsoft Graph, using custom hooks, MSAL authentication, infinite scroll pagination, and more.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://joaojmendesblog.com/assets/images/posts/use-mcp-servers-in-react-application/thumbnail.png" /><media:content medium="image" url="https://joaojmendesblog.com/assets/images/posts/use-mcp-servers-in-react-application/thumbnail.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>