<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://marwashli.github.io/articles-tech/feed.xml" rel="self" type="application/atom+xml" /><link href="https://marwashli.github.io/articles-tech/" rel="alternate" type="text/html" hreflang="fr" /><updated>2026-10-28T17:18:50+00:00</updated><id>https://marwashli.github.io/articles-tech/feed.xml</id><title type="html">Notes techniques</title><subtitle>Articles sur l&apos;architecture, la sécurité et les API.</subtitle><author><name>marwa SAHLI</name></author><entry xml:lang="en"><title type="html">From business need to frontend: AI-generated mockups and code with Specification-Driven Design</title><link href="https://marwashli.github.io/articles-tech/en/2026/10/specification-driven-design-ai/" rel="alternate" type="text/html" title="From business need to frontend: AI-generated mockups and code with Specification-Driven Design" /><published>2026-10-28T00:00:00+00:00</published><updated>2026-10-28T00:00:00+00:00</updated><id>https://marwashli.github.io/articles-tech/en/2026/10/specification-driven-design-ai</id><content type="html" xml:base="https://marwashli.github.io/articles-tech/en/2026/10/specification-driven-design-ai/"><![CDATA[<h1 id="from-business-need-to-frontend-ai-generated-mockups-and-code-with-specification-driven-design">From business need to frontend: AI-generated mockups and code with Specification-Driven Design</h1>

<p>“We need a screen where employees can request time off and managers can approve it.”</p>

<p>A few years ago, that sentence kicked off weeks of workshops, wireframes, back-and-forth with designers, and then development. Today, an AI agent can produce credible mockups and the matching React code in a few minutes. The problem is no longer <em>producing</em>: it is producing <strong>the right thing</strong>, in a way that is <strong>consistent</strong>, <strong>reproducible</strong> and <strong>under control</strong>.</p>

<p>That is exactly what <strong>Specification-Driven Design</strong> (or <em>Spec-Driven Development</em>, SDD) offers: making the specification, rather than the prompt or the code, the source of truth for the whole chain. This article shows how to apply it end to end, from business need to frontend, with particular attention to the real benefits and to the issues you should not ignore.</p>

<hr />

<h2 id="contents">Contents</h2>

<ol>
  <li><a href="#1-the-problem-with-vibe-coding">The problem with “vibe coding”</a></li>
  <li><a href="#2-the-principle-of-specification-driven-design">The principle of Specification-Driven Design</a></li>
  <li><a href="#3-pipeline-overview">Pipeline overview</a></li>
  <li><a href="#4-step-by-step-walkthrough">Step-by-step walkthrough</a></li>
  <li><a href="#5-repository-layout">Repository layout</a></li>
  <li><a href="#6-available-tooling">Available tooling</a></li>
  <li><a href="#7-the-benefits">The benefits</a></li>
  <li><a href="#8-being-aware-of-what-is-at-stake">Being aware of what is at stake</a></li>
  <li><a href="#9-where-humans-fit-the-control-grid">Where humans fit: the control grid</a></li>
  <li><a href="#10-where-to-start">Where to start</a></li>
  <li><a href="#11-conclusion">Conclusion</a></li>
</ol>

<hr />

<h2 id="1-the-problem-with-vibe-coding">1. The problem with “vibe coding”</h2>

<p>The most intuitive way to use AI to build an interface is to describe what you want in a chat, look at the result, then fix it in small touches: “put the button on the right”, “no, lighter”, “add a filter”. This is informally called <em>vibe coding</em>.</p>

<p>It works very well for a throwaway prototype. It works much less well as soon as you are building a real product:</p>

<ul>
  <li><strong>Intent gets lost.</strong> Decisions are scattered across a chat history nobody will read again. Why is this field mandatory? Nobody remembers.</li>
  <li><strong>Results are not reproducible.</strong> The same prompt, run again tomorrow or with another model, produces a different interface.</li>
  <li><strong>Consistency erodes.</strong> Each screen is generated in isolation; colours, spacing and behaviours drift apart.</li>
  <li><strong>Review is hard.</strong> You cannot validate a business need by reading 2,000 lines of JSX.</li>
  <li><strong>Edge cases are forgotten.</strong> AI happily draws the happy path; empty, error and loading states, access rights and accessibility fall through the cracks.</li>
</ul>

<p>Specification-Driven Design answers these five problems with a simple idea: <strong>you stop writing prompts and start writing specifications.</strong></p>

<hr />

<h2 id="2-the-principle-of-specification-driven-design">2. The principle of Specification-Driven Design</h2>

<p>SDD rests on three convictions.</p>

<p><strong>1. The specification is the source of truth.</strong> Mockups and code are <em>derived artifacts</em> that can be regenerated from the spec. If the result is not right, you don’t patch the code by hand: you fix the spec and regenerate.</p>

<p><strong>2. The specification is structured and progressive.</strong> You don’t go from one business sentence to code in a single step. You refine through successive layers, each readable and approvable by the right person:</p>

<table>
  <thead>
    <tr>
      <th>Layer</th>
      <th>Question it answers</th>
      <th>Reviewed by</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Business need</td>
      <td>Why? For whom?</td>
      <td>Business, product owner</td>
    </tr>
    <tr>
      <td>Functional spec</td>
      <td>What? Under which rules?</td>
      <td>Product owner, business</td>
    </tr>
    <tr>
      <td>Experience spec</td>
      <td>How does the user go about it?</td>
      <td>UX designer</td>
    </tr>
    <tr>
      <td>Design system</td>
      <td>What does it look like?</td>
      <td>UI designer</td>
    </tr>
    <tr>
      <td>Technical plan</td>
      <td>With which architecture?</td>
      <td>Tech lead</td>
    </tr>
  </tbody>
</table>

<p><strong>3. The specification lives in the Git repository.</strong> These are Markdown, JSON or YAML files, versioned alongside the code, subject to the same pull requests and the same review. Git history becomes the history of decisions.</p>

<p>The approach took shape in 2025 with the arrival of dedicated tools (GitHub Spec Kit, AWS’s Kiro, OpenSpec, BMAD-METHOD, Tessl…), which all offer, with variations, the same sequence: <strong>specify → clarify → plan → break into tasks → implement</strong>.</p>

<hr />

<h2 id="3-pipeline-overview">3. Pipeline overview</h2>

<p><img src="/articles-tech/assets/sdd/pipeline-sdd-en.svg" alt="Spec-driven pipeline: from business need (1) to functional spec (2), experience spec (3), design system (4), generated mockups (5), technical plan (6), frontend code (7) and verification (8), with human approvals and a feedback loop to the spec" /></p>

<p>Three families of elements stand out:</p>

<ul>
  <li><strong>In blue, the specifications</strong>: written or co-written with AI, approved by a human, versioned. They are authoritative.</li>
  <li><strong>In purple, the generated artifacts</strong>: mockups and code, produced by AI from the specs. They are disposable and can be regenerated.</li>
  <li><strong>In green, the automated checks</strong>: tests, accessibility audit, visual comparison.</li>
</ul>

<p>The orange diamonds mark the <strong>human approval points</strong>. The dotted arrow is the golden rule: when verification reveals a gap, <strong>go back to the spec</strong>.</p>

<hr />

<h2 id="4-step-by-step-walkthrough">4. Step-by-step walkthrough</h2>

<p>Running example: an internal <strong>time-off request</strong> application.</p>

<h3 id="step-0--write-the-project-constitution">Step 0 — Write the project “constitution”</h3>

<p>Before any feature, you write down once and for all the non-negotiable rules the AI must follow on every generation. Spec Kit calls this file the <em>constitution</em>; other tools talk about <em>steering</em> or project rules.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/constitution.md --&gt;</span>

<span class="gh"># Project constitution</span>

<span class="gu">## Stack</span>
<span class="p">
-</span> React 19 + strict TypeScript, Vite, React Router.
<span class="p">-</span> UI components: only those in <span class="sb">`src/design-system/`</span>. No hard-coded styles.
<span class="p">-</span> API calls: only through the client generated from <span class="sb">`contracts/openapi.yaml`</span>.

<span class="gu">## Quality</span>
<span class="p">
-</span> Accessibility: WCAG 2.2 level AA is mandatory.
<span class="p">-</span> Every screen explicitly handles the states: loading, empty, error, success.
<span class="p">-</span> Every acceptance criterion in the spec has at least one end-to-end test.

<span class="gu">## Forbidden</span>
<span class="p">
-</span> No new npm dependency unless it is listed in the plan and approved by a human.
<span class="p">-</span> No real personal data in fixtures or mockups.
<span class="p">-</span> No secrets or API keys in the code.
</code></pre></div></div>

<p>This file is the single most cost-effective tool in the whole approach: it saves you from repeating the same instructions in every prompt, and it makes deviations detectable.</p>

<h3 id="step-1--express-the-business-need">Step 1 — Express the business need</h3>

<p>The starting point is still natural-language text, written by the business. It doesn’t need to be perfect; above all, it must say <strong>why</strong>.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-time-off/brief.md --&gt;</span>

<span class="gh"># Need: managing time-off requests</span>

Today, time-off requests go through email and a shared spreadsheet.
Managers lose requests, employees don't know their balance,
and HR re-keys everything at the end of the month.

We want:
<span class="p">
-</span> an employee to be able to submit a request and see their balance in real time;
<span class="p">-</span> their manager to be notified and able to approve or reject in one click;
<span class="p">-</span> HR to be able to export the month's approved leave.

Constraints: usable on mobile, compliant with our accessibility rules.
Success metric: less than 2 minutes to submit a request.
</code></pre></div></div>

<h3 id="step-2--generate-then-clarify-the-functional-spec">Step 2 — Generate, then clarify the functional spec</h3>

<p>You ask the agent to turn the need into a structured specification: roles, user stories, business rules, testable acceptance criteria. A constrained notation helps a lot; Kiro, for example, uses <strong>EARS</strong> (<em>Easy Approach to Requirements Syntax</em>), which enforces forms such as “WHEN… THE SYSTEM SHALL…”.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-time-off/spec.md (excerpt) --&gt;</span>

<span class="gu">## Roles</span>
<span class="p">
-</span> Employee, Manager, HR.

<span class="gu">## US-01 — Submit a request</span>

As an employee, I want to submit a time-off request
so that I can plan my absence without going through email.

<span class="gu">### Acceptance criteria</span>
<span class="p">
-</span> AC-01.1 WHEN the employee opens the form,
  THE SYSTEM SHALL display their available balance per leave type.
<span class="p">-</span> AC-01.2 IF the end date is before the start date,
  THEN THE SYSTEM SHALL block submission and display an error on the relevant field.
<span class="p">-</span> AC-01.3 IF the number of days requested exceeds the balance,
  THEN THE SYSTEM SHALL warn without blocking submission.
<span class="p">-</span> AC-01.4 WHEN the request is submitted,
  THE SYSTEM SHALL display a confirmation and notify the manager.

<span class="gu">## Open questions</span>
<span class="p">
-</span> [ ] Are half days allowed?
<span class="p">-</span> [ ] Can a manager approve their own request?
</code></pre></div></div>

<p>The <strong>“Open questions”</strong> section is essential. Rather than letting the AI decide silently, you explicitly ask it to list its ambiguities: this is Spec Kit’s <em>clarify</em> step. The business answers them, the spec is updated, then <strong>approved</strong> (first human gate).</p>

<h3 id="step-3--specify-the-experience">Step 3 — Specify the experience</h3>

<p>This layer describes journeys and screens <strong>without talking about appearance yet</strong>: which screens, which information, which actions, which states.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-time-off/ux.md (excerpt) --&gt;</span>

<span class="gu">## Journey J1 — Submit a request (employee, mobile first)</span>

Home → "New request" → Form → Summary → Confirmation

<span class="gu">## Screen S2 — Request form</span>

Content:
<span class="p">
-</span> "My balance" card (per leave type) → AC-01.1
<span class="p">-</span> Leave type (list), start date, end date, half day (if approved)
<span class="p">-</span> Optional comment (500 characters max)
  Actions: "Review my request" (primary), "Cancel" (secondary)

States:
<span class="p">
-</span> Loading: skeleton for the balance card, form disabled
<span class="p">-</span> Balance failed to load: message + "Try again" button
<span class="p">-</span> Validation error: message under the field, focus moved to it → AC-01.2
<span class="p">-</span> Balance exceeded: non-blocking warning banner → AC-01.3
</code></pre></div></div>

<p>Notice the <strong>traceability</strong>: every screen element points back to the acceptance criterion that justifies it. That is what will later make it possible to check automatically that nothing has been forgotten.</p>

<h3 id="step-4--formalise-the-design-system">Step 4 — Formalise the design system</h3>

<p>To stop the AI from reinventing a style on every screen, the visual identity must <strong>also be a specification</strong>. The standard format is the W3C <strong>design tokens</strong> format: in October 2025 the <em>Design Tokens Community Group</em> published the first stable version of its specification (2025.10), already supported by Figma, Penpot, Sketch, Style Dictionary, Tokens Studio and Terrazzo.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"color"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"brand"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"primary"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"$type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"color"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"$value"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"colorSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"srgb"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"components"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.145</span><span class="p">,</span><span class="w"> </span><span class="mf">0.388</span><span class="p">,</span><span class="w"> </span><span class="mf">0.922</span><span class="p">],</span><span class="w">
          </span><span class="nl">"hex"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#2563eb"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"feedback"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"warning"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"$type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"color"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"$value"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"colorSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"srgb"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"components"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.706</span><span class="p">,</span><span class="w"> </span><span class="mf">0.325</span><span class="p">,</span><span class="w"> </span><span class="mf">0.035</span><span class="p">],</span><span class="w">
          </span><span class="nl">"hex"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#b45309"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"space"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"md"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"$type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dimension"</span><span class="p">,</span><span class="w"> </span><span class="nl">"$value"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span><span class="w"> </span><span class="nl">"unit"</span><span class="p">:</span><span class="w"> </span><span class="s2">"px"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You add the <strong>catalogue of allowed components</strong> (button, field, card, banner…) with their variants and usage rules. A major advantage: a single token file can generate web CSS variables and iOS and Android resources, and feed the mockup tool.</p>

<p>If the company already has a design system, reuse it as is: that is actually where the approach is most effective.</p>

<h3 id="step-5--generate-the-mockups">Step 5 — Generate the mockups</h3>

<p>You now have everything you need to generate <strong>constrained</strong> mockups: the agent receives the experience spec (which screens, which states) and the design system (which components, which tokens). It produces:</p>

<ul>
  <li>one screen per entry in <code class="language-plaintext highlighter-rouge">ux.md</code>, <strong>with each of its states</strong> (loading, empty, error, success);</li>
  <li>in mobile and desktop versions;</li>
  <li>using only components from the catalogue.</li>
</ul>

<p>Depending on the tools, mockups are produced directly in a design tool (Figma Make, for example, builds on the team’s existing component libraries), in an interface generation tool (Google Stitch, Uizard, Visily, UX Pilot), or as an HTML prototype. Thanks to MCP, a coding agent can also read from and write to the design tool.</p>

<p>This is the second human gate, and probably the most important one: <strong>the designer and the business approve the mockups</strong>. A comment (“the balance should be visible before even opening the form”) is not fixed in the mockup: it becomes a change to <code class="language-plaintext highlighter-rouge">ux.md</code>, and then you regenerate.</p>

<h3 id="step-6--plan-the-implementation">Step 6 — Plan the implementation</h3>

<p>The agent then produces a <strong>technical plan</strong> that respects the constitution, followed by a <strong>list of small, ordered tasks</strong>.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-time-off/tasks.md (excerpt) --&gt;</span>
<span class="p">
-</span> [ ] T01 Generate the API client from contracts/openapi.yaml (endpoints /leaves, /balances)
<span class="p">-</span> [ ] T02 Create the /time-off/new route and the page skeleton (S2)
<span class="p">-</span> [ ] T03 LeaveBalanceCard component: loading / error / success states (AC-01.1)
<span class="p">-</span> [ ] T04 Form: date validation, error message, focus management (AC-01.2)
<span class="p">-</span> [ ] T05 Balance-exceeded warning banner (AC-01.3)
<span class="p">-</span> [ ] T06 Playwright tests AC-01.1 → AC-01.4 + axe-core audit on S2
</code></pre></div></div>

<p>The <strong>API contract</strong> (OpenAPI) deserves a mention: it is a specification in its own right, shared with the backend team. The frontend can be built and tested against a mock server generated from this contract, before the API even exists.</p>

<h3 id="step-7--generate-the-code">Step 7 — Generate the code</h3>

<p>The coding agent (Claude Code, Copilot, Cursor, Codex, Gemini CLI, Kiro…) works through the tasks one by one. Each task is small, referenced and verifiable, which greatly limits drift.</p>

<p>You keep a <strong>human code review</strong> on pull requests: it no longer focuses on formatting, but on what AI misses most often (security, error handling, added dependencies, performance).</p>

<h3 id="step-8--verify-automatically">Step 8 — Verify automatically</h3>

<p>Verification closes the loop. Because each acceptance criterion is phrased in a testable way, it translates directly into a test:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// e2e/time-off/us-01.spec.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">test</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@playwright/test</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">AxeBuilder</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@axe-core/playwright</span><span class="dl">"</span><span class="p">;</span>

<span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">AC-01.2 — an end date before the start date blocks submission</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">({</span>
  <span class="nx">page</span><span class="p">,</span>
<span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="dl">"</span><span class="s2">/time-off/new</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="dl">"</span><span class="s2">Start date</span><span class="dl">"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">2026-11-10</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="dl">"</span><span class="s2">End date</span><span class="dl">"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">2026-11-05</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="dl">"</span><span class="s2">button</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Review my request</span><span class="dl">"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">();</span>

  <span class="kd">const</span> <span class="nx">end</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="dl">"</span><span class="s2">End date</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">end</span><span class="p">).</span><span class="nx">toHaveAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">aria-invalid</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">true</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">end</span><span class="p">).</span><span class="nx">toBeFocused</span><span class="p">();</span>
<span class="p">});</span>

<span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">S2 — no WCAG A/AA accessibility violations</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="dl">"</span><span class="s2">/time-off/new</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nx">AxeBuilder</span><span class="p">({</span> <span class="nx">page</span> <span class="p">})</span>
    <span class="p">.</span><span class="nx">withTags</span><span class="p">([</span><span class="dl">"</span><span class="s2">wcag2a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">wcag2aa</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">wcag21aa</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">wcag22aa</span><span class="dl">"</span><span class="p">])</span>
    <span class="p">.</span><span class="nx">analyze</span><span class="p">();</span>
  <span class="nx">expect</span><span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">violations</span><span class="p">).</span><span class="nx">toEqual</span><span class="p">([]);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Add to this:</p>

<ul>
  <li>a <strong>visual comparison</strong> between screenshots of the frontend and the approved mockups;</li>
  <li>a <strong>traceability check</strong>: a script verifies that every <code class="language-plaintext highlighter-rouge">AC-xx</code> in the spec is covered by at least one test;</li>
  <li>a <strong>design system compliance check</strong>: no hard-coded colours or sizes.</li>
</ul>

<p>When a check fails, the rule stays the same: work out whether the error comes from the spec or from the generation, and fix it at the source.</p>

<hr />

<h2 id="5-repository-layout">5. Repository layout</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>my-app/
├── specs/
│   ├── constitution.md            # non-negotiable rules
│   ├── design-system/
│   │   ├── tokens.json            # design tokens (DTCG format)
│   │   └── components.md          # catalogue and usage rules
│   └── 001-time-off/
│       ├── brief.md               # business need
│       ├── spec.md                # stories + criteria (approved)
│       ├── ux.md                  # journeys, screens, states
│       ├── plan.md                # architecture
│       └── tasks.md               # ordered tasks
├── contracts/
│   └── openapi.yaml               # API contract
├── mockups/                       # generated artifacts (or a link to the design tool)
├── src/                           # generated and reviewed code
└── e2e/                           # tests derived from acceptance criteria
</code></pre></div></div>

<p>Each feature has its own numbered spec folder; a pull request contains both the spec change and the regenerated code, so you can review <strong>the intent and its implementation</strong> in the same place.</p>

<hr />

<h2 id="6-available-tooling">6. Available tooling</h2>

<p>The ecosystem moves very fast; this overview gives landmarks by category rather than a ranking.</p>

<table>
  <thead>
    <tr>
      <th>Need</th>
      <th>Example tools</th>
      <th>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>SDD framework</td>
      <td>GitHub Spec Kit, Kiro, OpenSpec, BMAD-METHOD, Tessl</td>
      <td>Spec Kit is open source and works with most agents; Kiro is a full IDE tied to the AWS ecosystem; BMAD is heavier, for large projects</td>
    </tr>
    <tr>
      <td>Coding agents</td>
      <td>Claude Code, GitHub Copilot, Cursor, Codex, Gemini CLI</td>
      <td>Execute the tasks, read the constitution</td>
    </tr>
    <tr>
      <td>Mockup generation</td>
      <td>Figma Make, Google Stitch, Uizard, Visily, UX Pilot</td>
      <td>Figma Make builds on your libraries; Stitch is still an experimental Google Labs product</td>
    </tr>
    <tr>
      <td>Interface generation as code</td>
      <td>v0, Lovable, Bolt</td>
      <td>Very fast; need guardrails to respect your design system</td>
    </tr>
    <tr>
      <td>Design tokens</td>
      <td>Style Dictionary, Tokens Studio, Terrazzo</td>
      <td>Reference implementations of the DTCG format</td>
    </tr>
    <tr>
      <td>Verification</td>
      <td>Playwright, axe-core, visual regression tools</td>
      <td>Essential to close the loop</td>
    </tr>
    <tr>
      <td>Connecting tools</td>
      <td>MCP servers (design tool, ticketing, repository)</td>
      <td>Let the agent read from and write to your tools</td>
    </tr>
  </tbody>
</table>

<p>A good reflex: choose tools that store specs <strong>in plain text in the repository</strong> (Markdown, JSON). That is what limits vendor lock-in.</p>

<hr />

<h2 id="7-the-benefits">7. The benefits</h2>

<p><strong>Speed without sacrificing consistency.</strong> The time from an expressed need to a first working screen drops from weeks to hours, but unlike vibe coding, every screen follows the same design system and the same rules.</p>

<p><strong>Explicit, traceable decisions.</strong> Every choice is written down, reviewed and versioned. Six months later, you know why a field exists and which criterion it satisfies. That is invaluable for onboarding, maintenance and audits.</p>

<p><strong>Review at the right level.</strong> The business reviews a business document, the designer reviews mockups, the developer reviews code. Nobody has to decipher an artifact outside their area.</p>

<p><strong>Edge cases handled by design.</strong> Because the experience spec requires error, loading and empty states, and the constitution requires accessibility, these topics are no longer forgotten until the end of the project.</p>

<p><strong>Independence from the model.</strong> A well-written spec can be re-run with another agent or a newer model. You build up value in specs, not in ephemeral prompts.</p>

<p><strong>Better collaboration.</strong> The business need, the spec and the mockups become a shared language between business, design and engineering. Mockups generated very early let you validate with users before writing any code.</p>

<p><strong>Tests that flow from the spec.</strong> Acceptance criteria become tests; functional coverage becomes measurable.</p>

<p><strong>Easier evolution.</strong> Adding a business rule means changing the spec and regenerating the affected parts, rather than hunting for where to intervene in the code.</p>

<hr />

<h2 id="8-being-aware-of-what-is-at-stake">8. Being aware of what is at stake</h2>

<p>End-to-end automation is appealing. It also carries risks, some of them new. Knowing them is what separates controlled adoption from a gamble.</p>

<h3 id="garbage-in-garbage-out">“Garbage in, garbage out”</h3>

<p>AI amplifies the quality of the spec, in both directions. An ambiguous spec produces a plausible but wrong interface, all the more misleading because it looks finished. <strong>Writing good specs becomes the core skill</strong>, and it is real work. SDD does not remove the analysis effort: it moves it upstream.</p>

<h3 id="the-illusion-of-fully-automated">The illusion of “fully automated”</h3>

<p><em>Production</em> can be fully automated. <em>Decisions</em> should not be. A chain with no human gate will approve its own mistakes: an AI that writes the spec, generates the code and writes the tests can produce a perfectly consistent whole… that misses the point. At a minimum you need a human to approve the spec, one to approve the mockups, and a code review.</p>

<h3 id="waterfall-in-disguise">Waterfall in disguise</h3>

<p>Producing a lot of documents before coding is reminiscent of the V-model, and critics of Spec Kit point to exactly this overhead for small changes. The remedy: <strong>short, incremental</strong> specs (one feature at a time), and generation so fast that you can show mockups to the business on day one. For a one-line fix, there is no need to run the whole pipeline.</p>

<h3 id="drift-between-spec-and-code">Drift between spec and code</h3>

<p>If someone fixes “just a detail” directly in the code, the spec stops being the truth and the next regeneration will overwrite the fix. You need team discipline (fix the spec), possibly backed by tooling (hooks that flag discrepancies, traceability checks in CI).</p>

<h3 id="accessibility-is-not-a-given">Accessibility is not a given</h3>

<p>AI-generated code reproduces the web’s common flaws: missing labels, insufficient contrast, broken keyboard navigation, superfluous ARIA roles. Automated audits only catch some of them; testing with assistive technologies and, ideally, with the users concerned is still necessary. It is also a regulatory issue: in Europe, the <em>European Accessibility Act</em> has applied since June 2025 to many consumer-facing digital services.</p>

<h3 id="security-and-the-supply-chain">Security and the supply chain</h3>

<p>An agent that generates code can introduce an unnecessary, vulnerable or even non-existent dependency (a package name invented by a model can be registered by an attacker). It can also mishandle secrets, or expose data on the client side. Another point to watch: the specs and files the agent reads are a <strong>prompt-injection surface</strong>; malicious content slipped into a spec or a ticket can steer generation. So review specs the way you review code, restrict the agent’s permissions and scan dependencies in CI.</p>

<h3 id="confidentiality-and-intellectual-property">Confidentiality and intellectual property</h3>

<p>The business need, the business rules and sometimes sample data are sent to a hosted model. Check your provider’s contractual terms (retention, reuse of data), never put real personal data in specs or fixtures (GDPR), and think about the rights attached to the generated mockups and code, as well as any resemblance to existing interfaces.</p>

<h3 id="aesthetic-uniformity">Aesthetic uniformity</h3>

<p>Generation tools tend to converge on the same interfaces: the same component libraries, the same layouts, the same “default” look. For an internal application, that is not a problem. For a product that needs to stand out, art direction and a design system of your own remain essential; AI applies an identity, it does not create one for you.</p>

<h3 id="technical-debt-and-maintainability">Technical debt and maintainability</h3>

<p>Generated code can be verbose, duplicated, or rely on different patterns from one generation to the next. A precise constitution (architecture, conventions, allowed libraries), small tasks and human review limit this risk. You should also ask: if the generation tool disappeared tomorrow, could the team maintain this code by hand?</p>

<h3 id="costs-and-footprint">Costs and footprint</h3>

<p>Every regeneration consumes computing resources: a financial cost (tools often bill per credit or per token) and an environmental one. Regenerating a screen ten times for a visual detail is not free. Clear specs reduce the number of iterations.</p>

<h3 id="how-roles-change">How roles change</h3>

<p>SDD does not eliminate designers and developers, but it shifts where their value lies. Designers move from producing screens to designing the system (design system, interaction principles, approval). Developers move from writing components to architecture, review and quality. The business and the product owner take on more responsibility for writing precise specs. This transition requires training and support, especially for junior profiles, who used to learn by producing exactly what AI now produces.</p>

<h3 id="lock-in">Lock-in</h3>

<p>Some tools store specs in a proprietary format, impose their IDE or their model, or discourage manual edits to generated code. Favouring Markdown/JSON specs in the repository and open standards (DTCG design tokens, OpenAPI, MCP) keeps the door open to switching tools.</p>

<hr />

<h2 id="9-where-humans-fit-the-control-grid">9. Where humans fit: the control grid</h2>

<table>
  <thead>
    <tr>
      <th>Step</th>
      <th>Produced by</th>
      <th>Approved by</th>
      <th>Exit criterion</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Constitution</td>
      <td>Tech lead + design lead (with AI)</td>
      <td>Team</td>
      <td>Rules known and accepted</td>
    </tr>
    <tr>
      <td>Business need</td>
      <td>Business</td>
      <td>Product owner</td>
      <td>The “why” and the success metric are clear</td>
    </tr>
    <tr>
      <td>Functional spec</td>
      <td>AI, from the need</td>
      <td><strong>Business + PO</strong></td>
      <td>No open questions, testable criteria</td>
    </tr>
    <tr>
      <td>Experience spec</td>
      <td>AI + UX designer</td>
      <td><strong>UX designer</strong></td>
      <td>All states described, traceability to criteria</td>
    </tr>
    <tr>
      <td>Design system</td>
      <td>Designer (supported by AI tools)</td>
      <td><strong>Design lead</strong></td>
      <td>Tokens in the standard format, compliant contrast</td>
    </tr>
    <tr>
      <td>Mockups</td>
      <td>AI</td>
      <td><strong>Designer + business</strong> (+ users if possible)</td>
      <td>Journeys approved, no component outside the catalogue</td>
    </tr>
    <tr>
      <td>Plan and tasks</td>
      <td>AI</td>
      <td><strong>Tech lead</strong></td>
      <td>Constitution respected, no unjustified dependency</td>
    </tr>
    <tr>
      <td>Code</td>
      <td>AI</td>
      <td><strong>Code review</strong></td>
      <td>Security, errors, performance</td>
    </tr>
    <tr>
      <td>Verification</td>
      <td>Automated</td>
      <td>CI</td>
      <td>All criteria covered, zero blocking a11y violations</td>
    </tr>
  </tbody>
</table>

<p>The simple rule to remember: <strong>AI proposes, a qualified human decides, every time you move to a new layer.</strong></p>

<hr />

<h2 id="10-where-to-start">10. Where to start</h2>

<ol>
  <li><strong>Pick a pilot feature</strong> that is limited and low-risk (an internal screen, a form).</li>
  <li><strong>Write the project constitution</strong>, even a short one: stack, design system, accessibility, forbidden practices.</li>
  <li><strong>Export the existing design system as tokens</strong> in DTCG format. If there isn’t one, start with a palette, a spacing scale and five components.</li>
  <li><strong>Adopt a lightweight SDD framework</strong> (Spec Kit or a simple <code class="language-plaintext highlighter-rouge">specs/</code> folder convention) with the agent the team already uses.</li>
  <li><strong>Require the “Open questions” section</strong>, and generate nothing until it is empty.</li>
  <li><strong>Wire up verification from the start</strong>: tests derived from criteria, accessibility audit, traceability check.</li>
  <li><strong>Measure</strong>: time from need to production, number of round trips, defects found after release. Compare with your usual method before rolling it out more widely.</li>
</ol>

<hr />

<h2 id="11-conclusion">11. Conclusion</h2>

<p>AI has made producing mockups and code almost free. What is becoming rare and valuable is <strong>clarity of intent</strong>. Specification-Driven Design draws the logical conclusion: invest in the specification, let AI derive the artifacts, and put humans at the moments where decisions are made.</p>

<p>Done well, it gets you from a business need to a consistent, accessible, tested frontend in record time. Done badly, it mostly automates the production of well-presented mistakes. The difference comes down to three things: the quality of the specs, the discipline of fixing things at the source, and human approvals in the right places.</p>

<hr />

<h2 id="further-reading">Further reading</h2>

<ul>
  <li><a href="https://glukhov.org/ai-devtools/ai-coding-assistants/spec-kit-vs-kiro-vs-claude-code/">GitHub Spec Kit vs Kiro vs Claude Code: comparing SDD workflows</a></li>
  <li><a href="https://ssojet.com/blog/best-spec-driven-development-tools">7 spec-driven development tools (Spec Kit, Kiro, OpenSpec, Tessl…)</a></li>
  <li><a href="https://www.morphllm.com/spec-driven-development">Spec-driven development with Kiro and AI agents</a></li>
  <li><a href="https://www.w3.org/community/design-tokens/2025/10/28/design-tokens-specification-reaches-first-stable-version/">Design Tokens: first stable version of the specification (W3C DTCG)</a></li>
  <li><a href="https://resources.rework.com/tools/ai-tools/best-ai-tools-for-ui-ux-design-2026">Overview of AI tools for UI/UX in 2026</a></li>
  <li>Previous article on this blog: <a href="/articles-tech/en/2026/10/a2a-protocol/">the A2A protocol and how it compares with MCP</a></li>
</ul>]]></content><author><name>marwa SAHLI</name></author><category term="ai" /><category term="design" /><category term="frontend" /><category term="SDD" /><category term="spec-driven" /><category term="AI" /><category term="mockups" /><category term="design system" /><category term="design tokens" /><category term="frontend" /><category term="agents" /><summary type="html"><![CDATA[From business need to frontend: AI-generated mockups and code with Specification-Driven Design]]></summary></entry><entry xml:lang="fr"><title type="html">Du besoin métier au frontend : maquettes et code générés par l’IA avec le Specification-Driven Design</title><link href="https://marwashli.github.io/articles-tech/2026/10/specification-driven-design-ia/" rel="alternate" type="text/html" title="Du besoin métier au frontend : maquettes et code générés par l’IA avec le Specification-Driven Design" /><published>2026-10-28T00:00:00+00:00</published><updated>2026-10-28T00:00:00+00:00</updated><id>https://marwashli.github.io/articles-tech/2026/10/specification-driven-design-ia</id><content type="html" xml:base="https://marwashli.github.io/articles-tech/2026/10/specification-driven-design-ia/"><![CDATA[<h1 id="du-besoin-métier-au-frontend--maquettes-et-code-générés-par-lia-avec-le-specification-driven-design">Du besoin métier au frontend : maquettes et code générés par l’IA avec le Specification-Driven Design</h1>

<p>« Il nous faudrait un écran pour que les collaborateurs posent leurs congés et que les managers les valident. »</p>

<p>Il y a quelques années, cette phrase déclenchait des semaines d’ateliers, de wireframes, d’allers-retours avec les designers puis de développement. Aujourd’hui, un agent IA peut produire en quelques minutes des maquettes crédibles et le code React qui va avec. Le problème n’est plus de <em>produire</em> : c’est de produire <strong>la bonne chose</strong>, de façon <strong>cohérente</strong>, <strong>reproductible</strong> et <strong>maîtrisée</strong>.</p>

<p>C’est exactement ce que propose le <strong>Specification-Driven Design</strong> (ou <em>Spec-Driven Development</em>, SDD) : faire de la spécification, et non du prompt ou du code, la source de vérité de toute la chaîne. Cet article montre comment l’appliquer de bout en bout, du besoin métier jusqu’au frontend, avec une attention particulière aux bénéfices réels et aux enjeux qu’il ne faut pas ignorer.</p>

<hr />

<h2 id="sommaire">Sommaire</h2>

<ol>
  <li><a href="#1-le-problème-du--vibe-coding-">Le problème du « vibe coding »</a></li>
  <li><a href="#2-le-principe-du-specification-driven-design">Le principe du Specification-Driven Design</a></li>
  <li><a href="#3-vue-densemble-du-pipeline">Vue d’ensemble du pipeline</a></li>
  <li><a href="#4-mode-opératoire-étape-par-étape">Mode opératoire étape par étape</a></li>
  <li><a href="#5-organisation-du-dépôt">Organisation du dépôt</a></li>
  <li><a href="#6-loutillage-disponible">L’outillage disponible</a></li>
  <li><a href="#7-les-avantages">Les avantages</a></li>
  <li><a href="#8-avoir-conscience-des-enjeux">Avoir conscience des enjeux</a></li>
  <li><a href="#9-où-placer-lhumain--la-grille-de-contrôle">Où placer l’humain : la grille de contrôle</a></li>
  <li><a href="#10-par-où-commencer">Par où commencer</a></li>
  <li><a href="#11-conclusion">Conclusion</a></li>
</ol>

<hr />

<h2 id="1-le-problème-du--vibe-coding-">1. Le problème du « vibe coding »</h2>

<p>La manière la plus intuitive d’utiliser l’IA pour créer une interface consiste à décrire ce qu’on veut dans un chat, regarder le résultat, puis corriger par petites touches : « mets le bouton à droite », « non, plus clair », « ajoute un filtre ». C’est ce qu’on appelle familièrement le <em>vibe coding</em>.</p>

<p>Ça fonctionne très bien pour un prototype jetable. Ça fonctionne beaucoup moins bien dès qu’on vise un produit réel :</p>

<ul>
  <li><strong>L’intention se perd.</strong> Les décisions sont dispersées dans un historique de conversation que personne ne relira. Pourquoi ce champ est-il obligatoire ? Personne ne s’en souvient.</li>
  <li><strong>Le résultat n’est pas reproductible.</strong> Le même prompt, relancé demain ou avec un autre modèle, produit une interface différente.</li>
  <li><strong>La cohérence se dégrade.</strong> Chaque écran est généré isolément ; les couleurs, les espacements et les comportements divergent.</li>
  <li><strong>La revue est difficile.</strong> On ne peut pas valider un besoin métier en relisant 2 000 lignes de JSX.</li>
  <li><strong>Les cas limites sont oubliés.</strong> L’IA dessine volontiers le cas nominal ; les états vides, d’erreur ou de chargement, les droits d’accès, l’accessibilité passent à la trappe.</li>
</ul>

<p>Le Specification-Driven Design répond à ces cinq problèmes par une idée simple : <strong>on n’écrit plus de prompts, on écrit des spécifications.</strong></p>

<hr />

<h2 id="2-le-principe-du-specification-driven-design">2. Le principe du Specification-Driven Design</h2>

<p>Le SDD repose sur trois convictions.</p>

<p><strong>1. La spécification est la source de vérité.</strong> Les maquettes et le code sont des <em>artefacts dérivés</em>, régénérables à partir de la spec. Si le résultat ne convient pas, on ne retouche pas le code à la main : on corrige la spec et on régénère.</p>

<p><strong>2. La spécification est structurée et progressive.</strong> On ne passe pas d’une phrase métier à du code en une étape. On raffine par couches successives, chacune lisible et validable par la bonne personne :</p>

<table>
  <thead>
    <tr>
      <th>Couche</th>
      <th>Question à laquelle elle répond</th>
      <th>Relue par</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Besoin métier</td>
      <td>Pourquoi ? Pour qui ?</td>
      <td>Métier, product owner</td>
    </tr>
    <tr>
      <td>Spec fonctionnelle</td>
      <td>Quoi ? Selon quelles règles ?</td>
      <td>Product owner, métier</td>
    </tr>
    <tr>
      <td>Spec d’expérience</td>
      <td>Comment l’utilisateur s’y prend-il ?</td>
      <td>UX designer</td>
    </tr>
    <tr>
      <td>Design system</td>
      <td>À quoi cela ressemble-t-il ?</td>
      <td>UI designer</td>
    </tr>
    <tr>
      <td>Plan technique</td>
      <td>Avec quelle architecture ?</td>
      <td>Tech lead</td>
    </tr>
  </tbody>
</table>

<p><strong>3. La spécification vit dans le dépôt Git.</strong> Ce sont des fichiers Markdown, JSON ou YAML, versionnés à côté du code, soumis aux mêmes pull requests et à la même revue. L’historique Git devient l’historique des décisions.</p>

<p>Ce mouvement s’est structuré en 2025 avec l’apparition d’outils dédiés (GitHub Spec Kit, Kiro d’AWS, OpenSpec, BMAD-METHOD, Tessl…) qui proposent tous, avec des variantes, la même séquence : <strong>spécifier → clarifier → planifier → découper en tâches → implémenter</strong>.</p>

<hr />

<h2 id="3-vue-densemble-du-pipeline">3. Vue d’ensemble du pipeline</h2>

<p><img src="/articles-tech/assets/sdd/pipeline-sdd.svg" alt="Pipeline spec-driven : du besoin métier (1) à la spec fonctionnelle (2), la spec d'expérience (3), le design system (4), les maquettes générées (5), le plan technique (6), le code frontend (7) et la vérification (8), avec des validations humaines et une boucle de retour vers la spec" /></p>

<p>Trois familles d’éléments se distinguent :</p>

<ul>
  <li><strong>En bleu, les spécifications</strong> : écrites ou co-écrites avec l’IA, validées par un humain, versionnées. Elles font foi.</li>
  <li><strong>En violet, les artefacts générés</strong> : maquettes et code, produits par l’IA à partir des specs. Ils sont jetables et régénérables.</li>
  <li><strong>En vert, les contrôles automatiques</strong> : tests, audit d’accessibilité, comparaison visuelle.</li>
</ul>

<p>Les losanges orange marquent les <strong>points de validation humaine</strong>. La flèche en pointillés est la règle d’or : quand la vérification révèle un écart, <strong>on remonte à la spec</strong>.</p>

<hr />

<h2 id="4-mode-opératoire-étape-par-étape">4. Mode opératoire étape par étape</h2>

<p>Fil rouge : une application interne de <strong>demande de congés</strong>.</p>

<h3 id="étape-0--poser-la--constitution--du-projet">Étape 0 — Poser la « constitution » du projet</h3>

<p>Avant toute fonctionnalité, on écrit une fois pour toutes les règles non négociables que l’IA devra respecter à chaque génération. Spec Kit appelle ce fichier la <em>constitution</em> ; d’autres outils parlent de <em>steering</em> ou de règles projet.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/constitution.md --&gt;</span>

<span class="gh"># Constitution du projet</span>

<span class="gu">## Stack</span>
<span class="p">
-</span> React 19 + TypeScript strict, Vite, React Router.
<span class="p">-</span> Composants UI : uniquement ceux de <span class="sb">`src/design-system/`</span>. Aucun style en dur.
<span class="p">-</span> Appels API : uniquement via le client généré depuis <span class="sb">`contracts/openapi.yaml`</span>.

<span class="gu">## Qualité</span>
<span class="p">
-</span> Accessibilité : WCAG 2.2 niveau AA obligatoire.
<span class="p">-</span> Chaque écran gère explicitement les états : chargement, vide, erreur, succès.
<span class="p">-</span> Chaque critère d'acceptation de la spec a au moins un test end-to-end.

<span class="gu">## Interdits</span>
<span class="p">
-</span> Pas de nouvelle dépendance npm sans mention dans le plan et validation humaine.
<span class="p">-</span> Pas de donnée personnelle réelle dans les fixtures et les maquettes.
<span class="p">-</span> Pas de secret ni de clé d'API dans le code.
</code></pre></div></div>

<p>Ce fichier est l’outil le plus rentable de toute la démarche : il évite de répéter les mêmes consignes dans chaque prompt, et il rend les écarts détectables.</p>

<h3 id="étape-1--exprimer-le-besoin-métier">Étape 1 — Exprimer le besoin métier</h3>

<p>Le point de départ reste un texte en langage naturel, écrit par le métier. Pas besoin qu’il soit parfait ; il doit surtout dire <strong>pourquoi</strong>.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-conges/besoin.md --&gt;</span>

<span class="gh"># Besoin : gestion des demandes de congés</span>

Aujourd'hui, les demandes de congés passent par e-mail et un tableur partagé.
Les managers perdent des demandes, les collaborateurs ne connaissent pas leur solde,
et les RH ressaisissent tout en fin de mois.

Nous voulons que :
<span class="p">
-</span> un collaborateur puisse poser une demande et voir son solde en temps réel ;
<span class="p">-</span> son manager soit notifié et puisse valider ou refuser en un clic ;
<span class="p">-</span> les RH puissent exporter les congés validés du mois.

Contraintes : utilisable sur mobile, conforme à nos règles d'accessibilité.
Indicateur de succès : moins de 2 minutes pour poser une demande.
</code></pre></div></div>

<h3 id="étape-2--générer-puis-clarifier-la-spec-fonctionnelle">Étape 2 — Générer puis clarifier la spec fonctionnelle</h3>

<p>On demande à l’agent de transformer le besoin en spécification structurée : rôles, user stories, règles métier, critères d’acceptation testables. Une notation contrainte aide énormément ; Kiro utilise par exemple la notation <strong>EARS</strong> (<em>Easy Approach to Requirements Syntax</em>), qui impose des formes comme « QUAND… LE SYSTÈME DOIT… ».</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-conges/spec.md (extrait) --&gt;</span>

<span class="gu">## Rôles</span>
<span class="p">
-</span> Collaborateur, Manager, RH.

<span class="gu">## US-01 — Poser une demande</span>

En tant que collaborateur, je veux poser une demande de congés
afin de planifier mon absence sans passer par e-mail.

<span class="gu">### Critères d'acceptation</span>
<span class="p">
-</span> AC-01.1 QUAND le collaborateur ouvre le formulaire,
  LE SYSTÈME DOIT afficher son solde disponible par type de congé.
<span class="p">-</span> AC-01.2 SI la date de fin est antérieure à la date de début,
  ALORS LE SYSTÈME DOIT bloquer l'envoi et afficher une erreur sur le champ concerné.
<span class="p">-</span> AC-01.3 SI le nombre de jours demandés dépasse le solde,
  ALORS LE SYSTÈME DOIT avertir sans bloquer l'envoi.
<span class="p">-</span> AC-01.4 QUAND la demande est envoyée,
  LE SYSTÈME DOIT afficher une confirmation et notifier le manager.

<span class="gu">## Questions ouvertes</span>
<span class="p">
-</span> [ ] Les demi-journées sont-elles autorisées ?
<span class="p">-</span> [ ] Un manager peut-il valider sa propre demande ?
</code></pre></div></div>

<p>La section <strong>« Questions ouvertes »</strong> est essentielle. Plutôt que de laisser l’IA trancher en silence, on lui demande explicitement de lister ses ambiguïtés : c’est l’étape <em>clarify</em> de Spec Kit. Le métier y répond, la spec est mise à jour, puis <strong>validée</strong> (première porte humaine).</p>

<h3 id="étape-3--spécifier-lexpérience">Étape 3 — Spécifier l’expérience</h3>

<p>Cette couche décrit les parcours et les écrans <strong>sans parler encore d’apparence</strong> : quels écrans, quelles informations, quelles actions, quels états.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-conges/ux.md (extrait) --&gt;</span>

<span class="gu">## Parcours P1 — Poser une demande (collaborateur, mobile d'abord)</span>

Accueil → « Nouvelle demande » → Formulaire → Récapitulatif → Confirmation

<span class="gu">## Écran E2 — Formulaire de demande</span>

Contenu :
<span class="p">
-</span> Carte « Mon solde » (par type de congé) → AC-01.1
<span class="p">-</span> Type de congé (liste), date de début, date de fin, demi-journée (si validé)
<span class="p">-</span> Commentaire facultatif (500 caractères max)
  Actions : « Vérifier ma demande » (principale), « Annuler » (secondaire)

États :
<span class="p">
-</span> Chargement : squelette de la carte solde, formulaire désactivé
<span class="p">-</span> Erreur de chargement du solde : message + bouton « Réessayer »
<span class="p">-</span> Erreur de validation : message sous le champ, focus déplacé dessus → AC-01.2
<span class="p">-</span> Dépassement de solde : bandeau d'avertissement non bloquant → AC-01.3
</code></pre></div></div>

<p>Remarquez la <strong>traçabilité</strong> : chaque élément d’écran renvoie au critère d’acceptation qui le justifie. C’est ce qui permettra plus tard de vérifier automatiquement que rien n’a été oublié.</p>

<h3 id="étape-4--formaliser-le-design-system">Étape 4 — Formaliser le design system</h3>

<p>Pour que l’IA ne réinvente pas un style à chaque écran, l’identité visuelle doit être <strong>elle aussi une spécification</strong>. Le format standard est celui des <strong>design tokens</strong> du W3C : la <em>Design Tokens Community Group</em> a publié en octobre 2025 la première version stable de sa spécification (2025.10), déjà prise en charge par Figma, Penpot, Sketch, Style Dictionary, Tokens Studio ou Terrazzo.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"color"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"brand"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"primary"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"$type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"color"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"$value"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"colorSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"srgb"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"components"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.145</span><span class="p">,</span><span class="w"> </span><span class="mf">0.388</span><span class="p">,</span><span class="w"> </span><span class="mf">0.922</span><span class="p">],</span><span class="w">
          </span><span class="nl">"hex"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#2563eb"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"feedback"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"warning"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"$type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"color"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"$value"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"colorSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"srgb"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"components"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.706</span><span class="p">,</span><span class="w"> </span><span class="mf">0.325</span><span class="p">,</span><span class="w"> </span><span class="mf">0.035</span><span class="p">],</span><span class="w">
          </span><span class="nl">"hex"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#b45309"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"space"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"md"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"$type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dimension"</span><span class="p">,</span><span class="w"> </span><span class="nl">"$value"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span><span class="w"> </span><span class="nl">"unit"</span><span class="p">:</span><span class="w"> </span><span class="s2">"px"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>On y ajoute le <strong>catalogue des composants</strong> autorisés (bouton, champ, carte, bandeau…) avec leurs variantes et leurs règles d’usage. Avantage majeur : un même fichier de tokens peut générer les variables CSS du web, les ressources iOS et Android, et alimenter l’outil de maquettage.</p>

<p>Si l’entreprise a déjà un design system, on le reprend tel quel : c’est même là que l’approche est la plus efficace.</p>

<h3 id="étape-5--générer-les-maquettes">Étape 5 — Générer les maquettes</h3>

<p>On dispose maintenant de tout ce qu’il faut pour générer des maquettes <strong>contraintes</strong> : l’agent reçoit la spec d’expérience (quels écrans, quels états) et le design system (quels composants, quels tokens). Il produit :</p>

<ul>
  <li>un écran par entrée de <code class="language-plaintext highlighter-rouge">ux.md</code>, <strong>avec chacun de ses états</strong> (chargement, vide, erreur, succès) ;</li>
  <li>en version mobile et desktop ;</li>
  <li>en utilisant exclusivement les composants du catalogue.</li>
</ul>

<p>Selon les outils, les maquettes sont produites directement dans un outil de design (Figma Make s’appuie par exemple sur les bibliothèques de composants existantes de l’équipe), dans un outil de génération d’interfaces (Google Stitch, Uizard, Visily, UX Pilot), ou sous forme de prototype HTML. Grâce à MCP, un agent de code peut aussi lire et écrire dans l’outil de design.</p>

<p>C’est la deuxième porte humaine, et sans doute la plus importante : <strong>le designer et le métier valident les maquettes</strong>. Une remarque (« le solde devrait être visible avant même d’ouvrir le formulaire ») ne se corrige pas dans la maquette : elle se traduit en modification de <code class="language-plaintext highlighter-rouge">ux.md</code>, puis on régénère.</p>

<h3 id="étape-6--planifier-limplémentation">Étape 6 — Planifier l’implémentation</h3>

<p>L’agent produit ensuite un <strong>plan technique</strong> qui respecte la constitution, puis une <strong>liste de tâches</strong> ordonnées et de petite taille.</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- specs/001-conges/tasks.md (extrait) --&gt;</span>
<span class="p">
-</span> [ ] T01 Générer le client API depuis contracts/openapi.yaml (endpoints /leaves, /balances)
<span class="p">-</span> [ ] T02 Créer la route /conges/nouvelle et le squelette de page (E2)
<span class="p">-</span> [ ] T03 Composant LeaveBalanceCard : états chargement / erreur / succès (AC-01.1)
<span class="p">-</span> [ ] T04 Formulaire : validation des dates, message d'erreur, gestion du focus (AC-01.2)
<span class="p">-</span> [ ] T05 Bandeau d'avertissement de dépassement de solde (AC-01.3)
<span class="p">-</span> [ ] T06 Tests Playwright AC-01.1 → AC-01.4 + audit axe-core sur E2
</code></pre></div></div>

<p>Le <strong>contrat d’API</strong> (OpenAPI) mérite une mention : c’est une spécification à part entière, partagée avec l’équipe backend. Le frontend peut être développé et testé contre un serveur simulé généré depuis ce contrat, avant même que l’API existe.</p>

<h3 id="étape-7--générer-le-code">Étape 7 — Générer le code</h3>

<p>L’agent de code (Claude Code, Copilot, Cursor, Codex, Gemini CLI, Kiro…) exécute les tâches une par une. Chaque tâche est petite, référencée et vérifiable, ce qui limite fortement les dérives.</p>

<p>On garde une <strong>revue de code humaine</strong> sur les pull requests : elle ne porte plus sur la mise en forme, mais sur ce que l’IA rate le plus souvent (sécurité, gestion des erreurs, dépendances ajoutées, performances).</p>

<h3 id="étape-8--vérifier-automatiquement">Étape 8 — Vérifier automatiquement</h3>

<p>La vérification ferme la boucle. Comme chaque critère d’acceptation est formulé de façon testable, il se traduit directement en test :</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// e2e/conges/us-01.spec.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">test</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@playwright/test</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">AxeBuilder</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@axe-core/playwright</span><span class="dl">"</span><span class="p">;</span>

<span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">AC-01.2 — une date de fin antérieure bloque l'envoi</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">({</span>
  <span class="nx">page</span><span class="p">,</span>
<span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="dl">"</span><span class="s2">/conges/nouvelle</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="dl">"</span><span class="s2">Date de début</span><span class="dl">"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">2026-11-10</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="dl">"</span><span class="s2">Date de fin</span><span class="dl">"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">2026-11-05</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="dl">"</span><span class="s2">button</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Vérifier ma demande</span><span class="dl">"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">();</span>

  <span class="kd">const</span> <span class="nx">fin</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="dl">"</span><span class="s2">Date de fin</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">fin</span><span class="p">).</span><span class="nx">toHaveAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">aria-invalid</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">true</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">fin</span><span class="p">).</span><span class="nx">toBeFocused</span><span class="p">();</span>
<span class="p">});</span>

<span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">E2 — aucune violation d'accessibilité WCAG A/AA</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">goto</span><span class="p">(</span><span class="dl">"</span><span class="s2">/conges/nouvelle</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nx">AxeBuilder</span><span class="p">({</span> <span class="nx">page</span> <span class="p">})</span>
    <span class="p">.</span><span class="nx">withTags</span><span class="p">([</span><span class="dl">"</span><span class="s2">wcag2a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">wcag2aa</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">wcag21aa</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">wcag22aa</span><span class="dl">"</span><span class="p">])</span>
    <span class="p">.</span><span class="nx">analyze</span><span class="p">();</span>
  <span class="nx">expect</span><span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">violations</span><span class="p">).</span><span class="nx">toEqual</span><span class="p">([]);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>On complète avec :</p>

<ul>
  <li>une <strong>comparaison visuelle</strong> entre les captures du frontend et les maquettes validées ;</li>
  <li>un <strong>contrôle de traçabilité</strong> : un script vérifie que chaque <code class="language-plaintext highlighter-rouge">AC-xx</code> de la spec est couvert par au moins un test ;</li>
  <li>une <strong>vérification de conformité</strong> au design system : aucune couleur ou taille codée en dur.</li>
</ul>

<p>Quand un contrôle échoue, la règle reste la même : on identifie si l’erreur vient de la spec ou de la génération, et on corrige à la source.</p>

<hr />

<h2 id="5-organisation-du-dépôt">5. Organisation du dépôt</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mon-app/
├── specs/
│   ├── constitution.md            # règles non négociables
│   ├── design-system/
│   │   ├── tokens.json            # design tokens (format DTCG)
│   │   └── composants.md          # catalogue et règles d'usage
│   └── 001-conges/
│       ├── besoin.md              # expression métier
│       ├── spec.md                # stories + critères (validée)
│       ├── ux.md                  # parcours, écrans, états
│       ├── plan.md                # architecture
│       └── tasks.md               # tâches ordonnées
├── contracts/
│   └── openapi.yaml               # contrat d'API
├── maquettes/                     # artefacts générés (ou lien vers l'outil de design)
├── src/                           # code généré et revu
└── e2e/                           # tests dérivés des critères d'acceptation
</code></pre></div></div>

<p>Chaque fonctionnalité a son dossier de specs numéroté ; une pull request contient à la fois la modification de la spec et le code régénéré, ce qui permet de relire <strong>l’intention et sa réalisation</strong> au même endroit.</p>

<hr />

<h2 id="6-loutillage-disponible">6. L’outillage disponible</h2>

<p>L’écosystème évolue très vite ; ce panorama donne des repères par catégorie plutôt qu’un classement.</p>

<table>
  <thead>
    <tr>
      <th>Besoin</th>
      <th>Exemples d’outils</th>
      <th>Remarques</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Cadre SDD</td>
      <td>GitHub Spec Kit, Kiro, OpenSpec, BMAD-METHOD, Tessl</td>
      <td>Spec Kit est open source et fonctionne avec la plupart des agents ; Kiro est un IDE complet lié à l’écosystème AWS ; BMAD est plus lourd, pour de gros projets</td>
    </tr>
    <tr>
      <td>Agents de code</td>
      <td>Claude Code, GitHub Copilot, Cursor, Codex, Gemini CLI</td>
      <td>Exécutent les tâches, lisent la constitution</td>
    </tr>
    <tr>
      <td>Génération de maquettes</td>
      <td>Figma Make, Google Stitch, Uizard, Visily, UX Pilot</td>
      <td>Figma Make s’appuie sur vos bibliothèques ; Stitch reste un produit expérimental de Google Labs</td>
    </tr>
    <tr>
      <td>Génération d’interfaces en code</td>
      <td>v0, Lovable, Bolt</td>
      <td>Très rapides ; à encadrer pour respecter votre design system</td>
    </tr>
    <tr>
      <td>Design tokens</td>
      <td>Style Dictionary, Tokens Studio, Terrazzo</td>
      <td>Implémentations de référence du format DTCG</td>
    </tr>
    <tr>
      <td>Vérification</td>
      <td>Playwright, axe-core, outils de régression visuelle</td>
      <td>Indispensables pour fermer la boucle</td>
    </tr>
    <tr>
      <td>Connexion des outils</td>
      <td>Serveurs MCP (outil de design, ticketing, dépôt)</td>
      <td>Permettent à l’agent de lire et d’écrire dans vos outils</td>
    </tr>
  </tbody>
</table>

<p>Bon réflexe : choisir des outils qui stockent les specs <strong>en clair dans le dépôt</strong> (Markdown, JSON). C’est ce qui limite le verrouillage chez un fournisseur.</p>

<hr />

<h2 id="7-les-avantages">7. Les avantages</h2>

<p><strong>Une vitesse qui ne sacrifie pas la cohérence.</strong> Le temps entre un besoin exprimé et un premier écran fonctionnel passe de semaines à quelques heures, mais contrairement au vibe coding, tous les écrans respectent le même design system et les mêmes règles.</p>

<p><strong>Des décisions explicites et traçables.</strong> Chaque choix est écrit, relu, versionné. Six mois plus tard, on sait pourquoi un champ existe et quel critère il satisfait. C’est précieux pour l’onboarding, la maintenance et les audits.</p>

<p><strong>Une revue au bon niveau.</strong> Le métier relit un document métier, le designer relit des maquettes, le développeur relit du code. Personne n’a à déchiffrer un artefact qui n’est pas de son ressort.</p>

<p><strong>Des cas limites traités par construction.</strong> Parce que la spec d’expérience impose les états d’erreur, de chargement et de vide, et que la constitution impose l’accessibilité, ces sujets ne sont plus oubliés en fin de projet.</p>

<p><strong>Une indépendance vis-à-vis du modèle.</strong> Une spec bien écrite peut être réexécutée avec un autre agent ou un modèle plus récent. On capitalise sur les specs, pas sur des prompts éphémères.</p>

<p><strong>Une meilleure collaboration.</strong> Le besoin métier, la spec et les maquettes deviennent un langage commun entre métier, design et technique. Les maquettes générées très tôt permettent de valider avec les utilisateurs avant d’écrire du code.</p>

<p><strong>Des tests qui découlent de la spec.</strong> Les critères d’acceptation deviennent des tests ; la couverture fonctionnelle devient mesurable.</p>

<p><strong>Une évolution facilitée.</strong> Ajouter une règle métier revient à modifier la spec et régénérer les parties concernées, plutôt qu’à chercher où intervenir dans le code.</p>

<hr />

<h2 id="8-avoir-conscience-des-enjeux">8. Avoir conscience des enjeux</h2>

<p>L’automatisation de bout en bout est séduisante. Elle comporte aussi des risques, dont certains sont nouveaux. Les connaître est ce qui distingue une adoption maîtrisée d’un pari.</p>

<h3 id="-entrée-médiocre-sortie-médiocre-">« Entrée médiocre, sortie médiocre »</h3>

<p>L’IA amplifie la qualité de la spec, dans les deux sens. Une spec ambiguë produit une interface plausible mais fausse, et d’autant plus trompeuse qu’elle a l’air finie. <strong>Bien spécifier devient la compétence centrale</strong>, et c’est un vrai travail. Le SDD ne supprime pas l’effort d’analyse : il le déplace en amont.</p>

<h3 id="lillusion-du--totalement-automatisé-">L’illusion du « totalement automatisé »</h3>

<p>La <em>production</em> peut être entièrement automatisée. La <em>décision</em> ne doit pas l’être. Une chaîne sans porte humaine validera elle-même ses propres erreurs : l’IA qui écrit la spec, génère le code et écrit les tests peut produire un ensemble parfaitement cohérent… et hors sujet. Il faut au minimum un humain qui valide la spec, un qui valide les maquettes, et une revue du code.</p>

<h3 id="le-risque-de-waterfall-déguisé">Le risque de waterfall déguisé</h3>

<p>Produire beaucoup de documents avant de coder rappelle le cycle en V, et des critiques de Spec Kit pointent justement ce poids pour de petites évolutions. La parade : des specs <strong>courtes et incrémentales</strong> (une fonctionnalité à la fois), et une génération si rapide qu’on peut montrer des maquettes au métier dès le premier jour. Pour un correctif d’une ligne, inutile de dérouler tout le pipeline.</p>

<h3 id="la-dérive-entre-spec-et-code">La dérive entre spec et code</h3>

<p>Si quelqu’un corrige « juste un détail » directement dans le code, la spec cesse d’être la vérité et le prochain cycle de régénération écrasera la correction. Il faut une discipline d’équipe (on corrige la spec), éventuellement outillée (hooks qui signalent les écarts, contrôle de traçabilité en CI).</p>

<h3 id="laccessibilité-nest-pas-acquise">L’accessibilité n’est pas acquise</h3>

<p>Le code généré par IA reproduit les défauts fréquents du web : libellés manquants, contrastes insuffisants, navigation clavier cassée, rôles ARIA superflus. Les audits automatiques n’en détectent qu’une partie ; des tests avec des technologies d’assistance et, idéalement, avec des utilisateurs concernés restent nécessaires. C’est aussi un enjeu réglementaire : en Europe, l’<em>European Accessibility Act</em> s’applique depuis juin 2025 à de nombreux services numériques grand public.</p>

<h3 id="sécurité-et-chaîne-dapprovisionnement">Sécurité et chaîne d’approvisionnement</h3>

<p>Un agent qui génère du code peut introduire une dépendance inutile, vulnérable ou même inexistante (le nom d’un paquet inventé par un modèle peut être enregistré par un attaquant). Il peut aussi manipuler des secrets, ou exposer des données côté client. Autres points de vigilance : les specs et les fichiers lus par l’agent sont une <strong>surface d’injection de prompt</strong> ; un contenu malveillant glissé dans une spec ou un ticket peut orienter la génération. Il faut donc relire les specs comme on relit du code, restreindre les droits de l’agent et analyser les dépendances en CI.</p>

<h3 id="confidentialité-et-propriété-intellectuelle">Confidentialité et propriété intellectuelle</h3>

<p>Le besoin métier, les règles de gestion et parfois des exemples de données sont envoyés à un modèle hébergé. Il faut vérifier les conditions contractuelles du fournisseur (rétention, réutilisation des données), ne jamais placer de données personnelles réelles dans les specs ou les fixtures (RGPD), et s’interroger sur les droits attachés aux maquettes et au code générés, ainsi que sur la ressemblance éventuelle avec des interfaces existantes.</p>

<h3 id="luniformisation-esthétique">L’uniformisation esthétique</h3>

<p>Les outils de génération ont tendance à converger vers les mêmes interfaces : mêmes bibliothèques de composants, mêmes mises en page, même esthétique « par défaut ». Pour une application interne, ce n’est pas un problème. Pour un produit qui doit se différencier, le travail de direction artistique et un design system propre restent indispensables ; l’IA décline une identité, elle ne la crée pas à votre place.</p>

<h3 id="dette-technique-et-maintenabilité">Dette technique et maintenabilité</h3>

<p>Le code généré peut être verbeux, dupliqué, ou s’appuyer sur des motifs différents d’une génération à l’autre. Une constitution précise (architecture, conventions, bibliothèques autorisées), des tâches petites et une revue humaine limitent ce risque. Il faut aussi se demander : si l’outil de génération disparaît demain, l’équipe sait-elle maintenir ce code à la main ?</p>

<h3 id="coûts-et-empreinte">Coûts et empreinte</h3>

<p>Chaque régénération consomme des ressources de calcul : coût financier (les outils facturent souvent au crédit ou au jeton) et coût environnemental. Régénérer dix fois un écran pour un détail visuel n’est pas neutre. Des specs claires réduisent le nombre d’itérations.</p>

<h3 id="lévolution-des-métiers">L’évolution des métiers</h3>

<p>Le SDD ne supprime pas les designers et les développeurs, mais déplace leur valeur. Le designer passe de la production d’écrans à la conception du système (design system, principes d’interaction, validation). Le développeur passe de l’écriture de composants à l’architecture, à la revue et à la qualité. Le métier et le product owner prennent une responsabilité accrue dans l’écriture de specs précises. Cette transition demande de la formation et un accompagnement, en particulier pour les profils juniors, qui apprenaient jusqu’ici en produisant justement ce que l’IA produit désormais.</p>

<h3 id="le-verrouillage">Le verrouillage</h3>

<p>Certains outils stockent les specs dans un format propriétaire, imposent leur IDE ou leur modèle, ou découragent la modification manuelle du code généré. Privilégier des specs en Markdown/JSON dans le dépôt et des standards ouverts (design tokens DTCG, OpenAPI, MCP) garde la porte ouverte à un changement d’outil.</p>

<hr />

<h2 id="9-où-placer-lhumain--la-grille-de-contrôle">9. Où placer l’humain : la grille de contrôle</h2>

<table>
  <thead>
    <tr>
      <th>Étape</th>
      <th>Produit par</th>
      <th>Validé par</th>
      <th>Critère de passage</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Constitution</td>
      <td>Tech lead + design lead (avec l’IA)</td>
      <td>Équipe</td>
      <td>Règles connues et acceptées</td>
    </tr>
    <tr>
      <td>Besoin métier</td>
      <td>Métier</td>
      <td>Product owner</td>
      <td>Le « pourquoi » et l’indicateur de succès sont clairs</td>
    </tr>
    <tr>
      <td>Spec fonctionnelle</td>
      <td>IA, à partir du besoin</td>
      <td><strong>Métier + PO</strong></td>
      <td>Aucune question ouverte, critères testables</td>
    </tr>
    <tr>
      <td>Spec d’expérience</td>
      <td>IA + UX designer</td>
      <td><strong>UX designer</strong></td>
      <td>Tous les états décrits, traçabilité vers les critères</td>
    </tr>
    <tr>
      <td>Design system</td>
      <td>Designer (outils IA en appui)</td>
      <td><strong>Design lead</strong></td>
      <td>Tokens au format standard, contrastes conformes</td>
    </tr>
    <tr>
      <td>Maquettes</td>
      <td>IA</td>
      <td><strong>Designer + métier</strong> (+ utilisateurs si possible)</td>
      <td>Parcours validés, aucun composant hors catalogue</td>
    </tr>
    <tr>
      <td>Plan et tâches</td>
      <td>IA</td>
      <td><strong>Tech lead</strong></td>
      <td>Respect de la constitution, pas de dépendance non justifiée</td>
    </tr>
    <tr>
      <td>Code</td>
      <td>IA</td>
      <td><strong>Revue de code</strong></td>
      <td>Sécurité, erreurs, performance</td>
    </tr>
    <tr>
      <td>Vérification</td>
      <td>Automatique</td>
      <td>CI</td>
      <td>Tous les critères couverts, zéro violation a11y bloquante</td>
    </tr>
  </tbody>
</table>

<p>La règle simple à retenir : <strong>l’IA propose, un humain qualifié dispose, à chaque changement de couche.</strong></p>

<hr />

<h2 id="10-par-où-commencer">10. Par où commencer</h2>

<ol>
  <li><strong>Choisir une fonctionnalité pilote</strong> limitée et à faible risque (un écran interne, un formulaire).</li>
  <li><strong>Écrire la constitution</strong> du projet, même courte : stack, design system, accessibilité, interdits.</li>
  <li><strong>Exporter le design system existant en tokens</strong> au format DTCG. S’il n’existe pas, commencer par une palette, une échelle d’espacements et cinq composants.</li>
  <li><strong>Adopter un cadre SDD léger</strong> (Spec Kit ou une simple convention de dossiers <code class="language-plaintext highlighter-rouge">specs/</code>) avec l’agent déjà utilisé par l’équipe.</li>
  <li><strong>Imposer la section « Questions ouvertes »</strong> et ne rien générer tant qu’elle n’est pas vide.</li>
  <li><strong>Brancher les vérifications dès le départ</strong> : tests dérivés des critères, audit d’accessibilité, contrôle de traçabilité.</li>
  <li><strong>Mesurer</strong> : temps du besoin à la mise en production, nombre d’allers-retours, défauts détectés après livraison. Comparer avec la méthode habituelle avant de généraliser.</li>
</ol>

<hr />

<h2 id="11-conclusion">11. Conclusion</h2>

<p>L’IA a rendu la production de maquettes et de code presque gratuite. Ce qui devient rare et précieux, c’est la <strong>clarté de l’intention</strong>. Le Specification-Driven Design en tire la conséquence logique : on investit dans la spécification, on laisse l’IA dériver les artefacts, et on place des humains aux moments où une décision se prend.</p>

<p>Bien mené, il permet de passer d’un besoin métier à un frontend cohérent, accessible et testé en un temps inédit. Mal mené, il automatise surtout la production d’erreurs bien présentées. La différence tient à trois choses : la qualité des specs, la discipline de corriger à la source, et des validations humaines aux bons endroits.</p>

<hr />

<h2 id="pour-aller-plus-loin">Pour aller plus loin</h2>

<ul>
  <li><a href="https://glukhov.org/ai-devtools/ai-coding-assistants/spec-kit-vs-kiro-vs-claude-code/">GitHub Spec Kit vs Kiro vs Claude Code : comparaison des workflows SDD</a></li>
  <li><a href="https://ssojet.com/blog/best-spec-driven-development-tools">7 outils de spec-driven development (Spec Kit, Kiro, OpenSpec, Tessl…)</a></li>
  <li><a href="https://www.morphllm.com/spec-driven-development">Spec-driven development avec Kiro et les agents IA</a></li>
  <li><a href="https://www.w3.org/community/design-tokens/2025/10/28/design-tokens-specification-reaches-first-stable-version/">Design Tokens : première version stable de la spécification (W3C DTCG)</a></li>
  <li><a href="https://resources.rework.com/tools/ai-tools/best-ai-tools-for-ui-ux-design-2026">Panorama des outils IA pour l’UI/UX en 2026</a></li>
  <li>Article précédent sur ce blog : le protocole A2A et sa comparaison avec MCP</li>
</ul>]]></content><author><name>marwa SAHLI</name></author><category term="ia" /><category term="design" /><category term="frontend" /><category term="SDD" /><category term="spec-driven" /><category term="IA" /><category term="maquettes" /><category term="design system" /><category term="design tokens" /><category term="frontend" /><category term="agents" /><summary type="html"><![CDATA[Du besoin métier au frontend : maquettes et code générés par l’IA avec le Specification-Driven Design]]></summary></entry><entry xml:lang="en"><title type="html">The A2A protocol: how it works, step by step, and how it compares with MCP</title><link href="https://marwashli.github.io/articles-tech/en/2026/10/a2a-protocol/" rel="alternate" type="text/html" title="The A2A protocol: how it works, step by step, and how it compares with MCP" /><published>2026-10-27T00:00:00+00:00</published><updated>2026-10-27T00:00:00+00:00</updated><id>https://marwashli.github.io/articles-tech/en/2026/10/a2a-protocol</id><content type="html" xml:base="https://marwashli.github.io/articles-tech/en/2026/10/a2a-protocol/"><![CDATA[<h1 id="the-a2a-protocol-how-it-works-step-by-step-and-how-it-compares-with-mcp">The A2A protocol: how it works, step by step, and how it compares with MCP</h1>

<p>AI systems are no longer just a single model answering questions. We now build specialised <strong>agents</strong> (an HR agent, a booking agent, a code-review agent), often developed by different teams, on different frameworks (LangGraph, CrewAI, ADK, OpenAI Agents SDK…), and hosted by different providers. That raises a new question: <strong>how do these agents talk to each other?</strong></p>

<p>That is the question <strong>A2A (Agent2Agent)</strong> answers. This article introduces the protocol, walks through how it works step by step, and then compares it with <strong>MCP (Model Context Protocol)</strong>, with which it is often confused.</p>

<hr />

<h2 id="contents">Contents</h2>

<ol>
  <li><a href="#1-what-is-a2a">What is A2A?</a></li>
  <li><a href="#2-history-and-governance">History and governance</a></li>
  <li><a href="#3-key-concepts">Key concepts</a></li>
  <li><a href="#4-a-three-layer-architecture">A three-layer architecture</a></li>
  <li><a href="#5-how-it-works-step-by-step">How it works, step by step</a></li>
  <li><a href="#6-task-lifecycle">Task lifecycle</a></li>
  <li><a href="#7-security">Security</a></li>
  <li><a href="#8-a2a-vs-mcp">A2A vs MCP</a></li>
  <li><a href="#9-using-a2a-and-mcp-together">Using A2A and MCP together</a></li>
  <li><a href="#10-current-limitations-and-best-practices">Current limitations and best practices</a></li>
  <li><a href="#11-conclusion">Conclusion</a></li>
</ol>

<hr />

<h2 id="1-what-is-a2a">1. What is A2A?</h2>

<p>A2A is an <strong>open specification</strong> that defines how independent AI agents:</p>

<ul>
  <li><strong>discover each other</strong> (who can do what, and how to reach them),</li>
  <li><strong>delegate tasks</strong> to one another, including long-running, multi-step ones,</li>
  <li><strong>exchange data</strong> (text, JSON, files) securely,</li>
  <li><strong>track progress</strong> on work in flight (streaming, notifications).</li>
</ul>

<p>The core idea: every agent is treated as a <strong>black box</strong>. A client agent does not need to know the remote agent’s model, prompt, tools or memory. It hands over a task, the way you would hand a file to a colleague in another department, and gets a result back.</p>

<blockquote>
  <p><strong>In one sentence:</strong> MCP connects an agent to its <em>tools</em>; A2A connects an agent to <em>other agents</em>.</p>
</blockquote>

<hr />

<h2 id="2-history-and-governance">2. History and governance</h2>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>Milestone</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>April 2025</td>
      <td>Protocol announced by Google, with around fifty partners</td>
    </tr>
    <tr>
      <td>June 2025</td>
      <td>Project donated to the <strong>Linux Foundation</strong>, which now provides neutral governance</td>
    </tr>
    <tr>
      <td>Summer 2025</td>
      <td>Version 0.3: gRPC binding added, Agent Card signatures</td>
    </tr>
    <tr>
      <td>March 2026</td>
      <td><strong>Stable version 1.0</strong></td>
    </tr>
    <tr>
      <td>2026</td>
      <td>Production support in several platforms (Microsoft Copilot Studio, Azure AI Foundry, Amazon Bedrock AgentCore, ServiceNow, Salesforce…)</td>
    </tr>
  </tbody>
</table>

<p>Version 1.0 introduced several breaking changes compared with 0.x (method names, state names, removed fields). If you are reading tutorials from 2025, check which version they target.</p>

<hr />

<h2 id="3-key-concepts">3. Key concepts</h2>

<p>A2A relies on a small vocabulary:</p>

<table>
  <thead>
    <tr>
      <th>Concept</th>
      <th>Role</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Client agent</strong></td>
      <td>The agent that initiates the request (often an orchestrator).</td>
    </tr>
    <tr>
      <td><strong>Remote agent (A2A server)</strong></td>
      <td>The agent that receives and executes the task. Roles are fluid: an agent can be the client in one interaction and the server in another.</td>
    </tr>
    <tr>
      <td><strong>Agent Card</strong></td>
      <td>The agent’s public JSON identity card: name, description, version, skills, endpoints, authentication methods.</td>
    </tr>
    <tr>
      <td><strong>Skill</strong></td>
      <td>A capability declared in the Agent Card (e.g. <code class="language-plaintext highlighter-rouge">book_flight</code>), with a description, tags, examples and accepted formats.</td>
    </tr>
    <tr>
      <td><strong>Task</strong></td>
      <td>A unit of work with an identifier and a state, generated by the server. It can last a few milliseconds or several days.</td>
    </tr>
    <tr>
      <td><strong>Message</strong></td>
      <td>One turn of conversation, carried by a role (<code class="language-plaintext highlighter-rouge">user</code> or <code class="language-plaintext highlighter-rouge">agent</code>).</td>
    </tr>
    <tr>
      <td><strong>Part</strong></td>
      <td>A piece of content inside a message: text, structured JSON data, a URL to a file, or raw bytes.</td>
    </tr>
    <tr>
      <td><strong>Artifact</strong></td>
      <td>The deliverable produced by the task (a report, a JSON itinerary, a PDF…), also made of Parts.</td>
    </tr>
    <tr>
      <td><strong>Context</strong></td>
      <td>An identifier used to group several tasks and messages belonging to the same conversation.</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="4-a-three-layer-architecture">4. A three-layer architecture</h2>

<p>The v1.0 specification is split into three layers, so you can change transport without rewriting the agent’s logic:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│ Layer 3 — Protocol bindings                             │
│   JSON-RPC 2.0 over HTTPS  │  gRPC  │  HTTP+JSON (REST) │
├─────────────────────────────────────────────────────────┤
│ Layer 2 — Abstract operations                           │
│   SendMessage, SendStreamingMessage, GetTask,           │
│   ListTasks, CancelTask, SubscribeToTask,               │
│   push notification config, Agent Card retrieval        │
├─────────────────────────────────────────────────────────┤
│ Layer 1 — Canonical data model                          │
│   AgentCard, AgentSkill, Task, Message, Part,           │
│   Artifact, Extension (Protocol Buffers + JSON Schema)  │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<p>Each agent advertises the binding(s) it supports in its Agent Card (the <code class="language-plaintext highlighter-rouge">supportedInterfaces</code> field). JSON-RPC is the most common for publicly exposed agents; gRPC is attractive for low-latency internal traffic.</p>

<hr />

<h2 id="5-how-it-works-step-by-step">5. How it works, step by step</h2>

<p>Let’s take a concrete scenario: an <strong>HR agent</strong> needs to organise an interview and delegates the booking to a <strong>travel agent</strong> run by another team.</p>

<p><img src="/articles-tech/assets/images/sequence-a2a-en.svg" alt="A2A task delegation sequence: discovery, authentication, sending, tracking, multi-turn exchange and result" /></p>

<h3 id="step-1--discovery-via-the-agent-card">Step 1 — Discovery via the Agent Card</h3>

<p>The remote agent publishes its card at a standard URL: <code class="language-plaintext highlighter-rouge">https://&lt;domain&gt;/.well-known/agent-card.json</code> (an RFC 8615 convention). A simplified, v1.0-compliant example:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Travel booking agent"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Books flights and hotels for employees."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"provider"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"organization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MyCompany"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://mycompany.example"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"supportedInterfaces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://travel.mycompany.example/a2a"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"protocolBinding"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JSONRPC"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"protocolVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"capabilities"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"streaming"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pushNotifications"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"extendedAgentCard"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"defaultInputModes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"text/plain"</span><span class="p">,</span><span class="w"> </span><span class="s2">"application/json"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"defaultOutputModes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"application/json"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"skills"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"book_flight"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Book a flight"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Books a flight given an origin, a destination and dates."</span><span class="p">,</span><span class="w">
      </span><span class="nl">"tags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"travel"</span><span class="p">,</span><span class="w"> </span><span class="s2">"booking"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"examples"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"Book a Paris–Lyon flight on 12 November for employee 4432"</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"securitySchemes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"corp_oauth"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"oauth2"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"flows"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"clientCredentials"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"tokenUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://auth.mycompany.example/token"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"scopes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"flights:write"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Book flights"</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"security"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w"> </span><span class="nl">"corp_oauth"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"flights:write"</span><span class="p">]</span><span class="w"> </span><span class="p">}]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The client reads the card, checks that the <code class="language-plaintext highlighter-rouge">book_flight</code> skill exists, picks an interface and identifies the expected authentication method.</p>

<p>Discovery can also go through an internal <strong>registry</strong> (a catalogue of vetted agents) or <strong>direct configuration</strong> (a hard-coded URL) for tightly coupled systems.</p>

<h3 id="step-2--authentication">Step 2 — Authentication</h3>

<p>Credentials <strong>never</strong> travel in the body of the A2A message: they are sent in HTTP headers (<code class="language-plaintext highlighter-rouge">Authorization: Bearer …</code>). Here, the client runs an OAuth2 <em>client credentials</em> flow against the declared <code class="language-plaintext highlighter-rouge">tokenUrl</code>.</p>

<h3 id="step-3--sending-the-task-sendmessage">Step 3 — Sending the task (<code class="language-plaintext highlighter-rouge">SendMessage</code>)</h3>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/a2a</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">travel.mycompany.example</span>
<span class="na">Authorization</span><span class="p">:</span> <span class="s">Bearer eyJhbGciOi...</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">application/json</span>
<span class="na">Accept</span><span class="p">:</span> <span class="s">application/json, text/event-stream</span>
</code></pre></div></div>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"jsonrpc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SendMessage"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"params"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ROLE_USER"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"parts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Book a direct Paris–Lyon flight on 12 November for employee 4432."</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"configuration"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"returnImmediately"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Things to note:</p>

<ul>
  <li>The client <strong>does not supply</strong> a task identifier: the server generates it and returns it.</li>
  <li>For a trivial answer, the server can return a <code class="language-plaintext highlighter-rouge">Message</code> directly instead of creating a <code class="language-plaintext highlighter-rouge">Task</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">returnImmediately: true</code> asks the server to hand back the newly created task right away, so the client can follow progress in parallel (in v1.0 the default behaviour is blocking).</li>
</ul>

<h3 id="step-4--tracking-progress">Step 4 — Tracking progress</h3>

<p>Three mechanisms, depending on duration and topology:</p>

<table>
  <thead>
    <tr>
      <th>Mechanism</th>
      <th>When to use it</th>
      <th>Operations</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Polling</strong></td>
      <td>Short tasks, simple client</td>
      <td><code class="language-plaintext highlighter-rouge">GetTask</code> at regular intervals</td>
    </tr>
    <tr>
      <td><strong>SSE streaming</strong></td>
      <td>The client stays connected and wants real-time updates</td>
      <td><code class="language-plaintext highlighter-rouge">SendStreamingMessage</code> (send + stream) or <code class="language-plaintext highlighter-rouge">SubscribeToTask</code> (subscribe to an existing task)</td>
    </tr>
    <tr>
      <td><strong>Push notifications</strong></td>
      <td>Very long tasks, disconnected client (mobile, serverless)</td>
      <td>The client registers a webhook; the server POSTs updates to it, authenticated (signed JWT, HMAC or mTLS)</td>
    </tr>
  </tbody>
</table>

<p>Example of a received SSE stream:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data: {"task": {"id": "task-9876", "status": {"state": "TASK_STATE_SUBMITTED"}}}

data: {"statusUpdate": {"taskId": "task-9876", "status": {"state": "TASK_STATE_WORKING"}}}

data: {"artifactUpdate": {"taskId": "task-9876", "artifact": {"artifactId": "a1", "name": "itinerary", "parts": [ ... ]}}}

data: {"statusUpdate": {"taskId": "task-9876", "status": {"state": "TASK_STATE_COMPLETED"}}}
</code></pre></div></div>

<p>The stream closes automatically when the task reaches a terminal state.</p>

<h3 id="step-5--multi-turn-exchange-if-needed">Step 5 — Multi-turn exchange (if needed)</h3>

<p>If the remote agent needs clarification (“economy or business class?”), the task moves to <code class="language-plaintext highlighter-rouge">INPUT_REQUIRED</code>. The client replies with a new <code class="language-plaintext highlighter-rouge">SendMessage</code> carrying the same task identifier, and work resumes.</p>

<h3 id="step-6--retrieving-the-result">Step 6 — Retrieving the result</h3>

<p><strong>Artifacts</strong> hold the final deliverable. A client that missed the stream can always call <code class="language-plaintext highlighter-rouge">GetTask</code> to fetch the full task, with its history and artifacts.</p>

<h3 id="bonus-a-minimal-python-client">Bonus: a minimal Python client</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>

<span class="n">BASE</span> <span class="o">=</span> <span class="s">"https://travel.mycompany.example"</span>

<span class="c1"># 1. Discovery
</span><span class="n">card</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">BASE</span><span class="si">}</span><span class="s">/.well-known/agent-card.json"</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">).</span><span class="n">json</span><span class="p">()</span>
<span class="k">if</span> <span class="s">"book_flight"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">{</span><span class="n">s</span><span class="p">[</span><span class="s">"id"</span><span class="p">]</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">card</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"skills"</span><span class="p">,</span> <span class="p">[])}:</span>
    <span class="k">raise</span> <span class="nb">RuntimeError</span><span class="p">(</span><span class="s">"The agent does not offer the book_flight skill"</span><span class="p">)</span>

<span class="n">endpoint</span> <span class="o">=</span> <span class="n">card</span><span class="p">[</span><span class="s">"supportedInterfaces"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">"url"</span><span class="p">]</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s">"Authorization"</span><span class="p">:</span> <span class="s">"Bearer &lt;token&gt;"</span><span class="p">,</span> <span class="s">"Content-Type"</span><span class="p">:</span> <span class="s">"application/json"</span><span class="p">}</span>

<span class="c1"># 2. Send the task
</span><span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"jsonrpc"</span><span class="p">:</span> <span class="s">"2.0"</span><span class="p">,</span>
    <span class="s">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="s">"method"</span><span class="p">:</span> <span class="s">"SendMessage"</span><span class="p">,</span>
    <span class="s">"params"</span><span class="p">:</span> <span class="p">{</span>
        <span class="s">"message"</span><span class="p">:</span> <span class="p">{</span>
            <span class="s">"role"</span><span class="p">:</span> <span class="s">"ROLE_USER"</span><span class="p">,</span>
            <span class="s">"parts"</span><span class="p">:</span> <span class="p">[{</span><span class="s">"text"</span><span class="p">:</span> <span class="s">"Book a Paris–Lyon flight on 12/11 for employee 4432."</span><span class="p">}],</span>
        <span class="p">},</span>
        <span class="s">"configuration"</span><span class="p">:</span> <span class="p">{</span><span class="s">"returnImmediately"</span><span class="p">:</span> <span class="bp">True</span><span class="p">},</span>
    <span class="p">},</span>
<span class="p">}</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">payload</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">).</span><span class="n">json</span><span class="p">()[</span><span class="s">"result"</span><span class="p">]</span>

<span class="c1"># 3. Read the state (simple polling)
</span><span class="n">status</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span>
    <span class="n">endpoint</span><span class="p">,</span>
    <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s">"jsonrpc"</span><span class="p">:</span> <span class="s">"2.0"</span><span class="p">,</span> <span class="s">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">"method"</span><span class="p">:</span> <span class="s">"GetTask"</span><span class="p">,</span>
          <span class="s">"params"</span><span class="p">:</span> <span class="p">{</span><span class="s">"taskId"</span><span class="p">:</span> <span class="n">task</span><span class="p">[</span><span class="s">"task"</span><span class="p">][</span><span class="s">"id"</span><span class="p">]}},</span>
    <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span>
<span class="p">).</span><span class="n">json</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="n">status</span><span class="p">)</span>
</code></pre></div></div>

<blockquote>
  <p>In production, prefer the official SDKs (Python, JavaScript, Java, Go, .NET) published by the A2A project over hand-written HTTP calls. The exact shape of <code class="language-plaintext highlighter-rouge">result</code> depends on the server’s response (<code class="language-plaintext highlighter-rouge">task</code> or <code class="language-plaintext highlighter-rouge">message</code>).</p>
</blockquote>

<hr />

<h2 id="6-task-lifecycle">6. Task lifecycle</h2>

<p><img src="/articles-tech/assets/images/cycle-de-vie-tache-en.svg" alt="A2A task lifecycle: in-progress, interrupted and terminal states" /></p>

<table>
  <thead>
    <tr>
      <th>State (v1.0)</th>
      <th>Category</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_SUBMITTED</code></td>
      <td>in progress</td>
      <td>Accepted, not yet started</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_WORKING</code></td>
      <td>in progress</td>
      <td>Actively being processed</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_INPUT_REQUIRED</code></td>
      <td>interrupted</td>
      <td>Waiting for information from the client</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_AUTH_REQUIRED</code></td>
      <td>interrupted</td>
      <td>The client must provide additional credentials</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_COMPLETED</code></td>
      <td>terminal</td>
      <td>Finished successfully</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_FAILED</code></td>
      <td>terminal</td>
      <td>Unrecoverable error</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_CANCELED</code></td>
      <td>terminal</td>
      <td>Cancelled (often via <code class="language-plaintext highlighter-rouge">CancelTask</code>)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_REJECTED</code></td>
      <td>terminal</td>
      <td>Refused by the agent (policy, quota, capability)</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="7-security">7. Security</h2>

<p>A2A builds on web standards rather than inventing its own:</p>

<ul>
  <li><strong>Encrypted transport</strong>: HTTPS (TLS 1.2 minimum) is mandatory in production.</li>
  <li><strong>Authentication schemes</strong> aligned with OpenAPI: OAuth 2.0, OpenID Connect, API key, mTLS. Declared in <code class="language-plaintext highlighter-rouge">securitySchemes</code> and required via <code class="language-plaintext highlighter-rouge">security</code>.</li>
  <li><strong>Signed Agent Cards</strong>: a JWS signature (RFC 7515) computed over a canonical form of the card (RFC 8785) makes any tampering detectable. Otherwise, a modified card could redirect calls or inject malicious content into the client agent’s prompt.</li>
  <li><strong>Extended Agent Card</strong>: an agent can expose a minimal public card and a more detailed one, available only after authentication.</li>
  <li><strong>Traceability</strong>: the specification recommends OpenTelemetry and propagating W3C Trace Context headers (<code class="language-plaintext highlighter-rouge">traceparent</code>) to follow a request across the whole chain of agents.</li>
</ul>

<p>For high assurance: combine mTLS and OAuth2, or use sender-constrained tokens (DPoP, RFC 9449), so that a stolen token is useless from another machine.</p>

<hr />

<h2 id="8-a2a-vs-mcp">8. A2A vs MCP</h2>

<h3 id="reminder-what-is-mcp">Reminder: what is MCP?</h3>

<p><strong>MCP (Model Context Protocol)</strong>, launched by Anthropic in late 2024 and now widely adopted, standardises the connection between an AI application (the <em>client/host</em>) and <strong>servers</strong> that expose <strong>tools</strong>, <strong>resources</strong> (data) and <strong>prompts</strong>. It runs on JSON-RPC 2.0, locally over <code class="language-plaintext highlighter-rouge">stdio</code> or remotely over HTTP. Its specifications are named by date; the <strong>2026-07-28</strong> revision notably made the protocol <strong>stateless</strong> (sessions and the initialisation handshake were removed), which makes horizontal scaling of servers easier.</p>

<h3 id="comparison-table">Comparison table</h3>

<table>
  <thead>
    <tr>
      <th>Criterion</th>
      <th><strong>A2A</strong></th>
      <th><strong>MCP</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Purpose</strong></td>
      <td>Make autonomous agents collaborate</td>
      <td>Give an agent access to tools and data</td>
    </tr>
    <tr>
      <td><strong>Analogy</strong></td>
      <td>A colleague you delegate a file to</td>
      <td>A toolbox you use yourself</td>
    </tr>
    <tr>
      <td><strong>Nature of the counterpart</strong></td>
      <td>Opaque, autonomous, reasons on its own</td>
      <td>A deterministic function or exposed data source</td>
    </tr>
    <tr>
      <td><strong>Unit of exchange</strong></td>
      <td>A task (<code class="language-plaintext highlighter-rouge">Task</code>) with a state and a lifecycle</td>
      <td>A tool call / resource read</td>
    </tr>
    <tr>
      <td><strong>Typical duration</strong></td>
      <td>Seconds to days, multi-turn</td>
      <td>Usually short, request/response</td>
    </tr>
    <tr>
      <td><strong>Interaction</strong></td>
      <td>Negotiation possible (<code class="language-plaintext highlighter-rouge">INPUT_REQUIRED</code>, <code class="language-plaintext highlighter-rouge">AUTH_REQUIRED</code>)</td>
      <td>The model calls, the tool answers</td>
    </tr>
    <tr>
      <td><strong>Discovery</strong></td>
      <td>Public Agent Card (<code class="language-plaintext highlighter-rouge">/.well-known/agent-card.json</code>), registries</td>
      <td>Listing tools/resources on the connected server</td>
    </tr>
    <tr>
      <td><strong>Transports</strong></td>
      <td>JSON-RPC 2.0, gRPC, HTTP+JSON</td>
      <td>JSON-RPC 2.0 over stdio or HTTP</td>
    </tr>
    <tr>
      <td><strong>Async / long-running</strong></td>
      <td>Native: SSE, webhook push notifications</td>
      <td>Possible but less central</td>
    </tr>
    <tr>
      <td><strong>Content</strong></td>
      <td>Multimodal via Parts (text, JSON, files, bytes)</td>
      <td>Structured results, text, images, resources</td>
    </tr>
    <tr>
      <td><strong>Topology</strong></td>
      <td>Peer-to-peer, interchangeable client/server roles</td>
      <td>Host → client → server</td>
    </tr>
    <tr>
      <td><strong>Origin / governance</strong></td>
      <td>Google → Linux Foundation</td>
      <td>Anthropic → open governance by the MCP community</td>
    </tr>
    <tr>
      <td><strong>Maturity (Oct. 2026)</strong></td>
      <td>v1.0 stable (March 2026)</td>
      <td>2026-07-28 revision stable</td>
    </tr>
  </tbody>
</table>

<h3 id="the-right-question-to-ask">The right question to ask</h3>

<blockquote>
  <p><em>“Do I want to <strong>perform a precise action</strong>, or <strong>hand over a goal</strong> to someone who will figure out how to reach it?”</em></p>
</blockquote>

<ul>
  <li><strong>A precise, deterministic action</strong> (an SQL query, reading a file, calling a weather API) → <strong>MCP</strong>.</li>
  <li><strong>A goal to reach</strong> by a system that reasons, can ask questions, and whose implementation you don’t want to know about → <strong>A2A</strong>.</li>
</ul>

<p>There is a grey area: you can expose a whole agent as an MCP “tool”. That works for simple cases, but you lose what makes A2A strong: state tracking, multi-turn exchanges, long-running tasks and cross-organisation discovery.</p>

<hr />

<h2 id="9-using-a2a-and-mcp-together">9. Using A2A and MCP together</h2>

<p>The two protocols are <strong>complementary</strong> and together form the communication stack of multi-agent systems:</p>

<p><img src="/articles-tech/assets/images/a2a-mcp-ensemble-en.svg" alt="An orchestrator delegates via A2A to two agents, each of which uses its own tools via MCP" /></p>

<p><strong>Scenario:</strong></p>

<ol>
  <li>The user asks the orchestrator: “Organise Marie’s interview in Lyon on Thursday.”</li>
  <li>The orchestrator delegates via <strong>A2A</strong> to the HR agent (“schedule the interview”) and to the travel agent (“book the trip”).</li>
  <li>Each of these agents uses <strong>MCP</strong> internally to query its own tools: calendar, HRIS, booking API.</li>
  <li>The agents return their <strong>artifacts</strong> (confirmed slot, ticket) to the orchestrator via A2A.</li>
</ol>

<p>Rule of thumb: <strong>A2A between agents, MCP between an agent and its tools.</strong></p>

<hr />

<h2 id="10-current-limitations-and-best-practices">10. Current limitations and best practices</h2>

<p><strong>Limitations to be aware of:</strong></p>

<ul>
  <li><strong>Per-skill data schema</strong>: a skill declares that it accepts <code class="language-plaintext highlighter-rouge">application/json</code>, but the specification does not standardise the exact schema expected. Workaround: publish a JSON Schema in the skill description or through an <em>Extension</em> declared in the Agent Card.</li>
  <li><strong>Downscoping across delegation chains</strong>: A2A says how to pass credentials, not how to narrow them when a highly privileged orchestrator delegates to a less privileged agent. Workaround: OAuth 2.0 Token Exchange (RFC 8693) to issue short-lived, narrowly scoped tokens.</li>
  <li><strong>No standard registry</strong>: discovery through a central catalogue is still something you build yourself.</li>
  <li><strong>Migrating from 0.x to 1.0</strong>: method names (<code class="language-plaintext highlighter-rouge">message/send</code> → <code class="language-plaintext highlighter-rouge">SendMessage</code>), prefixed state names, removal of the <code class="language-plaintext highlighter-rouge">kind</code> and <code class="language-plaintext highlighter-rouge">final</code> fields.</li>
</ul>

<p><strong>Best practices:</strong></p>

<ul>
  <li>Treat the Agent Card as a <strong>contract</strong>: any change to a skill, URL or capability must bump the version.</li>
  <li><strong>Sign</strong> Agent Cards and verify signatures on the client side.</li>
  <li>Serve the card with <code class="language-plaintext highlighter-rouge">Content-Type: application/json</code>.</li>
  <li>Put an <strong>API gateway</strong> in front of agents to centralise authentication, quotas, logging and long-lived connection handling.</li>
  <li>Instrument with <strong>OpenTelemetry</strong> from day one: debugging a chain of five agents without distributed traces is painful.</li>
  <li>Never blindly trust content received from another agent: it is data, not instructions (prompt-injection risk).</li>
</ul>

<hr />

<h2 id="11-conclusion">11. Conclusion</h2>

<p>A2A does for agents what HTTP did for documents or SMTP for email: it provides a <strong>common, neutral language</strong> that lets heterogeneous systems cooperate without bespoke integration. With a stable v1.0, Linux Foundation governance and adoption by the major platforms, it is now ready for production use.</p>

<p>MCP and A2A are not competitors: <strong>MCP gives an agent hands; A2A gives it colleagues.</strong> A modern multi-agent architecture uses both.</p>

<hr />

<h2 id="further-reading">Further reading</h2>

<ul>
  <li><a href="https://a2a-protocol.org/latest/specification/">Official A2A specification</a></li>
  <li><a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">A2A and MCP: how the two protocols relate (official A2A site)</a></li>
  <li><a href="https://modelcontextprotocol.io/specification/latest">MCP specification</a></li>
  <li><a href="https://tyk.io/learning-center/a2a-protocol-architecture-and-technical-specification/">A2A v1.0 technical guide — Tyk</a></li>
  <li><a href="https://metorial.com/for-ai-crawlers/mcp-vs-a2a">MCP vs A2A — Metorial</a></li>
</ul>]]></content><author><name>marwa SAHLI</name></author><category term="ai" /><category term="agents" /><category term="protocols" /><category term="A2A" /><category term="MCP" /><category term="agents" /><category term="interoperability" /><category term="LLM" /><summary type="html"><![CDATA[The A2A protocol: how it works, step by step, and how it compares with MCP]]></summary></entry><entry xml:lang="fr"><title type="html">Le protocole A2A : fonctionnement, mode opératoire et comparaison avec MCP</title><link href="https://marwashli.github.io/articles-tech/2026/10/a2a-protocole/" rel="alternate" type="text/html" title="Le protocole A2A : fonctionnement, mode opératoire et comparaison avec MCP" /><published>2026-10-27T00:00:00+00:00</published><updated>2026-10-27T00:00:00+00:00</updated><id>https://marwashli.github.io/articles-tech/2026/10/a2a-protocole</id><content type="html" xml:base="https://marwashli.github.io/articles-tech/2026/10/a2a-protocole/"><![CDATA[<h1 id="le-protocole-a2a--fonctionnement-mode-opératoire-et-comparaison-avec-mcp">Le protocole A2A : fonctionnement, mode opératoire et comparaison avec MCP</h1>

<p>Les systèmes d’IA ne se résument plus à un seul modèle qui répond à des questions. On construit désormais des <strong>agents</strong> spécialisés — un agent RH, un agent de réservation, un agent de revue de code — souvent développés par des équipes différentes, sur des frameworks différents (LangGraph, CrewAI, ADK, OpenAI Agents SDK…), et hébergés chez des fournisseurs différents. Le problème devient alors : <strong>comment ces agents se parlent-ils ?</strong></p>

<p>C’est la question à laquelle répond <strong>A2A (Agent2Agent)</strong>. Cet article présente le protocole, détaille son mode opératoire pas à pas, puis le compare à <strong>MCP (Model Context Protocol)</strong>, avec lequel il est souvent confondu.</p>

<hr />

<h2 id="sommaire">Sommaire</h2>

<ol>
  <li><a href="#1-quest-ce-que-a2a-">Qu’est-ce que A2A ?</a></li>
  <li><a href="#2-historique-et-gouvernance">Historique et gouvernance</a></li>
  <li><a href="#3-les-concepts-clés">Les concepts clés</a></li>
  <li><a href="#4-architecture-en-trois-couches">Architecture en trois couches</a></li>
  <li><a href="#5-mode-opératoire-pas-à-pas">Mode opératoire pas à pas</a></li>
  <li><a href="#6-cycle-de-vie-dune-tâche">Cycle de vie d’une tâche</a></li>
  <li><a href="#7-sécurité">Sécurité</a></li>
  <li><a href="#8-a2a-vs-mcp">A2A vs MCP</a></li>
  <li><a href="#9-utiliser-a2a-et-mcp-ensemble">Utiliser A2A et MCP ensemble</a></li>
  <li><a href="#10-limites-actuelles-et-bonnes-pratiques">Limites actuelles et bonnes pratiques</a></li>
  <li><a href="#11-conclusion">Conclusion</a></li>
</ol>

<hr />

<h2 id="1-quest-ce-que-a2a-">1. Qu’est-ce que A2A ?</h2>

<p>A2A est une <strong>spécification ouverte</strong> qui définit comment des agents IA indépendants :</p>

<ul>
  <li><strong>se découvrent</strong> (qui sait faire quoi, et comment le contacter),</li>
  <li><strong>se délèguent des tâches</strong>, y compris longues et multi-étapes,</li>
  <li><strong>échangent des données</strong> (texte, JSON, fichiers) de façon sécurisée,</li>
  <li><strong>suivent l’avancement</strong> d’un travail en cours (streaming, notifications).</li>
</ul>

<p>L’idée centrale : chaque agent est traité comme une <strong>boîte noire</strong>. Un agent client n’a pas besoin de connaître le modèle, le prompt, les outils ou la mémoire de l’agent distant. Il lui confie une tâche, comme on confierait un dossier à un collègue d’un autre service, et récupère un résultat.</p>

<blockquote>
  <p><strong>En une phrase :</strong> MCP relie un agent à ses <em>outils</em> ; A2A relie un agent à d’<em>autres agents</em>.</p>
</blockquote>

<hr />

<h2 id="2-historique-et-gouvernance">2. Historique et gouvernance</h2>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>Étape</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Avril 2025</td>
      <td>Annonce du protocole par Google, avec une cinquantaine de partenaires</td>
    </tr>
    <tr>
      <td>Juin 2025</td>
      <td>Don du projet à la <strong>Linux Foundation</strong>, qui en assure désormais la gouvernance neutre</td>
    </tr>
    <tr>
      <td>Été 2025</td>
      <td>Version 0.3 : ajout d’une liaison gRPC, signature des Agent Cards</td>
    </tr>
    <tr>
      <td>Mars 2026</td>
      <td><strong>Version 1.0 stable</strong></td>
    </tr>
    <tr>
      <td>2026</td>
      <td>Support en production dans plusieurs plateformes (Microsoft Copilot Studio, Azure AI Foundry, Amazon Bedrock AgentCore, ServiceNow, Salesforce…)</td>
    </tr>
  </tbody>
</table>

<p>La v1.0 a introduit plusieurs changements cassants par rapport aux versions 0.x (noms de méthodes, noms d’états, suppression de certains champs). Si vous lisez des tutoriels de 2025, vérifiez la version ciblée.</p>

<hr />

<h2 id="3-les-concepts-clés">3. Les concepts clés</h2>

<p>A2A repose sur un petit vocabulaire :</p>

<table>
  <thead>
    <tr>
      <th>Concept</th>
      <th>Rôle</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Client agent</strong></td>
      <td>L’agent qui initie la demande (souvent un orchestrateur).</td>
    </tr>
    <tr>
      <td><strong>Remote agent (serveur A2A)</strong></td>
      <td>L’agent qui reçoit et exécute la tâche. Les rôles sont fluides : un agent peut être client dans une interaction et serveur dans une autre.</td>
    </tr>
    <tr>
      <td><strong>Agent Card</strong></td>
      <td>Fiche d’identité JSON publique de l’agent : nom, description, version, compétences, points d’accès, modes d’authentification.</td>
    </tr>
    <tr>
      <td><strong>Skill (compétence)</strong></td>
      <td>Une capacité déclarée dans l’Agent Card (ex. <code class="language-plaintext highlighter-rouge">book_flight</code>), avec description, tags, exemples et formats acceptés.</td>
    </tr>
    <tr>
      <td><strong>Task (tâche)</strong></td>
      <td>Unité de travail avec un identifiant et un état, générée par le serveur. Elle peut durer quelques millisecondes ou plusieurs jours.</td>
    </tr>
    <tr>
      <td><strong>Message</strong></td>
      <td>Un tour de conversation, porté par un rôle (<code class="language-plaintext highlighter-rouge">user</code> ou <code class="language-plaintext highlighter-rouge">agent</code>).</td>
    </tr>
    <tr>
      <td><strong>Part</strong></td>
      <td>Un morceau de contenu dans un message : texte, données JSON structurées, URL vers un fichier, ou octets bruts.</td>
    </tr>
    <tr>
      <td><strong>Artifact</strong></td>
      <td>Le livrable produit par la tâche (un rapport, un JSON d’itinéraire, un PDF…), lui aussi composé de Parts.</td>
    </tr>
    <tr>
      <td><strong>Context</strong></td>
      <td>Identifiant qui permet de regrouper plusieurs tâches et messages d’une même conversation.</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="4-architecture-en-trois-couches">4. Architecture en trois couches</h2>

<p>La spécification v1.0 est découpée en trois couches, ce qui permet de changer de transport sans réécrire la logique de l’agent :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│ Couche 3 — Liaisons de protocole (bindings)             │
│   JSON-RPC 2.0 sur HTTPS  │  gRPC  │  HTTP+JSON (REST)  │
├─────────────────────────────────────────────────────────┤
│ Couche 2 — Opérations abstraites                        │
│   SendMessage, SendStreamingMessage, GetTask,           │
│   ListTasks, CancelTask, SubscribeToTask,               │
│   gestion des notifications push, récupération de carte │
├─────────────────────────────────────────────────────────┤
│ Couche 1 — Modèle de données canonique                  │
│   AgentCard, AgentSkill, Task, Message, Part,           │
│   Artifact, Extension (Protocol Buffers + JSON Schema)  │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<p>Chaque agent annonce dans son Agent Card la ou les liaisons qu’il supporte (champ <code class="language-plaintext highlighter-rouge">supportedInterfaces</code>). JSON-RPC est la plus répandue pour les agents exposés publiquement ; gRPC est intéressant pour les échanges internes à faible latence.</p>

<hr />

<h2 id="5-mode-opératoire-pas-à-pas">5. Mode opératoire pas à pas</h2>

<p>Prenons un scénario concret : un <strong>agent RH</strong> doit organiser un entretien et délègue la réservation à un <strong>agent de voyage</strong> géré par une autre équipe.</p>

<p><img src="/articles-tech/assets/images/sequence-a2a.svg" alt="Séquence d'une délégation de tâche A2A : découverte, authentification, envoi, suivi, échange multi-tours et résultat" /></p>

<h3 id="étape-1--découverte-via-lagent-card">Étape 1 — Découverte via l’Agent Card</h3>

<p>L’agent distant publie sa carte à une URL standard : <code class="language-plaintext highlighter-rouge">https://&lt;domaine&gt;/.well-known/agent-card.json</code> (convention RFC 8615). Exemple simplifié conforme v1.0 :</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Agent de réservation de voyages"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Réserve des vols et hôtels pour les collaborateurs."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"provider"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"organization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MonEntreprise"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://monentreprise.example"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"supportedInterfaces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://voyage.monentreprise.example/a2a"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"protocolBinding"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JSONRPC"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"protocolVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"capabilities"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"streaming"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pushNotifications"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"extendedAgentCard"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"defaultInputModes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"text/plain"</span><span class="p">,</span><span class="w"> </span><span class="s2">"application/json"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"defaultOutputModes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"application/json"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"skills"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"book_flight"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Réserver un vol"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Réserve un vol à partir d'une origine, d'une destination et de dates."</span><span class="p">,</span><span class="w">
      </span><span class="nl">"tags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"voyage"</span><span class="p">,</span><span class="w"> </span><span class="s2">"réservation"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"examples"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"Réserve un Paris–Lyon le 12 novembre pour le matricule 4432"</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"securitySchemes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"corp_oauth"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"oauth2"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"flows"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"clientCredentials"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"tokenUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://auth.monentreprise.example/token"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"scopes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"flights:write"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Réserver des vols"</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"security"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w"> </span><span class="nl">"corp_oauth"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"flights:write"</span><span class="p">]</span><span class="w"> </span><span class="p">}]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Le client lit la carte, vérifie que le skill <code class="language-plaintext highlighter-rouge">book_flight</code> existe, choisit une interface et identifie le mode d’authentification attendu.</p>

<p>La découverte peut aussi passer par un <strong>registre</strong> interne (catalogue d’agents validés) ou par une <strong>configuration directe</strong> (URL codée en dur) pour des systèmes fortement couplés.</p>

<h3 id="étape-2--authentification">Étape 2 — Authentification</h3>

<p>Les identifiants ne voyagent <strong>jamais</strong> dans le corps du message A2A : ils sont transmis dans les en-têtes HTTP (<code class="language-plaintext highlighter-rouge">Authorization: Bearer …</code>). Ici, le client effectue un flux OAuth2 <em>client credentials</em> auprès du <code class="language-plaintext highlighter-rouge">tokenUrl</code> déclaré.</p>

<h3 id="étape-3--envoi-de-la-tâche-sendmessage">Étape 3 — Envoi de la tâche (<code class="language-plaintext highlighter-rouge">SendMessage</code>)</h3>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/a2a</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">voyage.monentreprise.example</span>
<span class="na">Authorization</span><span class="p">:</span> <span class="s">Bearer eyJhbGciOi...</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">application/json</span>
<span class="na">Accept</span><span class="p">:</span> <span class="s">application/json, text/event-stream</span>
</code></pre></div></div>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"jsonrpc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SendMessage"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"params"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ROLE_USER"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"parts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Réserve un vol direct Paris–Lyon le 12 novembre pour le matricule 4432."</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"configuration"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"returnImmediately"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Points à noter :</p>

<ul>
  <li>Le client <strong>ne fournit pas</strong> d’identifiant de tâche : c’est le serveur qui le génère et le renvoie.</li>
  <li>Pour une réponse triviale, le serveur peut renvoyer directement un <code class="language-plaintext highlighter-rouge">Message</code> au lieu de créer une <code class="language-plaintext highlighter-rouge">Task</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">returnImmediately: true</code> demande au serveur de rendre la main tout de suite avec la tâche créée, pour que le client puisse suivre l’avancement en parallèle (en v1.0, le comportement par défaut est bloquant).</li>
</ul>

<h3 id="étape-4--suivi-de-lavancement">Étape 4 — Suivi de l’avancement</h3>

<p>Trois mécanismes, selon la durée et la topologie :</p>

<table>
  <thead>
    <tr>
      <th>Mécanisme</th>
      <th>Quand l’utiliser</th>
      <th>Opérations</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Polling</strong></td>
      <td>Tâches courtes, client simple</td>
      <td><code class="language-plaintext highlighter-rouge">GetTask</code> à intervalle régulier</td>
    </tr>
    <tr>
      <td><strong>Streaming SSE</strong></td>
      <td>Le client reste connecté et veut des mises à jour en temps réel</td>
      <td><code class="language-plaintext highlighter-rouge">SendStreamingMessage</code> (envoi + flux) ou <code class="language-plaintext highlighter-rouge">SubscribeToTask</code> (s’abonner à une tâche existante)</td>
    </tr>
    <tr>
      <td><strong>Notifications push</strong></td>
      <td>Tâches très longues, client déconnecté (mobile, serverless)</td>
      <td>Le client enregistre un webhook ; le serveur y POSTe les mises à jour, authentifiées (JWT signé, HMAC ou mTLS)</td>
    </tr>
  </tbody>
</table>

<p>Exemple de flux SSE reçu :</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data: {"task": {"id": "task-9876", "status": {"state": "TASK_STATE_SUBMITTED"}}}

data: {"statusUpdate": {"taskId": "task-9876", "status": {"state": "TASK_STATE_WORKING"}}}

data: {"artifactUpdate": {"taskId": "task-9876", "artifact": {"artifactId": "a1", "name": "itineraire", "parts": [ ... ]}}}

data: {"statusUpdate": {"taskId": "task-9876", "status": {"state": "TASK_STATE_COMPLETED"}}}
</code></pre></div></div>

<p>Le flux se ferme automatiquement quand la tâche atteint un état terminal.</p>

<h3 id="étape-5--échange-multi-tours-si-nécessaire">Étape 5 — Échange multi-tours (si nécessaire)</h3>

<p>Si l’agent distant a besoin d’une précision (« classe éco ou affaires ? »), la tâche passe en <code class="language-plaintext highlighter-rouge">INPUT_REQUIRED</code>. Le client répond par un nouveau <code class="language-plaintext highlighter-rouge">SendMessage</code> portant le même identifiant de tâche, et le travail reprend.</p>

<h3 id="étape-6--récupération-du-résultat">Étape 6 — Récupération du résultat</h3>

<p>Les <strong>artifacts</strong> contiennent le livrable final. Un client qui a raté le flux peut toujours appeler <code class="language-plaintext highlighter-rouge">GetTask</code> pour obtenir la tâche complète, avec son historique et ses artifacts.</p>

<h3 id="bonus--un-client-minimal-en-python">Bonus : un client minimal en Python</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>

<span class="n">BASE</span> <span class="o">=</span> <span class="s">"https://voyage.monentreprise.example"</span>

<span class="c1"># 1. Découverte
</span><span class="n">card</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">BASE</span><span class="si">}</span><span class="s">/.well-known/agent-card.json"</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">).</span><span class="n">json</span><span class="p">()</span>
<span class="k">if</span> <span class="s">"book_flight"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">{</span><span class="n">s</span><span class="p">[</span><span class="s">"id"</span><span class="p">]</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">card</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"skills"</span><span class="p">,</span> <span class="p">[])}:</span>
    <span class="k">raise</span> <span class="nb">RuntimeError</span><span class="p">(</span><span class="s">"L'agent ne propose pas la compétence book_flight"</span><span class="p">)</span>

<span class="n">endpoint</span> <span class="o">=</span> <span class="n">card</span><span class="p">[</span><span class="s">"supportedInterfaces"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">"url"</span><span class="p">]</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s">"Authorization"</span><span class="p">:</span> <span class="s">"Bearer &lt;jeton&gt;"</span><span class="p">,</span> <span class="s">"Content-Type"</span><span class="p">:</span> <span class="s">"application/json"</span><span class="p">}</span>

<span class="c1"># 2. Envoi de la tâche
</span><span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"jsonrpc"</span><span class="p">:</span> <span class="s">"2.0"</span><span class="p">,</span>
    <span class="s">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="s">"method"</span><span class="p">:</span> <span class="s">"SendMessage"</span><span class="p">,</span>
    <span class="s">"params"</span><span class="p">:</span> <span class="p">{</span>
        <span class="s">"message"</span><span class="p">:</span> <span class="p">{</span>
            <span class="s">"role"</span><span class="p">:</span> <span class="s">"ROLE_USER"</span><span class="p">,</span>
            <span class="s">"parts"</span><span class="p">:</span> <span class="p">[{</span><span class="s">"text"</span><span class="p">:</span> <span class="s">"Réserve un Paris–Lyon le 12/11 pour le matricule 4432."</span><span class="p">}],</span>
        <span class="p">},</span>
        <span class="s">"configuration"</span><span class="p">:</span> <span class="p">{</span><span class="s">"returnImmediately"</span><span class="p">:</span> <span class="bp">True</span><span class="p">},</span>
    <span class="p">},</span>
<span class="p">}</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">payload</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">).</span><span class="n">json</span><span class="p">()[</span><span class="s">"result"</span><span class="p">]</span>

<span class="c1"># 3. Lecture de l'état (polling simple)
</span><span class="n">status</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span>
    <span class="n">endpoint</span><span class="p">,</span>
    <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s">"jsonrpc"</span><span class="p">:</span> <span class="s">"2.0"</span><span class="p">,</span> <span class="s">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">"method"</span><span class="p">:</span> <span class="s">"GetTask"</span><span class="p">,</span>
          <span class="s">"params"</span><span class="p">:</span> <span class="p">{</span><span class="s">"taskId"</span><span class="p">:</span> <span class="n">task</span><span class="p">[</span><span class="s">"task"</span><span class="p">][</span><span class="s">"id"</span><span class="p">]}},</span>
    <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span>
<span class="p">).</span><span class="n">json</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="n">status</span><span class="p">)</span>
</code></pre></div></div>

<blockquote>
  <p>En production, préférez les SDK officiels (Python, JavaScript, Java, Go, .NET) publiés par le projet A2A plutôt que des appels HTTP à la main. La forme exacte de <code class="language-plaintext highlighter-rouge">result</code> dépend de la réponse du serveur (<code class="language-plaintext highlighter-rouge">task</code> ou <code class="language-plaintext highlighter-rouge">message</code>).</p>
</blockquote>

<hr />

<h2 id="6-cycle-de-vie-dune-tâche">6. Cycle de vie d’une tâche</h2>

<p><img src="/articles-tech/assets/images/cycle-de-vie-tache.svg" alt="Cycle de vie d'une tâche A2A : états en cours, interrompus et terminaux" /></p>

<table>
  <thead>
    <tr>
      <th>État (v1.0)</th>
      <th>Catégorie</th>
      <th>Signification</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_SUBMITTED</code></td>
      <td>en cours</td>
      <td>Acceptée, pas encore démarrée</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_WORKING</code></td>
      <td>en cours</td>
      <td>Traitement actif</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_INPUT_REQUIRED</code></td>
      <td>interrompue</td>
      <td>En attente d’une information du client</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_AUTH_REQUIRED</code></td>
      <td>interrompue</td>
      <td>Le client doit fournir des identifiants supplémentaires</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_COMPLETED</code></td>
      <td>terminale</td>
      <td>Terminée avec succès</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_FAILED</code></td>
      <td>terminale</td>
      <td>Erreur irrécupérable</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_CANCELED</code></td>
      <td>terminale</td>
      <td>Annulée (souvent via <code class="language-plaintext highlighter-rouge">CancelTask</code>)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TASK_STATE_REJECTED</code></td>
      <td>terminale</td>
      <td>Refusée par l’agent (politique, quota, capacité)</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="7-sécurité">7. Sécurité</h2>

<p>A2A s’appuie sur des standards du web plutôt que d’inventer les siens :</p>

<ul>
  <li><strong>Transport chiffré</strong> : HTTPS (TLS 1.2 minimum) obligatoire en production.</li>
  <li><strong>Schémas d’authentification</strong> alignés sur OpenAPI : OAuth 2.0, OpenID Connect, clé d’API, mTLS. Déclarés dans <code class="language-plaintext highlighter-rouge">securitySchemes</code> et exigés via <code class="language-plaintext highlighter-rouge">security</code>.</li>
  <li><strong>Agent Cards signées</strong> : une signature JWS (RFC 7515) calculée sur une forme canonique de la carte (RFC 8785) permet de détecter toute falsification — une carte modifiée pourrait sinon rediriger les appels ou injecter du contenu malveillant dans le prompt de l’agent client.</li>
  <li><strong>Agent Card étendue</strong> : un agent peut exposer une carte publique minimale et une carte plus détaillée, accessible seulement après authentification.</li>
  <li><strong>Traçabilité</strong> : la spécification recommande OpenTelemetry et la propagation des en-têtes W3C Trace Context (<code class="language-plaintext highlighter-rouge">traceparent</code>) pour suivre une requête à travers toute la chaîne d’agents.</li>
</ul>

<p>Pour un niveau d’assurance élevé : combiner mTLS et OAuth2, ou utiliser des jetons liés à l’émetteur (DPoP, RFC 9449), afin qu’un jeton volé soit inutilisable depuis une autre machine.</p>

<hr />

<h2 id="8-a2a-vs-mcp">8. A2A vs MCP</h2>

<h3 id="rappel--quest-ce-que-mcp-">Rappel : qu’est-ce que MCP ?</h3>

<p><strong>MCP (Model Context Protocol)</strong>, lancé par Anthropic fin 2024 et désormais largement adopté, standardise la connexion entre une application IA (le <em>client/host</em>) et des <strong>serveurs</strong> qui exposent des <strong>outils</strong> (tools), des <strong>ressources</strong> (données) et des <strong>prompts</strong>. Il fonctionne sur JSON-RPC 2.0, en local via <code class="language-plaintext highlighter-rouge">stdio</code> ou à distance via HTTP. Ses spécifications sont nommées par date ; la révision <strong>2026-07-28</strong> a notamment rendu le protocole <strong>sans état</strong> (suppression des sessions et de la poignée de main d’initialisation), ce qui facilite le déploiement horizontal des serveurs.</p>

<h3 id="tableau-comparatif">Tableau comparatif</h3>

<table>
  <thead>
    <tr>
      <th>Critère</th>
      <th><strong>A2A</strong></th>
      <th><strong>MCP</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Objet</strong></td>
      <td>Faire collaborer des agents autonomes entre eux</td>
      <td>Donner à un agent l’accès à des outils et des données</td>
    </tr>
    <tr>
      <td><strong>Analogie</strong></td>
      <td>Un collègue à qui l’on délègue un dossier</td>
      <td>Une boîte à outils que l’on manipule soi-même</td>
    </tr>
    <tr>
      <td><strong>Nature de l’interlocuteur</strong></td>
      <td>Opaque, autonome, raisonne par lui-même</td>
      <td>Fonction déterministe ou source de données exposée</td>
    </tr>
    <tr>
      <td><strong>Unité d’échange</strong></td>
      <td>Tâche (<code class="language-plaintext highlighter-rouge">Task</code>) avec état et cycle de vie</td>
      <td>Appel d’outil / lecture de ressource</td>
    </tr>
    <tr>
      <td><strong>Durée typique</strong></td>
      <td>Secondes à jours, multi-tours</td>
      <td>Généralement courte, requête/réponse</td>
    </tr>
    <tr>
      <td><strong>Interaction</strong></td>
      <td>Négociation possible (<code class="language-plaintext highlighter-rouge">INPUT_REQUIRED</code>, <code class="language-plaintext highlighter-rouge">AUTH_REQUIRED</code>)</td>
      <td>Le modèle appelle, l’outil répond</td>
    </tr>
    <tr>
      <td><strong>Découverte</strong></td>
      <td>Agent Card publique (<code class="language-plaintext highlighter-rouge">/.well-known/agent-card.json</code>), registres</td>
      <td>Listing des outils/ressources auprès du serveur connecté</td>
    </tr>
    <tr>
      <td><strong>Transports</strong></td>
      <td>JSON-RPC 2.0, gRPC, HTTP+JSON</td>
      <td>JSON-RPC 2.0 sur stdio ou HTTP</td>
    </tr>
    <tr>
      <td><strong>Asynchrone / long</strong></td>
      <td>Natif : SSE, notifications push par webhook</td>
      <td>Possible mais moins central</td>
    </tr>
    <tr>
      <td><strong>Contenus</strong></td>
      <td>Multimodal via Parts (texte, JSON, fichiers, octets)</td>
      <td>Résultats structurés, texte, images, ressources</td>
    </tr>
    <tr>
      <td><strong>Topologie</strong></td>
      <td>Pair à pair, rôles client/serveur interchangeables</td>
      <td>Hôte → client → serveur</td>
    </tr>
    <tr>
      <td><strong>Origine / gouvernance</strong></td>
      <td>Google → Linux Foundation</td>
      <td>Anthropic → gouvernance ouverte de la communauté MCP</td>
    </tr>
    <tr>
      <td><strong>Maturité (oct. 2026)</strong></td>
      <td>v1.0 stable (mars 2026)</td>
      <td>Révision 2026-07-28 stable</td>
    </tr>
  </tbody>
</table>

<h3 id="la-bonne-question-à-se-poser">La bonne question à se poser</h3>

<blockquote>
  <p><em>« Est-ce que je veux <strong>exécuter une action précise</strong> ou <strong>confier un objectif</strong> à quelqu’un qui saura s’organiser ? »</em></p>
</blockquote>

<ul>
  <li><strong>Action précise et déterministe</strong> (requête SQL, lecture d’un fichier, appel d’API météo) → <strong>MCP</strong>.</li>
  <li><strong>Objectif à atteindre</strong> par un système qui raisonne, peut poser des questions, et dont on ne veut pas connaître l’implémentation → <strong>A2A</strong>.</li>
</ul>

<p>Il existe une zone grise : on peut exposer un agent complet comme un « outil » MCP. Cela fonctionne pour des cas simples, mais on perd ce qui fait la force d’A2A : le suivi d’état, les échanges multi-tours, les tâches longues et la découverte inter-organisations.</p>

<hr />

<h2 id="9-utiliser-a2a-et-mcp-ensemble">9. Utiliser A2A et MCP ensemble</h2>

<p>Les deux protocoles sont <strong>complémentaires</strong> et forment ensemble la pile de communication des systèmes multi-agents :</p>

<p><img src="/articles-tech/assets/images/a2a-mcp-ensemble.svg" alt="Un orchestrateur délègue via A2A à deux agents, qui utilisent chacun leurs outils via MCP" /></p>

<p><strong>Scénario :</strong></p>

<ol>
  <li>L’utilisateur demande à l’orchestrateur : « Organise l’entretien de Marie à Lyon jeudi. »</li>
  <li>L’orchestrateur délègue via <strong>A2A</strong> à l’agent RH (« planifie l’entretien ») et à l’agent Voyage (« réserve le déplacement »).</li>
  <li>Chacun de ces agents utilise <strong>MCP</strong> en interne pour interroger ses propres outils : agenda, SIRH, API de réservation.</li>
  <li>Les agents renvoient leurs <strong>artifacts</strong> (créneau confirmé, billet) à l’orchestrateur via A2A.</li>
</ol>

<p>Règle pratique : <strong>A2A entre agents, MCP entre un agent et ses outils.</strong></p>

<hr />

<h2 id="10-limites-actuelles-et-bonnes-pratiques">10. Limites actuelles et bonnes pratiques</h2>

<p><strong>Limites à connaître :</strong></p>

<ul>
  <li><strong>Schéma des données par skill</strong> : un skill déclare accepter <code class="language-plaintext highlighter-rouge">application/json</code>, mais la spécification ne standardise pas le schéma exact attendu. Solution : publier un JSON Schema dans la description du skill ou via une <em>Extension</em> déclarée dans l’Agent Card.</li>
  <li><strong>Réduction des droits en cascade</strong> : A2A dit comment transmettre des identifiants, pas comment les restreindre lorsqu’un orchestrateur très privilégié délègue à un agent moins privilégié. Solution : OAuth 2.0 Token Exchange (RFC 8693) pour émettre des jetons courts et ciblés.</li>
  <li><strong>Registre non standardisé</strong> : la découverte via un catalogue central reste à construire soi-même.</li>
  <li><strong>Migration 0.x → 1.0</strong> : noms de méthodes (<code class="language-plaintext highlighter-rouge">message/send</code> → <code class="language-plaintext highlighter-rouge">SendMessage</code>), noms d’états préfixés, disparition des champs <code class="language-plaintext highlighter-rouge">kind</code> et <code class="language-plaintext highlighter-rouge">final</code>.</li>
</ul>

<p><strong>Bonnes pratiques :</strong></p>

<ul>
  <li>Considérer l’Agent Card comme un <strong>contrat</strong> : tout changement de skill, d’URL ou de capacité doit incrémenter la version.</li>
  <li><strong>Signer</strong> les Agent Cards et vérifier les signatures côté client.</li>
  <li>Servir la carte avec <code class="language-plaintext highlighter-rouge">Content-Type: application/json</code>.</li>
  <li>Placer une <strong>passerelle API</strong> devant les agents pour centraliser authentification, quotas, journalisation et gestion des connexions longues.</li>
  <li>Instrumenter avec <strong>OpenTelemetry</strong> dès le départ : déboguer une chaîne de cinq agents sans traces distribuées est très pénible.</li>
  <li>Ne jamais faire confiance aveuglément au contenu reçu d’un autre agent : c’est une donnée, pas une instruction (risque d’injection de prompt).</li>
</ul>

<hr />

<h2 id="11-conclusion">11. Conclusion</h2>

<p>A2A fait pour les agents ce que HTTP a fait pour les documents ou SMTP pour les e-mails : un <strong>langage commun et neutre</strong> qui permet à des systèmes hétérogènes de coopérer sans intégration sur mesure. Avec sa v1.0 stable, sa gouvernance par la Linux Foundation et son adoption par les grandes plateformes, il est désormais utilisable en production.</p>

<p>MCP et A2A ne sont pas concurrents : <strong>MCP donne des mains à un agent, A2A lui donne des collègues.</strong> Une architecture multi-agents moderne utilise les deux.</p>

<hr />

<h2 id="pour-aller-plus-loin">Pour aller plus loin</h2>

<ul>
  <li><a href="https://a2a-protocol.org/latest/specification/">Spécification officielle A2A</a></li>
  <li><a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">A2A et MCP — relation entre les deux protocoles (site officiel A2A)</a></li>
  <li><a href="https://modelcontextprotocol.io/specification/latest">Spécification MCP</a></li>
  <li><a href="https://tyk.io/learning-center/a2a-protocol-architecture-and-technical-specification/">Guide technique A2A v1.0 — Tyk</a></li>
  <li><a href="https://metorial.com/for-ai-crawlers/mcp-vs-a2a">MCP vs A2A — Metorial</a></li>
</ul>]]></content><author><name>marwa SAHLI</name></author><category term="ia" /><category term="agents" /><category term="protocoles" /><category term="A2A" /><category term="MCP" /><category term="agents" /><category term="interopérabilité" /><category term="LLM" /><summary type="html"><![CDATA[Le protocole A2A : fonctionnement, mode opératoire et comparaison avec MCP]]></summary></entry><entry xml:lang="en"><title type="html">Authentication protocols for applications: REST APIs, MCP servers and beyond</title><link href="https://marwashli.github.io/articles-tech/en/2026/10/authentication-protocols-api-mcp/" rel="alternate" type="text/html" title="Authentication protocols for applications: REST APIs, MCP servers and beyond" /><published>2026-10-24T00:00:00+00:00</published><updated>2026-10-24T00:00:00+00:00</updated><id>https://marwashli.github.io/articles-tech/en/2026/10/authentication-protocols-api-mcp</id><content type="html" xml:base="https://marwashli.github.io/articles-tech/en/2026/10/authentication-protocols-api-mcp/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>There is no single ideal authentication protocol, only the right protocol for each situation: who is calling (a human, a service, an AI agent), on whose behalf, and at what level of risk. An API key is enough for an internal script; a consumer application requires OAuth 2.0 and OpenID Connect; a remote MCP server now follows a precise OAuth 2.1 profile.</p>

<p>Two notions are often confused:</p>

<ul>
  <li><strong>Authentication (AuthN)</strong>: proving <em>who</em> you are (a user, an application, a machine).</li>
  <li><strong>Authorization (AuthZ)</strong>: determining <em>what</em> you are allowed to do (read, write, administer).</li>
</ul>

<p>OAuth 2.0 is a delegated <strong>authorization</strong> protocol; OpenID Connect adds the <strong>authentication</strong> layer on top. That distinction explains why the two are almost always used together.</p>

<p>This article reviews the methods, from the simplest to the most robust, then looks in detail at MCP (Model Context Protocol) servers, which connect AI agents to tools and data.</p>

<!--more-->

<h2 id="simple-methods-basic-auth-and-api-keys">Simple methods: Basic Auth and API keys</h2>

<p>Both methods rely on a static secret sent with every request. They are easy to implement and suit internal or server-to-server exchanges, but never an application acting on behalf of third-party users.</p>

<h3 id="http-basic-authentication">HTTP Basic Authentication</h3>

<p>The client sends the username and password, Base64-encoded, in the <code class="language-plaintext highlighter-rouge">Authorization</code> header:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">GET</span> <span class="nn">/api/orders</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Authorization</span><span class="p">:</span> <span class="s">Basic YWxpY2U6czNjcjN0</span>
</code></pre></div></div>

<ul>
  <li><strong>Pros</strong>: standard (RFC 7617), supported everywhere, no infrastructure.</li>
  <li><strong>Limits</strong>: Base64 is not encryption, so HTTPS is mandatory; the password travels with every call; no expiry, no scope, revocation = changing the password.</li>
  <li><strong>Reasonable use</strong>: internal tools, admin endpoints behind a VPN, the initial exchange to obtain a token.</li>
</ul>

<h3 id="api-keys">API keys</h3>

<p>A long random string identifies the calling application, sent in a dedicated header:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">GET</span> <span class="nn">/v1/weather?city=Lyon</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">X-API-Key</span><span class="p">:</span> <span class="s">sk_live_4f9a…</span>
</code></pre></div></div>

<ul>
  <li><strong>Pros</strong>: easy to issue, revoke and rotate; ideal for per-customer billing and quotas.</li>
  <li><strong>Limits</strong>: the key identifies an <em>application</em>, not a <em>user</em>; it is a “bearer” secret (whoever holds it can use it); often no expiry and no fine-grained scope.</li>
  <li><strong>Best practices</strong>: send it in a header, never in the URL (it would end up in logs); store only a hash on the server side; prefix keys (<code class="language-plaintext highlighter-rouge">sk_live_</code>, <code class="language-plaintext highlighter-rouge">sk_test_</code>) to make leaks easier to detect; allow several active keys so they can be rotated without downtime.</li>
</ul>

<h2 id="tokens-sessions-bearer-and-jwt">Tokens: sessions, Bearer and JWT</h2>

<p>Instead of sending a permanent secret, the client exchanges proof of identity for a <strong>short-lived token</strong>. This is the basis of every modern method.</p>

<h3 id="sessions-and-cookies">Sessions and cookies</h3>

<p>After login, the server creates a session and returns its identifier in a cookie. This is the historical model for server-rendered web applications.</p>

<ul>
  <li><strong>Strengths</strong>: immediate revocation (delete the session); <code class="language-plaintext highlighter-rouge">HttpOnly</code>, <code class="language-plaintext highlighter-rouge">Secure</code> and <code class="language-plaintext highlighter-rouge">SameSite</code> cookies protect against theft by JavaScript.</li>
  <li><strong>Limits</strong>: server-side state (shared storage needed across instances), CSRF protection required, poorly suited to mobile clients or APIs consumed by third parties.</li>
</ul>

<h3 id="bearer-tokens">Bearer tokens</h3>

<p>A bearer token (RFC 6750) is sent in the <code class="language-plaintext highlighter-rouge">Authorization: Bearer &lt;token&gt;</code> header. Whoever holds it can use it, so it must be short-lived, sent over HTTPS and never logged. The token can be <strong>opaque</strong> (a string the server checks with the issuer through introspection, RFC 7662) or <strong>self-contained</strong> (a JWT).</p>

<h3 id="json-web-token-jwt">JSON Web Token (JWT)</h3>

<p>A JWT (RFC 7519) is made of three Base64URL-encoded parts: <code class="language-plaintext highlighter-rouge">header.payload.signature</code>. The payload contains <em>claims</em>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://auth.example.com"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"user_8241"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"aud"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.example.com"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1793011200</span><span class="p">,</span><span class="w">
  </span><span class="nl">"scope"</span><span class="p">:</span><span class="w"> </span><span class="s2">"orders:read"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The API verifies the signature with the issuer’s public key (published as a JWKS), then checks <code class="language-plaintext highlighter-rouge">iss</code>, <code class="language-plaintext highlighter-rouge">aud</code> and <code class="language-plaintext highlighter-rouge">exp</code>, with no network call.</p>

<ul>
  <li><strong>Strengths</strong>: fast local verification, ideal for microservices.</li>
  <li><strong>Pitfalls</strong>: a JWT cannot be revoked before it expires (hence short lifetimes, 5 to 15 minutes, plus refresh tokens); reject the <code class="language-plaintext highlighter-rouge">none</code> algorithm and pin the list of accepted algorithms (RS256, ES256…); the payload is readable by anyone, it is not encrypted; never forget to check the <code class="language-plaintext highlighter-rouge">aud</code> audience.</li>
</ul>

<p>A JWT is a token <strong>format</strong>, not a protocol: <em>how</em> you obtain that token is what OAuth 2.0 defines.</p>

<h2 id="oauth-20-and-oauth-21-delegated-authorization">OAuth 2.0 and OAuth 2.1: delegated authorization</h2>

<p>OAuth 2.0 (RFC 6749) lets an application access an API <strong>on behalf of a user, without ever knowing their password</strong>. It is the de facto standard for public APIs (Google, GitHub, Microsoft, Salesforce…). OAuth 2.1, being finalised at the IETF, consolidates a decade of best practice: PKCE becomes mandatory, and the implicit and “password” flows are removed.</p>

<h3 id="the-four-roles">The four roles</h3>

<table>
  <thead>
    <tr>
      <th>Role</th>
      <th>Description</th>
      <th>Example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Resource owner</td>
      <td>The user who gives consent</td>
      <td>Alice</td>
    </tr>
    <tr>
      <td>Client</td>
      <td>The application requesting access</td>
      <td>A mobile app, an MCP client</td>
    </tr>
    <tr>
      <td>Authorization server</td>
      <td>Authenticates and issues tokens</td>
      <td>Keycloak, Auth0, Entra ID, Okta</td>
    </tr>
    <tr>
      <td>Resource server</td>
      <td>The protected API that validates tokens</td>
      <td>The REST API, the MCP server</td>
    </tr>
  </tbody>
</table>

<h3 id="the-recommended-flow-authorization-code--pkce">The recommended flow: Authorization Code + PKCE</h3>

<ol>
  <li>The client generates a random secret (<code class="language-plaintext highlighter-rouge">code_verifier</code>) and its SHA-256 hash (<code class="language-plaintext highlighter-rouge">code_challenge</code>).</li>
  <li>It redirects the user to the authorization server with <code class="language-plaintext highlighter-rouge">client_id</code>, <code class="language-plaintext highlighter-rouge">redirect_uri</code>, <code class="language-plaintext highlighter-rouge">scope</code>, <code class="language-plaintext highlighter-rouge">state</code> and <code class="language-plaintext highlighter-rouge">code_challenge</code>.</li>
  <li>The user authenticates and gives consent.</li>
  <li>The authorization server redirects back to the client with a single-use <strong>authorization code</strong>.</li>
  <li>The client exchanges this code, together with the <code class="language-plaintext highlighter-rouge">code_verifier</code>, for an <strong>access token</strong> (and often a <strong>refresh token</strong>).</li>
  <li>The client calls the API with <code class="language-plaintext highlighter-rouge">Authorization: Bearer &lt;access_token&gt;</code>.</li>
</ol>

<p>PKCE (RFC 7636) prevents an attacker who intercepts the code from exchanging it, since they don’t know the <code class="language-plaintext highlighter-rouge">code_verifier</code>. It is now required for every client, including server-side applications.</p>

<h3 id="other-flows-grant-types">Other flows (grant types)</h3>

<table>
  <thead>
    <tr>
      <th>Flow</th>
      <th>Use case</th>
      <th>User present?</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Authorization Code + PKCE</td>
      <td>Web apps, mobile apps, SPAs, MCP clients</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Client Credentials</td>
      <td>Service to service, scheduled jobs, back ends</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Device Authorization (RFC 8628)</td>
      <td>TVs, CLIs, connected devices without a browser</td>
      <td>Yes, on another device</td>
    </tr>
    <tr>
      <td>Refresh Token</td>
      <td>Renew an expired access token without logging in again</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Token Exchange (RFC 8693)</td>
      <td>A service swaps a token it received for a token for a downstream API</td>
      <td>No</td>
    </tr>
    <tr>
      <td><del>Implicit</del>, <del>Password</del></td>
      <td>Removed in OAuth 2.1</td>
      <td>—</td>
    </tr>
  </tbody>
</table>

<h3 id="scopes-audiences-and-refresh-tokens">Scopes, audiences and refresh tokens</h3>

<ul>
  <li><strong>Scopes</strong> (<code class="language-plaintext highlighter-rouge">orders:read</code>, <code class="language-plaintext highlighter-rouge">profile</code>) limit what the token allows; request the bare minimum.</li>
  <li>The <strong>resource</strong> parameter (RFC 8707) binds the token to a specific API: a token issued for API A must be rejected by API B.</li>
  <li><strong>Refresh tokens</strong> must be rotated for public clients (each use issues a new one and invalidates the old one).</li>
</ul>

<h2 id="openid-connect-and-saml-federated-identity-and-sso">OpenID Connect and SAML: federated identity and SSO</h2>

<p>When the application needs to know <strong>who the user is</strong> (single sign-on, “Sign in with Google”), you add an identity layer on top of OAuth: OpenID Connect for modern applications, SAML for existing enterprise systems.</p>

<h3 id="openid-connect-oidc">OpenID Connect (OIDC)</h3>

<p>OIDC reuses OAuth 2.0’s Authorization Code flow and adds the <code class="language-plaintext highlighter-rouge">openid</code> scope. The authorization server (called the <em>OpenID Provider</em>) returns, in addition to the access token, an <strong>ID token</strong>: a signed JWT describing the user (<code class="language-plaintext highlighter-rouge">sub</code>, <code class="language-plaintext highlighter-rouge">email</code>, <code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">auth_time</code>…).</p>

<ul>
  <li><strong>Automatic discovery</strong>: <code class="language-plaintext highlighter-rouge">/.well-known/openid-configuration</code> publishes the endpoints, the algorithms and the public key (JWKS).</li>
  <li><strong>UserInfo endpoint</strong> to obtain additional attributes.</li>
  <li><strong>Golden rule</strong>: the ID token is for the <em>client</em> to learn who the user is; the access token is for calling the <em>API</em>. Never send an ID token to an API as an access token.</li>
</ul>

<h3 id="saml-20">SAML 2.0</h3>

<p>SAML (Security Assertion Markup Language) exchanges <strong>signed XML assertions</strong> between an identity provider (IdP: ADFS, Okta, Entra ID) and a service provider (SP), through browser redirects.</p>

<ul>
  <li><strong>Strengths</strong>: very widespread in large enterprises and B2B SaaS, mature.</li>
  <li><strong>Limits</strong>: verbose, hard to implement correctly (XML signature wrapping attacks), designed for the browser rather than for APIs or mobile apps.</li>
  <li><strong>Trend</strong>: new projects favour OIDC; SAML is still needed to integrate with existing corporate directories, often through an identity broker (Keycloak, Auth0) that translates SAML into OIDC.</li>
</ul>

<h3 id="what-about-the-users-themselves">What about the users themselves?</h3>

<p>OIDC and SAML delegate identity verification to the identity provider, which can then use multi-factor authentication (MFA) or <strong>passkeys</strong> (WebAuthn/FIDO2), today the most phishing-resistant method.</p>

<h2 id="mtls-hmac-signatures-and-dpop-strong-authentication-between-machines">mTLS, HMAC signatures and DPoP: strong authentication between machines</h2>

<p>These mechanisms address the main weakness of bearer tokens (a stolen token can be used by anyone) by requiring <strong>proof of possession</strong> of a key that only the legitimate client holds.</p>

<h3 id="mutual-tls-mtls">Mutual TLS (mTLS)</h3>

<p>In a classic TLS handshake, only the server presents a certificate. With mTLS, <strong>the client presents one too</strong>, signed by a certificate authority the server trusts.</p>

<ul>
  <li><strong>Uses</strong>: microservice communication (often automated by a <em>service mesh</em> such as Istio or Linkerd), banking APIs and Open Banking, zero-trust architectures.</li>
  <li><strong>With OAuth</strong>: RFC 8705 binds an access token to the client’s certificate (a <em>certificate-bound token</em>); a stolen token is useless without the private key.</li>
  <li><strong>Constraints</strong>: certificate lifecycle management (issuance, rotation, revocation); harder to deploy in browsers or on mobile.</li>
</ul>

<h3 id="request-signing-hmac">Request signing (HMAC)</h3>

<p>The client and the server share a secret key. The client computes a signature (HMAC-SHA256) over parts of the request (method, path, timestamp, body hash) and sends it in a header. The server recomputes it and compares.</p>

<ul>
  <li><strong>Examples</strong>: AWS Signature Version 4, webhook verification (Stripe, GitHub, Slack).</li>
  <li><strong>Strengths</strong>: the secret never travels; message integrity is guaranteed; the timestamp blocks replay.</li>
  <li><strong>Standardisation</strong>: RFC 9421 (HTTP Message Signatures) now standardises this approach, including with asymmetric keys.</li>
</ul>

<h3 id="dpop-demonstrating-proof-of-possession">DPoP (Demonstrating Proof of Possession)</h3>

<p>DPoP (RFC 9449) brings proof of possession to the application layer, without a certificate infrastructure. The client generates a key pair and attaches a small signed JWT (the <code class="language-plaintext highlighter-rouge">DPoP</code> header) to every request, covering the method, the URL and a timestamp. The authorization server binds the access token to that public key.</p>

<p>DPoP is a good fit for SPAs and mobile apps, where mTLS is hard to deploy.</p>

<h2 id="the-case-of-mcp-servers">The case of MCP servers</h2>

<p>A remote MCP server is an <strong>OAuth 2.1 resource server</strong>: under the 2026-07-28 revision of the specification, the client discovers the authorization server on its own, identifies itself with a URL (Client ID Metadata Document) and obtains a token valid for that server only. A local (stdio) MCP server, on the other hand, simply reads its credentials from the environment.</p>

<h3 id="reminder-what-is-mcp">Reminder: what is MCP?</h3>

<p>The Model Context Protocol is an open protocol that connects AI applications (Claude, ChatGPT, VS Code, Cursor…) to tools and data sources through “MCP servers”. What makes it special from a security standpoint: an agent can connect to a server it has never seen before, with no prior relationship between the client’s vendor and the server’s. The authentication profile is designed for exactly that scenario.</p>

<h3 id="two-transports-two-approaches">Two transports, two approaches</h3>

<p>The specification makes authorization <strong>optional</strong>, but sets out what to do depending on the transport.</p>

<table>
  <thead>
    <tr>
      <th>Transport</th>
      <th>Where the server runs</th>
      <th>Authentication</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>stdio</td>
      <td>Local process launched by the host application</td>
      <td>Do not follow the OAuth specification; read credentials from the environment (variables, config files)</td>
    </tr>
    <tr>
      <td>Streamable HTTP</td>
      <td>Remote server reachable by URL</td>
      <td>The MCP specification’s OAuth 2.1 profile</td>
    </tr>
  </tbody>
</table>

<p>The old HTTP+SSE transport (2024-11-05 revision) is deprecated.</p>

<h3 id="the-authorization-flow-step-by-step">The authorization flow, step by step</h3>

<p><img src="/articles-tech/assets/images/flux-autorisation-mcp-en.svg" alt="MCP authorization flow: 10 exchanges between the MCP client, the MCP server and the authorization server" /></p>

<p>The client only needs the MCP server’s URL: an initial <code class="language-plaintext highlighter-rouge">401</code> rejection tells it where to find the metadata; it then runs a standard Authorization Code + PKCE flow and replays its request with the token.</p>

<p>The server’s <code class="language-plaintext highlighter-rouge">401</code> response looks like this:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">401</span> <span class="ne">Unauthorized</span>
<span class="na">WWW-Authenticate</span><span class="p">:</span> <span class="s">Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"</span>
</code></pre></div></div>

<h3 id="the-standard-building-blocks-involved">The standard building blocks involved</h3>

<table>
  <thead>
    <tr>
      <th>Building block</th>
      <th>Reference</th>
      <th>Role in MCP</th>
      <th>Requirement</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Protected Resource Metadata</td>
      <td>RFC 9728</td>
      <td>The MCP server states which authorization server to use</td>
      <td>Mandatory (server and client)</td>
    </tr>
    <tr>
      <td>Authorization server metadata</td>
      <td>RFC 8414 and OpenID Connect Discovery</td>
      <td>Endpoint discovery</td>
      <td>The client must support both</td>
    </tr>
    <tr>
      <td>PKCE</td>
      <td>RFC 7636</td>
      <td>Protects the code exchange</td>
      <td>Mandatory</td>
    </tr>
    <tr>
      <td>Resource Indicators</td>
      <td>RFC 8707</td>
      <td>Binds the token to the target MCP server</td>
      <td>Mandatory</td>
    </tr>
    <tr>
      <td>Client ID Metadata Documents (CIMD)</td>
      <td>IETF draft</td>
      <td>The <code class="language-plaintext highlighter-rouge">client_id</code> is an HTTPS URL pointing to a JSON document describing the client</td>
      <td>Recommended</td>
    </tr>
    <tr>
      <td>Dynamic Client Registration</td>
      <td>RFC 7591</td>
      <td>Automatic client registration</td>
      <td>Optional, <strong>deprecated</strong> in 2026-07-28</td>
    </tr>
    <tr>
      <td>Issuer validation</td>
      <td>RFC 9207</td>
      <td>The client checks <code class="language-plaintext highlighter-rouge">iss</code> before exchanging the code</td>
      <td>Mandatory since 2026-07-28</td>
    </tr>
  </tbody>
</table>

<h3 id="client-registration-the-order-of-precedence">Client registration: the order of precedence</h3>

<p>How does an MCP client obtain a <code class="language-plaintext highlighter-rouge">client_id</code> from an authorization server it doesn’t know? The specification recommends this order:</p>

<ol>
  <li>Use <strong>pre-registered</strong> credentials for that server, if it has any.</li>
  <li>Use a <strong>Client ID Metadata Document</strong> if the authorization server advertises <code class="language-plaintext highlighter-rouge">client_id_metadata_document_supported</code>.</li>
  <li>Fall back to <strong>Dynamic Client Registration</strong> if the server exposes a <code class="language-plaintext highlighter-rouge">registration_endpoint</code>.</li>
  <li>Otherwise, ask the user to enter the client details themselves.</li>
</ol>

<p>CIMD has the advantage of being portable: the identifier is a URL hosted by the client’s vendor, resolved on demand by any authorization server, with no registration database to maintain.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"client_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://app.example.com/oauth/client-metadata.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"client_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My MCP client"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"redirect_uris"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"http://127.0.0.1:3000/callback"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"grant_types"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"authorization_code"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"token_endpoint_auth_method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="mcp-specific-security-points">MCP-specific security points</h3>

<ul>
  <li><strong>No token passthrough</strong>: the MCP server must never forward the token it received from the client to a downstream API; it obtains its own token (for example via Token Exchange).</li>
  <li><strong>Audience validation</strong>: the MCP server only accepts tokens issued specifically for it.</li>
  <li><strong>Issuer-bound credentials</strong>: a client must associate its credentials with the authorization server that issued them and never reuse them with another one.</li>
  <li><strong>Incremental consent</strong>: since the 2025-11-25 revision, a client can request additional scopes at the moment a tool needs them, rather than asking for everything up front.</li>
  <li><strong>stdio servers</strong>: secrets in the environment should have minimal scope, because the local process runs with the user’s permissions.</li>
</ul>

<h2 id="comparison-and-decision-guide">Comparison and decision guide</h2>

<p>The right choice depends first on whether a human user is involved, then on the level of risk.</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Identifies</th>
      <th>Expiry / revocation</th>
      <th>Complexity</th>
      <th>Typical use case</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Basic Auth</td>
      <td>A user or a technical account</td>
      <td>None / password change</td>
      <td>Very low</td>
      <td>Internal tools, administration</td>
    </tr>
    <tr>
      <td>API key</td>
      <td>An application</td>
      <td>Manual / simple revocation</td>
      <td>Low</td>
      <td>Public APIs with quotas, server integrations</td>
    </tr>
    <tr>
      <td>Session + cookie</td>
      <td>A user</td>
      <td>Configurable / immediate</td>
      <td>Low</td>
      <td>Classic web applications</td>
    </tr>
    <tr>
      <td>JWT</td>
      <td>User or application</td>
      <td>Short / hard before expiry</td>
      <td>Medium</td>
      <td>Microservices, stateless APIs</td>
    </tr>
    <tr>
      <td>OAuth 2.0 / 2.1</td>
      <td>An application acting for a user</td>
      <td>Short + refresh / via the authorization server</td>
      <td>Medium to high</td>
      <td>APIs open to third parties, remote MCP clients</td>
    </tr>
    <tr>
      <td>OpenID Connect</td>
      <td>A user (identity)</td>
      <td>Short</td>
      <td>Medium</td>
      <td>SSO, “Sign in with…”</td>
    </tr>
    <tr>
      <td>SAML 2.0</td>
      <td>A user (identity)</td>
      <td>Short</td>
      <td>High</td>
      <td>Legacy enterprise SSO</td>
    </tr>
    <tr>
      <td>mTLS</td>
      <td>A machine</td>
      <td>Certificate lifetime / CRL, OCSP</td>
      <td>High</td>
      <td>Zero trust, service mesh, finance</td>
    </tr>
    <tr>
      <td>HMAC signature</td>
      <td>An application + message integrity</td>
      <td>Per request (timestamp)</td>
      <td>Medium</td>
      <td>Webhooks, cloud APIs</td>
    </tr>
    <tr>
      <td>DPoP</td>
      <td>Application, token bound to a key</td>
      <td>Short</td>
      <td>Medium</td>
      <td>High-security SPAs and mobile apps</td>
    </tr>
  </tbody>
</table>

<p><strong>Decision shortcut:</strong></p>

<ul>
  <li>A user signs in to <em>your</em> application → <strong>OIDC</strong> (with passkeys or MFA at the identity provider).</li>
  <li>A third-party application accesses a user’s data → <strong>OAuth 2.1 Authorization Code + PKCE</strong>.</li>
  <li>A service calls another service → <strong>Client Credentials</strong>, hardened with <strong>mTLS</strong> or <strong>DPoP</strong> if the data is sensitive.</li>
  <li>You receive webhooks → <strong>HMAC signature</strong>.</li>
  <li>An internal script or a prototype → <strong>API key</strong>, with rotation.</li>
  <li>A remote MCP server → <strong>the MCP specification’s OAuth 2.1 profile</strong> (previous section); a local MCP server → <strong>environment variables</strong>.</li>
</ul>

<h2 id="best-practices-and-conclusion">Best practices and conclusion</h2>

<p>Whatever protocol you pick, most incidents come from implementation details rather than from the protocol itself.</p>

<ul>
  <li><strong>HTTPS everywhere</strong>, no exceptions, including internally.</li>
  <li><strong>Don’t reinvent</strong>: rely on a proven authorization server (Keycloak, Auth0, Entra ID, Okta, Cognito…) and maintained libraries.</li>
  <li><strong>Short-lived tokens</strong> (minutes) and rotated refresh tokens.</li>
  <li><strong>Validate every token</strong>: signature, issuer <code class="language-plaintext highlighter-rouge">iss</code>, audience <code class="language-plaintext highlighter-rouge">aud</code>, expiry <code class="language-plaintext highlighter-rouge">exp</code>, scopes.</li>
  <li><strong>Least privilege</strong>: minimal scopes, one token per target API.</li>
  <li><strong>Secrets out of the code</strong>: a vault (Vault, AWS Secrets Manager…) or environment variables, never in Git.</li>
  <li><strong>Nothing sensitive in URLs or logs</strong> (keys, tokens, authorization codes).</li>
  <li><strong>Rate limiting</strong> on authentication endpoints to slow down credential stuffing.</li>
  <li><strong>Log and monitor</strong> token issuance, failures and revocations.</li>
  <li><strong>For AI agents</strong>: explicit user consent, tokens limited to the target MCP server, no token passthrough to downstream APIs.</li>
</ul>

<p>In short, API keys and Basic Auth remain useful for simple cases; OAuth 2.1 and OpenID Connect are the norm as soon as users and third parties are involved; mTLS, HMAC and DPoP harden machine-to-machine exchanges. MCP servers reinvent nothing: they apply these standards through a strict profile, designed so that AI agents can connect to thousands of services without prior registration.</p>

<h2 id="sources">Sources</h2>

<ul>
  <li><a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization">MCP — Authorization, 2026-07-28 revision</a></li>
  <li><a href="https://modelcontextprotocol.io/specification/latest/basic/authorization/client-registration">MCP — Client Registration</a></li>
  <li><a href="https://ssojet.com/blog/mcp-authorization-spec-2026-07-28-what-changed">MCP Authorization Spec 2026-07-28: what changed (SSOJet)</a></li>
  <li><a href="https://ssojet.com/blog/mcp-authentication-oauth-tokens-security-ai-connections.md">MCP Authentication: requirements of the 2026-07-28 revision (SSOJet)</a></li>
  <li><a href="https://den.dev/blog/mcp-november-authorization-spec/">What’s new in the MCP 2025-11-25 authorization spec (den.dev)</a></li>
  <li>RFCs cited: 6749 (OAuth 2.0), 6750 (Bearer), 7519 (JWT), 7617 (Basic), 7636 (PKCE), 7662 (introspection), 8414, 8628, 8693, 8705, 8707, 9207, 9421, 9449, 9728 — available at <a href="https://www.rfc-editor.org">rfc-editor.org</a></li>
</ul>]]></content><author><name>marwa SAHLI</name></author><category term="security" /><category term="authentication" /><category term="oauth" /><category term="api" /><category term="mcp" /><summary type="html"><![CDATA[Basic Auth, API keys, JWT, OAuth 2.1, OpenID Connect, SAML, mTLS, DPoP and the MCP server authorization profile: how to choose.]]></summary></entry><entry xml:lang="fr"><title type="html">Les protocoles d’authentification pour les applications : API REST, serveurs MCP et au-delà</title><link href="https://marwashli.github.io/articles-tech/2026/10/protocoles-authentification-api-mcp/" rel="alternate" type="text/html" title="Les protocoles d’authentification pour les applications : API REST, serveurs MCP et au-delà" /><published>2026-10-24T00:00:00+00:00</published><updated>2026-10-24T00:00:00+00:00</updated><id>https://marwashli.github.io/articles-tech/2026/10/protocoles-authentification-api-mcp</id><content type="html" xml:base="https://marwashli.github.io/articles-tech/2026/10/protocoles-authentification-api-mcp/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Il n’existe pas un protocole d’authentification idéal, mais un protocole adapté à chaque situation : qui appelle (un humain, un service, un agent IA), au nom de qui, et avec quel niveau de risque. Une clé d’API suffit pour un script interne ; une application grand public exige OAuth 2.0 et OpenID Connect ; un serveur MCP distant suit désormais un profil OAuth 2.1 précis.</p>

<p>Deux notions sont souvent confondues :</p>

<ul>
  <li><strong>Authentification (AuthN)</strong> : prouver <em>qui</em> l’on est (un utilisateur, une application, une machine).</li>
  <li><strong>Autorisation (AuthZ)</strong> : déterminer <em>ce que</em> l’on a le droit de faire (lire, écrire, administrer).</li>
</ul>

<p>OAuth 2.0 est un protocole d’<strong>autorisation</strong> déléguée ; OpenID Connect y ajoute la couche d’<strong>authentification</strong>. Cette distinction explique pourquoi les deux sont presque toujours utilisés ensemble.</p>

<p>Cet article passe en revue les méthodes, de la plus simple à la plus robuste, puis détaille le cas des serveurs MCP (Model Context Protocol), qui connectent les agents IA aux outils et données.</p>

<!--more-->

<h2 id="méthodes-simples--basic-auth-et-clés-dapi">Méthodes simples : Basic Auth et clés d’API</h2>

<p>Ces deux méthodes reposent sur un secret statique envoyé à chaque requête : faciles à mettre en œuvre, elles conviennent aux échanges internes ou de serveur à serveur, jamais à une application qui agit au nom d’utilisateurs tiers.</p>

<h3 id="http-basic-authentication">HTTP Basic Authentication</h3>

<p>Le client envoie l’identifiant et le mot de passe encodés en Base64 dans l’en-tête <code class="language-plaintext highlighter-rouge">Authorization</code> :</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">GET</span> <span class="nn">/api/commandes</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Authorization</span><span class="p">:</span> <span class="s">Basic YWxpY2U6czNjcjN0</span>
</code></pre></div></div>

<ul>
  <li><strong>Avantages</strong> : standard (RFC 7617), supporté partout, aucune infrastructure.</li>
  <li><strong>Limites</strong> : Base64 n’est pas un chiffrement, donc HTTPS est obligatoire ; le mot de passe transite à chaque appel ; pas d’expiration, pas de portée (scope), révocation = changement de mot de passe.</li>
  <li><strong>Usage raisonnable</strong> : outils internes, endpoints d’administration derrière un VPN, échange initial pour obtenir un jeton.</li>
</ul>

<h3 id="clés-dapi-api-keys">Clés d’API (API keys)</h3>

<p>Une chaîne aléatoire longue identifie l’application appelante, transmise dans un en-tête dédié :</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">GET</span> <span class="nn">/v1/meteo?ville=Lyon</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">X-API-Key</span><span class="p">:</span> <span class="s">sk_live_4f9a…</span>
</code></pre></div></div>

<ul>
  <li><strong>Avantages</strong> : simple à émettre, révoquer et faire tourner ; idéal pour la facturation et les quotas par client.</li>
  <li><strong>Limites</strong> : la clé identifie une <em>application</em>, pas un <em>utilisateur</em> ; c’est un secret « porteur » (quiconque la détient l’utilise) ; souvent sans expiration ni portée fine.</li>
  <li><strong>Bonnes pratiques</strong> : la passer en en-tête, jamais dans l’URL (elle finirait dans les journaux) ; stocker côté serveur uniquement un hachage ; préfixer les clés (<code class="language-plaintext highlighter-rouge">sk_live_</code>, <code class="language-plaintext highlighter-rouge">sk_test_</code>) pour faciliter la détection de fuites ; prévoir plusieurs clés actives pour permettre la rotation sans coupure.</li>
</ul>

<h2 id="jetons--sessions-bearer-et-jwt">Jetons : sessions, Bearer et JWT</h2>

<p>Au lieu d’envoyer un secret permanent, le client échange une preuve d’identité contre un <strong>jeton à durée de vie limitée</strong>. C’est la base de toutes les méthodes modernes.</p>

<h3 id="sessions-et-cookies">Sessions et cookies</h3>

<p>Après connexion, le serveur crée une session et renvoie un identifiant dans un cookie. C’est le modèle historique des applications web rendues côté serveur.</p>

<ul>
  <li><strong>Atouts</strong> : révocation immédiate (on supprime la session), cookies <code class="language-plaintext highlighter-rouge">HttpOnly</code>, <code class="language-plaintext highlighter-rouge">Secure</code>, <code class="language-plaintext highlighter-rouge">SameSite</code> qui protègent contre le vol par JavaScript.</li>
  <li><strong>Limites</strong> : état côté serveur (stockage partagé nécessaire entre instances), protection CSRF requise, peu adapté aux clients mobiles ou aux API consommées par des tiers.</li>
</ul>

<h3 id="jetons-bearer">Jetons Bearer</h3>

<p>Un jeton « porteur » (RFC 6750) est envoyé dans l’en-tête <code class="language-plaintext highlighter-rouge">Authorization: Bearer &lt;jeton&gt;</code>. Celui qui le détient l’utilise : il doit donc être court, transmis en HTTPS et jamais journalisé. Le jeton peut être <strong>opaque</strong> (une chaîne que le serveur vérifie auprès de l’émetteur, par introspection — RFC 7662) ou <strong>auto-porteur</strong> (un JWT).</p>

<h3 id="json-web-token-jwt">JSON Web Token (JWT)</h3>

<p>Un JWT (RFC 7519) est composé de trois parties encodées en Base64URL : <code class="language-plaintext highlighter-rouge">en-tête.charge-utile.signature</code>. La charge utile contient des <em>claims</em> :</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://auth.exemple.fr"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"user_8241"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"aud"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.exemple.fr"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1793011200</span><span class="p">,</span><span class="w">
  </span><span class="nl">"scope"</span><span class="p">:</span><span class="w"> </span><span class="s2">"commandes:lire"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>L’API vérifie la signature avec la clé publique de l’émetteur (publiée en JWKS), puis contrôle <code class="language-plaintext highlighter-rouge">iss</code>, <code class="language-plaintext highlighter-rouge">aud</code> et <code class="language-plaintext highlighter-rouge">exp</code>, sans appel réseau.</p>

<ul>
  <li><strong>Atouts</strong> : vérification locale et rapide, idéale pour les microservices.</li>
  <li><strong>Pièges</strong> : un JWT ne se révoque pas avant son expiration (d’où des durées courtes, 5 à 15 minutes, et des refresh tokens) ; refuser l’algorithme <code class="language-plaintext highlighter-rouge">none</code> et fixer la liste des algorithmes acceptés (RS256, ES256…) ; la charge utile est lisible par tous, elle n’est pas chiffrée ; ne jamais oublier de vérifier l’audience <code class="language-plaintext highlighter-rouge">aud</code>.</li>
</ul>

<p>Le JWT est un <strong>format</strong> de jeton, pas un protocole : la question de <em>comment</em> obtenir ce jeton est réglée par OAuth 2.0.</p>

<h2 id="oauth-20-et-oauth-21--lautorisation-déléguée">OAuth 2.0 et OAuth 2.1 : l’autorisation déléguée</h2>

<p>OAuth 2.0 (RFC 6749) permet à une application d’accéder à une API <strong>au nom d’un utilisateur, sans jamais connaître son mot de passe</strong>. C’est le standard de fait des API publiques (Google, GitHub, Microsoft, Salesforce…). OAuth 2.1, en cours de finalisation à l’IETF, consolide une décennie de bonnes pratiques : PKCE obligatoire, suppression des flux implicite et « mot de passe ».</p>

<h3 id="les-quatre-rôles">Les quatre rôles</h3>

<table>
  <thead>
    <tr>
      <th>Rôle</th>
      <th>Description</th>
      <th>Exemple</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Propriétaire de la ressource</td>
      <td>L’utilisateur qui consent</td>
      <td>Alice</td>
    </tr>
    <tr>
      <td>Client</td>
      <td>L’application qui demande l’accès</td>
      <td>Une app mobile, un client MCP</td>
    </tr>
    <tr>
      <td>Serveur d’autorisation</td>
      <td>Authentifie et délivre les jetons</td>
      <td>Keycloak, Auth0, Entra ID, Okta</td>
    </tr>
    <tr>
      <td>Serveur de ressources</td>
      <td>L’API protégée qui valide les jetons</td>
      <td>L’API REST, le serveur MCP</td>
    </tr>
  </tbody>
</table>

<h3 id="le-flux-recommandé--authorization-code--pkce">Le flux recommandé : Authorization Code + PKCE</h3>

<ol>
  <li>Le client génère un secret aléatoire (<code class="language-plaintext highlighter-rouge">code_verifier</code>) et son empreinte SHA-256 (<code class="language-plaintext highlighter-rouge">code_challenge</code>).</li>
  <li>Il redirige l’utilisateur vers le serveur d’autorisation avec <code class="language-plaintext highlighter-rouge">client_id</code>, <code class="language-plaintext highlighter-rouge">redirect_uri</code>, <code class="language-plaintext highlighter-rouge">scope</code>, <code class="language-plaintext highlighter-rouge">state</code> et <code class="language-plaintext highlighter-rouge">code_challenge</code>.</li>
  <li>L’utilisateur s’authentifie et donne son consentement.</li>
  <li>Le serveur d’autorisation redirige vers le client avec un <strong>code d’autorisation</strong> à usage unique.</li>
  <li>Le client échange ce code, accompagné du <code class="language-plaintext highlighter-rouge">code_verifier</code>, contre un <strong>access token</strong> (et souvent un <strong>refresh token</strong>).</li>
  <li>Le client appelle l’API avec <code class="language-plaintext highlighter-rouge">Authorization: Bearer &lt;access_token&gt;</code>.</li>
</ol>

<p>PKCE (RFC 7636) empêche un attaquant qui intercepterait le code de l’échanger, puisqu’il ne connaît pas le <code class="language-plaintext highlighter-rouge">code_verifier</code>. Il est désormais exigé pour tous les clients, y compris les applications serveur.</p>

<h3 id="les-autres-flux-grant-types">Les autres flux (grant types)</h3>

<table>
  <thead>
    <tr>
      <th>Flux</th>
      <th>Cas d’usage</th>
      <th>Utilisateur présent ?</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Authorization Code + PKCE</td>
      <td>Applications web, mobiles, SPA, clients MCP</td>
      <td>Oui</td>
    </tr>
    <tr>
      <td>Client Credentials</td>
      <td>Service à service, tâches planifiées, back-ends</td>
      <td>Non</td>
    </tr>
    <tr>
      <td>Device Authorization (RFC 8628)</td>
      <td>TV, CLI, objets connectés sans navigateur</td>
      <td>Oui, sur un autre appareil</td>
    </tr>
    <tr>
      <td>Refresh Token</td>
      <td>Renouveler un access token expiré sans nouvelle connexion</td>
      <td>Non</td>
    </tr>
    <tr>
      <td>Token Exchange (RFC 8693)</td>
      <td>Un service échange un jeton reçu contre un jeton pour une API en aval</td>
      <td>Non</td>
    </tr>
    <tr>
      <td><del>Implicit</del>, <del>Password</del></td>
      <td>Supprimés dans OAuth 2.1</td>
      <td>—</td>
    </tr>
  </tbody>
</table>

<h3 id="scopes-audiences-et-refresh-tokens">Scopes, audiences et refresh tokens</h3>

<ul>
  <li>Les <strong>scopes</strong> (<code class="language-plaintext highlighter-rouge">commandes:lire</code>, <code class="language-plaintext highlighter-rouge">profil</code>) limitent ce que le jeton autorise ; demander le strict minimum.</li>
  <li>Le paramètre <strong>resource</strong> (RFC 8707) lie le jeton à une API précise : un jeton émis pour l’API A doit être refusé par l’API B.</li>
  <li>Les <strong>refresh tokens</strong> doivent être soumis à rotation (chaque usage en délivre un nouveau et invalide l’ancien) pour les clients publics.</li>
</ul>

<h2 id="openid-connect-et-saml--identité-fédérée-et-sso">OpenID Connect et SAML : identité fédérée et SSO</h2>

<p>Quand l’application doit savoir <strong>qui est l’utilisateur</strong> (connexion unique, « Se connecter avec Google »), on ajoute une couche d’identité au-dessus d’OAuth : OpenID Connect pour les applications modernes, SAML pour l’existant d’entreprise.</p>

<h3 id="openid-connect-oidc">OpenID Connect (OIDC)</h3>

<p>OIDC réutilise le flux Authorization Code d’OAuth 2.0 en ajoutant le scope <code class="language-plaintext highlighter-rouge">openid</code>. Le serveur d’autorisation (appelé <em>OpenID Provider</em>) renvoie, en plus de l’access token, un <strong>ID token</strong> : un JWT signé qui décrit l’utilisateur (<code class="language-plaintext highlighter-rouge">sub</code>, <code class="language-plaintext highlighter-rouge">email</code>, <code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">auth_time</code>…).</p>

<ul>
  <li><strong>Découverte automatique</strong> : <code class="language-plaintext highlighter-rouge">/.well-known/openid-configuration</code> publie les endpoints, les algorithmes et la clé publique (JWKS).</li>
  <li><strong>Endpoint UserInfo</strong> pour obtenir des attributs complémentaires.</li>
  <li><strong>Règle d’or</strong> : l’ID token sert au <em>client</em> pour connaître l’utilisateur ; l’access token sert à appeler l’<em>API</em>. Ne jamais envoyer un ID token à une API comme jeton d’accès.</li>
</ul>

<h3 id="saml-20">SAML 2.0</h3>

<p>SAML (Security Assertion Markup Language) échange des <strong>assertions XML signées</strong> entre un fournisseur d’identité (IdP : ADFS, Okta, Entra ID) et un fournisseur de services (SP), via des redirections du navigateur.</p>

<ul>
  <li><strong>Atouts</strong> : très répandu dans les grands comptes et les SaaS B2B, mature.</li>
  <li><strong>Limites</strong> : verbeux, complexe à implémenter correctement (attaques par contournement de signature XML), pensé pour le navigateur et non pour les API ou les applications mobiles.</li>
  <li><strong>Tendance</strong> : les nouveaux projets privilégient OIDC ; SAML reste nécessaire pour s’intégrer aux annuaires d’entreprise existants, souvent via un courtier d’identité (Keycloak, Auth0) qui traduit SAML en OIDC.</li>
</ul>

<h3 id="et-les-utilisateurs-eux-mêmes-">Et les utilisateurs eux-mêmes ?</h3>

<p>OIDC et SAML délèguent la vérification de l’identité au fournisseur d’identité, qui peut alors utiliser l’authentification multifacteur (MFA) ou les <strong>passkeys</strong> (WebAuthn/FIDO2), aujourd’hui la méthode la plus résistante à l’hameçonnage.</p>

<h2 id="mtls-signatures-hmac-et-dpop--lauthentification-forte-entre-machines">mTLS, signatures HMAC et DPoP : l’authentification forte entre machines</h2>

<p>Ces mécanismes répondent à la principale faiblesse des jetons Bearer — un jeton volé est utilisable par n’importe qui — en exigeant une <strong>preuve de possession</strong> d’une clé que seul le client légitime détient.</p>

<h3 id="tls-mutuel-mtls">TLS mutuel (mTLS)</h3>

<p>Dans un échange TLS classique, seul le serveur présente un certificat. En mTLS, le <strong>client présente aussi le sien</strong>, signé par une autorité de certification reconnue par le serveur.</p>

<ul>
  <li><strong>Usages</strong> : communications entre microservices (souvent automatisées par un <em>service mesh</em> comme Istio ou Linkerd), API bancaires et Open Banking, architectures zero trust.</li>
  <li><strong>Avec OAuth</strong> : la RFC 8705 lie un access token au certificat du client (<em>certificate-bound token</em>) ; un jeton volé est inutilisable sans la clé privée.</li>
  <li><strong>Contraintes</strong> : gestion du cycle de vie des certificats (émission, rotation, révocation), plus délicate à déployer côté navigateur ou mobile.</li>
</ul>

<h3 id="signature-de-requêtes-hmac">Signature de requêtes (HMAC)</h3>

<p>Le client et le serveur partagent une clé secrète. Le client calcule une signature (HMAC-SHA256) sur des éléments de la requête — méthode, chemin, horodatage, empreinte du corps — et l’envoie en en-tête. Le serveur recalcule et compare.</p>

<ul>
  <li><strong>Exemples</strong> : AWS Signature Version 4, vérification des webhooks (Stripe, GitHub, Slack).</li>
  <li><strong>Atouts</strong> : le secret ne transite jamais ; l’intégrité du message est garantie ; l’horodatage bloque le rejeu.</li>
  <li><strong>Standardisation</strong> : la RFC 9421 (HTTP Message Signatures) normalise désormais cette approche, y compris avec des clés asymétriques.</li>
</ul>

<h3 id="dpop-demonstrating-proof-of-possession">DPoP (Demonstrating Proof of Possession)</h3>

<p>DPoP (RFC 9449) apporte la preuve de possession au niveau applicatif, sans infrastructure de certificats. Le client génère une paire de clés et joint à chaque requête un petit JWT signé (en-tête <code class="language-plaintext highlighter-rouge">DPoP</code>) qui couvre la méthode, l’URL et l’horodatage. Le serveur d’autorisation lie l’access token à cette clé publique.</p>

<p>DPoP convient bien aux SPA et applications mobiles, là où mTLS est difficile à déployer.</p>

<h2 id="le-cas-des-serveurs-mcp">Le cas des serveurs MCP</h2>

<p>Un serveur MCP distant est un <strong>serveur de ressources OAuth 2.1</strong> : selon la révision 2026-07-28 de la spécification, le client découvre seul le serveur d’autorisation, s’identifie par une URL (Client ID Metadata Document) et obtient un jeton valable pour ce seul serveur. Un serveur MCP local (stdio), lui, lit simplement ses identifiants dans l’environnement.</p>

<h3 id="rappel--quest-ce-que-mcp-">Rappel : qu’est-ce que MCP ?</h3>

<p>Le Model Context Protocol est un protocole ouvert qui connecte les applications d’IA (Claude, ChatGPT, VS Code, Cursor…) à des outils et des sources de données via des « serveurs MCP ». La particularité du point de vue de la sécurité : un agent peut se connecter à un serveur qu’il n’a jamais vu, sans relation préalable entre l’éditeur du client et celui du serveur. Le profil d’authentification est conçu pour ce scénario.</p>

<h3 id="deux-transports-deux-approches">Deux transports, deux approches</h3>

<p>La spécification rend l’autorisation <strong>optionnelle</strong>, mais fixe la marche à suivre selon le transport.</p>

<table>
  <thead>
    <tr>
      <th>Transport</th>
      <th>Où tourne le serveur</th>
      <th>Authentification</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>stdio</td>
      <td>Processus local lancé par l’application hôte</td>
      <td>Ne pas suivre la spécification OAuth ; identifiants lus dans l’environnement (variables, fichiers de configuration)</td>
    </tr>
    <tr>
      <td>Streamable HTTP</td>
      <td>Serveur distant accessible par URL</td>
      <td>Profil OAuth 2.1 de la spécification MCP</td>
    </tr>
  </tbody>
</table>

<p>L’ancien transport HTTP+SSE (révision 2024-11-05) est déprécié.</p>

<h3 id="le-flux-dautorisation-pas-à-pas">Le flux d’autorisation pas à pas</h3>

<p><img src="/articles-tech/assets/images/flux-autorisation-mcp.svg" alt="Flux d'autorisation MCP : 10 échanges entre le client MCP, le serveur MCP et le serveur d'autorisation" /></p>

<p>Le client n’a besoin que de l’URL du serveur MCP : un premier refus <code class="language-plaintext highlighter-rouge">401</code> lui indique où trouver les métadonnées, puis il enchaîne un flux Authorization Code + PKCE classique et rejoue sa requête avec le jeton.</p>

<p>La réponse <code class="language-plaintext highlighter-rouge">401</code> du serveur ressemble à ceci :</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">401</span> <span class="ne">Unauthorized</span>
<span class="na">WWW-Authenticate</span><span class="p">:</span> <span class="s">Bearer resource_metadata="https://mcp.exemple.fr/.well-known/oauth-protected-resource"</span>
</code></pre></div></div>

<h3 id="les-briques-standards-mobilisées">Les briques standards mobilisées</h3>

<table>
  <thead>
    <tr>
      <th>Brique</th>
      <th>Référence</th>
      <th>Rôle dans MCP</th>
      <th>Exigence</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Protected Resource Metadata</td>
      <td>RFC 9728</td>
      <td>Le serveur MCP indique quel serveur d’autorisation utiliser</td>
      <td>Obligatoire (serveur et client)</td>
    </tr>
    <tr>
      <td>Métadonnées du serveur d’autorisation</td>
      <td>RFC 8414 et OpenID Connect Discovery</td>
      <td>Découverte des endpoints</td>
      <td>Le client doit gérer les deux</td>
    </tr>
    <tr>
      <td>PKCE</td>
      <td>RFC 7636</td>
      <td>Protège l’échange du code</td>
      <td>Obligatoire</td>
    </tr>
    <tr>
      <td>Resource Indicators</td>
      <td>RFC 8707</td>
      <td>Lie le jeton au serveur MCP visé</td>
      <td>Obligatoire</td>
    </tr>
    <tr>
      <td>Client ID Metadata Documents (CIMD)</td>
      <td>Brouillon IETF</td>
      <td>Le <code class="language-plaintext highlighter-rouge">client_id</code> est une URL HTTPS pointant vers un JSON décrivant le client</td>
      <td>Recommandé</td>
    </tr>
    <tr>
      <td>Dynamic Client Registration</td>
      <td>RFC 7591</td>
      <td>Enregistrement automatique du client</td>
      <td>Facultatif, <strong>déprécié</strong> en 2026-07-28</td>
    </tr>
    <tr>
      <td>Validation de l’émetteur</td>
      <td>RFC 9207</td>
      <td>Le client vérifie <code class="language-plaintext highlighter-rouge">iss</code> avant d’échanger le code</td>
      <td>Obligatoire depuis 2026-07-28</td>
    </tr>
  </tbody>
</table>

<h3 id="enregistrement-du-client--lordre-de-priorité">Enregistrement du client : l’ordre de priorité</h3>

<p>Comment un client MCP obtient-il un <code class="language-plaintext highlighter-rouge">client_id</code> auprès d’un serveur d’autorisation qu’il ne connaît pas ? La spécification recommande cet ordre :</p>

<ol>
  <li>Utiliser des identifiants <strong>pré-enregistrés</strong> pour ce serveur, s’il en dispose.</li>
  <li>Utiliser un <strong>Client ID Metadata Document</strong> si le serveur d’autorisation annonce <code class="language-plaintext highlighter-rouge">client_id_metadata_document_supported</code>.</li>
  <li>Se rabattre sur le <strong>Dynamic Client Registration</strong> si le serveur expose un <code class="language-plaintext highlighter-rouge">registration_endpoint</code>.</li>
  <li>À défaut, demander à l’utilisateur de saisir lui-même les informations du client.</li>
</ol>

<p>Le CIMD a l’avantage d’être portable : l’identifiant est une URL hébergée par l’éditeur du client, résolue à la demande par n’importe quel serveur d’autorisation, sans base d’enregistrements à maintenir.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"client_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://app.exemple.fr/oauth/client-metadata.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"client_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Mon client MCP"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"redirect_uris"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"http://127.0.0.1:3000/callback"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"grant_types"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"authorization_code"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"token_endpoint_auth_method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="points-de-sécurité-propres-à-mcp">Points de sécurité propres à MCP</h3>

<ul>
  <li><strong>Pas de transmission de jeton</strong> : le serveur MCP ne doit jamais relayer vers une API en aval le jeton reçu du client ; il obtient son propre jeton (par exemple via Token Exchange).</li>
  <li><strong>Validation de l’audience</strong> : le serveur MCP n’accepte que des jetons émis spécifiquement pour lui.</li>
  <li><strong>Identifiants liés à l’émetteur</strong> : un client doit associer ses identifiants au serveur d’autorisation qui les a émis et ne jamais les réutiliser auprès d’un autre.</li>
  <li><strong>Consentement incrémental</strong> : depuis la révision 2025-11-25, un client peut demander des scopes supplémentaires au moment où un outil en a besoin, plutôt que tout demander d’emblée.</li>
  <li><strong>Serveurs stdio</strong> : les secrets dans l’environnement doivent avoir une portée minimale, car le processus local s’exécute avec les droits de l’utilisateur.</li>
</ul>

<h2 id="comparatif-et-guide-de-choix">Comparatif et guide de choix</h2>

<p>Le bon choix dépend d’abord de la présence ou non d’un utilisateur humain, puis du niveau de risque.</p>

<table>
  <thead>
    <tr>
      <th>Méthode</th>
      <th>Identifie</th>
      <th>Expiration / révocation</th>
      <th>Complexité</th>
      <th>Cas d’usage typique</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Basic Auth</td>
      <td>Un utilisateur ou un compte technique</td>
      <td>Aucune / changement de mot de passe</td>
      <td>Très faible</td>
      <td>Outils internes, administration</td>
    </tr>
    <tr>
      <td>Clé d’API</td>
      <td>Une application</td>
      <td>Manuelle / révocation simple</td>
      <td>Faible</td>
      <td>API publiques à quotas, intégrations serveur</td>
    </tr>
    <tr>
      <td>Session + cookie</td>
      <td>Un utilisateur</td>
      <td>Configurable / immédiate</td>
      <td>Faible</td>
      <td>Applications web classiques</td>
    </tr>
    <tr>
      <td>JWT</td>
      <td>Utilisateur ou application</td>
      <td>Courte / difficile avant expiration</td>
      <td>Moyenne</td>
      <td>Microservices, API sans état</td>
    </tr>
    <tr>
      <td>OAuth 2.0 / 2.1</td>
      <td>Application agissant pour un utilisateur</td>
      <td>Courte + refresh / via le serveur d’autorisation</td>
      <td>Moyenne à élevée</td>
      <td>API ouvertes à des tiers, clients MCP distants</td>
    </tr>
    <tr>
      <td>OpenID Connect</td>
      <td>Un utilisateur (identité)</td>
      <td>Courte</td>
      <td>Moyenne</td>
      <td>SSO, « Se connecter avec… »</td>
    </tr>
    <tr>
      <td>SAML 2.0</td>
      <td>Un utilisateur (identité)</td>
      <td>Courte</td>
      <td>Élevée</td>
      <td>SSO d’entreprise historique</td>
    </tr>
    <tr>
      <td>mTLS</td>
      <td>Une machine</td>
      <td>Durée du certificat / CRL, OCSP</td>
      <td>Élevée</td>
      <td>Zero trust, service mesh, finance</td>
    </tr>
    <tr>
      <td>Signature HMAC</td>
      <td>Une application + intégrité du message</td>
      <td>Par requête (horodatage)</td>
      <td>Moyenne</td>
      <td>Webhooks, API cloud</td>
    </tr>
    <tr>
      <td>DPoP</td>
      <td>Application, jeton lié à une clé</td>
      <td>Courte</td>
      <td>Moyenne</td>
      <td>SPA et mobiles à haut niveau de sécurité</td>
    </tr>
  </tbody>
</table>

<p><strong>Raccourci de décision :</strong></p>

<ul>
  <li>Un utilisateur se connecte à <em>votre</em> application → <strong>OIDC</strong> (avec passkeys ou MFA chez le fournisseur d’identité).</li>
  <li>Une application tierce accède aux données d’un utilisateur → <strong>OAuth 2.1 Authorization Code + PKCE</strong>.</li>
  <li>Un service appelle un autre service → <strong>Client Credentials</strong>, renforcé par <strong>mTLS</strong> ou <strong>DPoP</strong> si les données sont sensibles.</li>
  <li>Vous recevez des webhooks → <strong>signature HMAC</strong>.</li>
  <li>Un script interne ou un prototype → <strong>clé d’API</strong>, avec rotation.</li>
  <li>Un serveur MCP distant → <strong>le profil OAuth 2.1 de la spécification MCP</strong> (section précédente) ; un serveur MCP local → <strong>variables d’environnement</strong>.</li>
</ul>

<h2 id="bonnes-pratiques-et-conclusion">Bonnes pratiques et conclusion</h2>

<p>Quel que soit le protocole retenu, la plupart des incidents viennent de détails d’implémentation plutôt que du protocole lui-même.</p>

<ul>
  <li><strong>HTTPS partout</strong>, sans exception, y compris en interne.</li>
  <li><strong>Ne pas réinventer</strong> : s’appuyer sur un serveur d’autorisation éprouvé (Keycloak, Auth0, Entra ID, Okta, Cognito…) et des bibliothèques maintenues.</li>
  <li><strong>Jetons courts</strong> (minutes) et refresh tokens soumis à rotation.</li>
  <li><strong>Valider chaque jeton</strong> : signature, émetteur <code class="language-plaintext highlighter-rouge">iss</code>, audience <code class="language-plaintext highlighter-rouge">aud</code>, expiration <code class="language-plaintext highlighter-rouge">exp</code>, scopes.</li>
  <li><strong>Moindre privilège</strong> : scopes minimaux, un jeton par API cible.</li>
  <li><strong>Secrets hors du code</strong> : coffre-fort (Vault, AWS Secrets Manager…) ou variables d’environnement, jamais dans Git.</li>
  <li><strong>Rien de sensible dans les URL ni dans les journaux</strong> (clés, jetons, codes d’autorisation).</li>
  <li><strong>Limitation de débit</strong> sur les endpoints d’authentification pour freiner le bourrage d’identifiants.</li>
  <li><strong>Journaliser et surveiller</strong> les émissions de jetons, les échecs et les révocations.</li>
  <li><strong>Pour les agents IA</strong> : consentement explicite de l’utilisateur, jetons limités au serveur MCP visé, pas de transmission de jeton vers les API en aval.</li>
</ul>

<p>En résumé, les clés d’API et Basic Auth restent utiles pour les cas simples ; OAuth 2.1 et OpenID Connect sont la norme dès qu’un utilisateur et des tiers entrent en jeu ; mTLS, HMAC et DPoP durcissent les échanges entre machines. Les serveurs MCP ne réinventent rien : ils appliquent ces standards avec un profil strict, conçu pour que des agents IA puissent se connecter à des milliers de services sans enregistrement préalable.</p>

<h2 id="sources">Sources</h2>

<ul>
  <li><a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization">MCP — Authorization, révision 2026-07-28</a></li>
  <li><a href="https://modelcontextprotocol.io/specification/latest/basic/authorization/client-registration">MCP — Client Registration</a></li>
  <li><a href="https://ssojet.com/blog/mcp-authorization-spec-2026-07-28-what-changed">MCP Authorization Spec 2026-07-28 : ce qui change (SSOJet)</a></li>
  <li><a href="https://ssojet.com/blog/mcp-authentication-oauth-tokens-security-ai-connections.md">MCP Authentication : exigences de la révision 2026-07-28 (SSOJet)</a></li>
  <li><a href="https://den.dev/blog/mcp-november-authorization-spec/">Nouveautés de la spécification d’autorisation MCP 2025-11-25 (den.dev)</a></li>
  <li>RFC citées : 6749 (OAuth 2.0), 6750 (Bearer), 7519 (JWT), 7617 (Basic), 7636 (PKCE), 7662 (introspection), 8414, 8628, 8693, 8705, 8707, 9207, 9421, 9449, 9728 — consultables sur <a href="https://www.rfc-editor.org">rfc-editor.org</a></li>
</ul>]]></content><author><name>marwa SAHLI</name></author><category term="sécurité" /><category term="authentification" /><category term="oauth" /><category term="api" /><category term="mcp" /><summary type="html"><![CDATA[Basic Auth, clés d'API, JWT, OAuth 2.1, OpenID Connect, SAML, mTLS, DPoP et le profil d'autorisation des serveurs MCP : comment choisir.]]></summary></entry></feed>