<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vlastimil Elias</title>
    <description>The latest articles on DEV Community by Vlastimil Elias (@velias).</description>
    <link>https://dev.to/velias</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3933314%2F2e21561c-bfcf-4ed5-a823-8b5c60c1e630.png</url>
      <title>DEV Community: Vlastimil Elias</title>
      <link>https://dev.to/velias</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/velias"/>
    <language>en</language>
    <item>
      <title>Domain Knowledge for AI agents: static vs just-in-time teaching</title>
      <dc:creator>Vlastimil Elias</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:18:41 +0000</pubDate>
      <link>https://dev.to/velias/domain-knowledge-for-ai-agents-static-vs-just-in-time-teaching-2lgp</link>
      <guid>https://dev.to/velias/domain-knowledge-for-ai-agents-static-vs-just-in-time-teaching-2lgp</guid>
      <description>&lt;p&gt;MCP connects agents to tools and data. That is only half of Agent Experience. The other half is agent &lt;strong&gt;teaching&lt;/strong&gt;: how the agent should interpret tool results, what to ask user next, and when &lt;em&gt;not&lt;/em&gt; to invent an answer from general knowledge.&lt;/p&gt;

&lt;p&gt;What you are teaching is &lt;strong&gt;Domain Knowledge&lt;/strong&gt; — the curated rules raw APIs and databases do not carry safely on their own (funnels, status meanings, what must not be mixed up). Without it, agents misread codes and invent incorrect next steps. Getting that knowledge into the agent at the right moment is a classic &lt;strong&gt;Knowledge Engineering&lt;/strong&gt; problem: capture how experts reason about the domain, then deliver it through channels the runtime actually honors — not only as a wiki page nobody loads.&lt;/p&gt;

&lt;p&gt;Early LLMs were mostly &lt;strong&gt;knowledge recall&lt;/strong&gt; machines — answer from what was stored in the weights. Modern models are intentionally trained and productized toward &lt;strong&gt;agentic&lt;/strong&gt; behavior: plan tool calls, read each result, decide the next hop, and ask the user when something is missing (the tool-use / agentic loop). Domain Knowledge teaching is what makes that loop trustworthy in &lt;em&gt;your&lt;/em&gt; domain — so the model composes the right next steps instead of inventing them from general memory.&lt;/p&gt;

&lt;p&gt;This post is for two audiences: people building &lt;strong&gt;MCP servers&lt;/strong&gt; who want agents to use their tools safely, and people wiring &lt;strong&gt;multi-agent stacks&lt;/strong&gt; (or Agent Skills) on top of those servers who need to know what belongs in a system prompt versus what must ride every tool result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The delivery split
&lt;/h2&gt;

&lt;p&gt;How Domain Knowledge is &lt;em&gt;delivered&lt;/em&gt; matters as much as how it is authored. Illustrating with a simple &lt;strong&gt;e-commerce&lt;/strong&gt; stack (customers → orders → shipping / help articles); the same split applies to any domain MCP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static teaching&lt;/strong&gt; — stable Domain Knowledge (funnel, core enums, policy). Loaded once per session or agent role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic / just-in-time teaching&lt;/strong&gt; — observation-local Domain Knowledge: scope and next steps for &lt;em&gt;this&lt;/em&gt; result. Travels with the tool response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dynamic teaching has two practical implementations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structured fields&lt;/strong&gt; — machine-actionable truth (&lt;code&gt;status&lt;/code&gt;, next-step arguments, ids, closed codes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual hints&lt;/strong&gt; — short, data-driven English (&lt;code&gt;agentHints&lt;/code&gt;) assembled only when this payload needs it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not treat Skills or system prompts as a substitute for the response-local half.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn60ucogpj2dsfvgy2854.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn60ucogpj2dsfvgy2854.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Domain Knowledge: MCP first
&lt;/h2&gt;

&lt;h3&gt;
  
  
  On the MCP server (portable contract)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP server instructions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Short shared core: resolve funnel, core grades/enums, “act from structured fields; follow hints when present”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool descriptions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dense per-tool how-to — size-capped and always-on in context, so summary, not an encyclopedia&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every consumer of the server gets &lt;em&gt;tools&lt;/em&gt; when connected. Server instructions are weaker than that sounds. The MCP spec treats them as optional (clients &lt;strong&gt;MAY&lt;/strong&gt; surface them), so support varies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Codex&lt;/strong&gt; — documents support, but &lt;a href="https://github.com/openai/codex/issues/29097" rel="noopener noreferrer"&gt;does not reliably expose them&lt;/a&gt; as server-wide agent guidance (often demoted to namespace metadata).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Desktop / Claude.ai&lt;/strong&gt; — &lt;a href="https://github.com/anthropics/claude-ai-mcp/issues/131" rel="noopener noreferrer"&gt;silently drop&lt;/a&gt; server instructions, &lt;strong&gt;Claude Code&lt;/strong&gt; honors them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static text that &lt;em&gt;does&lt;/em&gt; land still has a budget. Hosts such as Claude truncate server and tool descriptions around &lt;strong&gt;~2 KB each&lt;/strong&gt;. Whatever you ship there is loaded into the model context for the session — so an encyclopedia burns tokens on every turn, with &lt;strong&gt;inference speed and cost&lt;/strong&gt; consequences even when the agent never needs that paragraph.&lt;/p&gt;

&lt;p&gt;Put a short shared core in server instructions &lt;em&gt;and&lt;/em&gt; mirror essentials in tool descriptions. Treat instructions alone as insufficient. Prefer case-local structured fields and hints for the long tail: they ride every call without bloating always-on context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Off the MCP server (useful, not sufficient)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Agent Skills&lt;/strong&gt; (&lt;code&gt;SKILL.md&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Task-level progressive disclosure: host loads metadata - agent pulls the body when the &lt;em&gt;task&lt;/em&gt; looks relevant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-agent system prompts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Role packaging: “you are the orders / fulfillment specialist” — stable playbook for that agent only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The usual vendor split is right as far as it goes: &lt;strong&gt;MCP connects&lt;/strong&gt;; &lt;strong&gt;Skills teach procedures&lt;/strong&gt;. In a multi-agent stack the same idea shows up as orchestrator and specialist system prompts. Both are good homes for the &lt;em&gt;static&lt;/em&gt; guide of a given agent. Both are weak for “explain &lt;em&gt;this&lt;/em&gt; rare code” or “follow &lt;em&gt;these&lt;/em&gt; related ids” — they load for a task or role, not for this tool result.&lt;/p&gt;

&lt;p&gt;Two delivery traps today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skills ≠ MCP install.&lt;/strong&gt; Enabling a MCP server does not install a companion Skill. Until Skills ship with the MCP server (in progress), Skill-only teaching is optional and unreliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System prompts are private.&lt;/strong&gt; A specialist’s prompt is invisible to every other client of the same MCP. If correct interpretation depends on that prompt alone, the portable contract is broken, knowledge is fragmented and may be inconsistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the static slice is not enough
&lt;/h2&gt;

&lt;p&gt;Static Domain Knowledge is usually authored as natural language — server instructions, tool descriptions, Skills, system prompts. A happy-path guide reads like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Resolve the customer first, then list orders for that customer.
If resolve returns status need_order, ask which order they mean —
show recentOrders; do not invent an order id.
Order status is the source of truth for fulfillment; help articles
explain policy, they do not override status.
Prefer structured fields on the tool result; follow agentHints when present.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works for the stable funnel. Reality adds storefront-specific scope, optional filters not yet pinned, rare answer-shaping flags, and bridges to sibling domains (shipping, returns, payments) that only matter when related ids are present in the data. Encoding every case in always-on instructions produces an encyclopedia that burns context every turn, still misses the rare code that is not on &lt;em&gt;this&lt;/em&gt; call, and fails for callers that never saw your Skill or system prompt. Those cases need teaching on the tool result itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic Domain Knowledge on the tool result
&lt;/h2&gt;

&lt;p&gt;Industry names for this half vary — progressive disclosure, just-in-time instructions, tool-response steering, next-action hints. In Knowledge Engineering terms: put the rule next to the case. It can be implemented as two layers on the tool result:&lt;/p&gt;

&lt;p&gt;Walk the same short conversation through both layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; “What’s the status of my order?”&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured fields (primary)
&lt;/h3&gt;

&lt;p&gt;Machine-actionable truth the agent can act on even if it ignores English: workflow status, ready-made next-call args, closed codes, thin stubs for a follow-on tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool:&lt;/strong&gt; &lt;code&gt;resolve_customer&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"need_order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"askNext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recentOrders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ORD-1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-1038"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-1021"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listOrdersArgs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust_9f3a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next hop is in the payload: ask which order, or call &lt;code&gt;list_orders&lt;/code&gt; with &lt;code&gt;customerId&lt;/code&gt; already filled — no extra always-on instructions required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; “The latest one — ORD-1042.”&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Tool:&lt;/strong&gt; &lt;code&gt;get_order&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shipmentStub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"trackingId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1Z999"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"carrier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ups"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"relatedDocs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"preferredId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kb-returns-eu"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stubs (&lt;code&gt;shipmentStub&lt;/code&gt;, &lt;code&gt;relatedDocs&lt;/code&gt;) are portable ids for later joins — still on this orders MCP. Following them onto sibling servers is the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contextual hints (reinforcement)
&lt;/h3&gt;

&lt;p&gt;Short, &lt;strong&gt;data-driven&lt;/strong&gt; English assembled only when this payload needs it — not a glossary pasted on every call. Replay the same two turns with &lt;code&gt;agentHints&lt;/code&gt; added — still on the orders MCP.&lt;/p&gt;

&lt;p&gt;Opening turn — &lt;code&gt;resolve_customer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"need_order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"askNext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recentOrders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ORD-1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-1038"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-1021"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listOrdersArgs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust_9f3a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agentHints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Ask which order they mean. Show recentOrders. Do not invent an order id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"This customer resolved on the EU storefront only — do not merge US marketplace orders into the same answer."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the user picks ORD-1042 — same &lt;code&gt;get_order&lt;/code&gt;, with a rare-code gloss when the user asks for an ETA:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; “When will ORD-1042 arrive?”&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Tool:&lt;/strong&gt; &lt;code&gt;get_order&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"etaUnknown"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shipmentStub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"trackingId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1Z999"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"carrier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ups"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"relatedDocs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"preferredId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kb-returns-eu"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agentHints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"etaUnknown: carrier accepted the parcel but has not published a delivery window — say in transit, do not invent a date."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Core statuses (&lt;code&gt;placed&lt;/code&gt; / &lt;code&gt;shipped&lt;/code&gt; / &lt;code&gt;delivered&lt;/code&gt; / …) stay in the static guide once. Behavioral gloss fires when the answer-shaping code is present. Hints are not a dump of every tally in the payload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxn04g27co5m8ci9lvdn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxn04g27co5m8ci9lvdn.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting company data across domains
&lt;/h2&gt;

&lt;p&gt;Enterprises rarely keep one system of record. Orders, shipping, payments, returns policy, inventory — often different teams, different APIs, and increasingly &lt;strong&gt;different MCP servers&lt;/strong&gt; (or specialist agents) in the same stack.&lt;/p&gt;

&lt;p&gt;Static manuals that say “also try the shipping server” on every turn are noise. Dynamic teaching is a practical join layer for the workflow engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structured fields&lt;/strong&gt; carry portable ids, stubs, and counts, but only when a cross-domain link is in scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual hints&lt;/strong&gt; say when and why to follow them — and what not to overwrite (e.g. help articles do not replace order status).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continuing after the agent said ORD-1042 is in transit with no ETA. The orders payload already had stubs; now hints say &lt;em&gt;when and why&lt;/em&gt; to leave this MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agentHints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Tracking exists — call get_shipment on the shipping MCP with trackingId."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Returns policy is in relatedDocs; call get_help_article — it does not override order status."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; “OK — where is the parcel right now, and what’s my return window?”&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Tools:&lt;/strong&gt; &lt;code&gt;get_shipment&lt;/code&gt; (shipping MCP) and &lt;code&gt;get_help_article&lt;/code&gt; (help MCP):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;get_shipment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"trackingId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1Z999"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lastScan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Brussels hub"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"in_transit"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;get_help_article&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kb-returns-eu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EU returns — 30 days from delivery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EU return policy is ...."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Structured stubs + hints teach the join; sibling servers answer the detail. An &lt;strong&gt;orchestrator or sibling agent&lt;/strong&gt; can act on those cues even if it never shared your Skill: the bridge rides the tool result, not a private system prompt.&lt;/p&gt;

&lt;p&gt;You are not federating every backend into one payload. You teach the next hop across company Domain Knowledge boundaries — whether the follow-on lives on this MCP, another MCP, or another agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can a Skill or system prompt replace dynamic teaching?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No&lt;/strong&gt; — not for the case-local half. Skills and system prompts are fine for packaging the &lt;em&gt;static&lt;/em&gt; guide (“you are the orders agent; resolve customer first”). They are a poor home for “explain &lt;em&gt;this&lt;/em&gt; &lt;code&gt;etaUnknown&lt;/code&gt;” or “follow &lt;em&gt;these&lt;/em&gt; related ids.”&lt;/p&gt;

&lt;p&gt;Why, briefly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent loads Skill when the &lt;em&gt;task&lt;/em&gt; looks relevant; a system prompt loads for the Agent &lt;em&gt;role&lt;/em&gt;. Neither sees what is on &lt;em&gt;this&lt;/em&gt; tool result unless you paste every edge case into the prompt.&lt;/li&gt;
&lt;li&gt;Encoding those edge cases as a long “if status X and flag Y…” Skill burns context and is easy to mis-apply — especially on smaller models. Hints are already matched to the payload.&lt;/li&gt;
&lt;li&gt;Skills and prompts do not invent &lt;code&gt;status&lt;/code&gt; / next-step fields. Those have to come from the tool.&lt;/li&gt;
&lt;li&gt;Other callers of the same MCP (evals, APIs, sibling agents) never see your prompt. Teaching that must always work has to ride the response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use Skills / prompts as optional packaging of the static core. Keep dynamic teaching on the wire.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the pieces fit
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Static guide / Skills / system prompts  →  how the domain usually works
Response Structured fields              →  what to do next (primary)
Response Contextual hints               →  this result only — rare codes, scope, next MCP hop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put the stable funnel and core statuses in the MCP static guide (and optionally in a Skill or agent prompt). Put product- or case-specific routing, rare-code glosses, and cross-domain bridges on the tool result — structured fields first, hints as reinforcement.&lt;/p&gt;

&lt;p&gt;For anything the agent &lt;em&gt;must&lt;/em&gt; get right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool descriptions&lt;/strong&gt; (plus server instructions when the client honors them) — what hosts actually surface; keep them short (~2 KB caps, always-on context cost).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured fields + hints on results&lt;/strong&gt; — guaranteed every call, scoped to the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills / System prompts&lt;/strong&gt; — nice for a given host or role; never the only place correct interpretation lives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Later, if hosts ship Skills with the MCP server, more of the static guide can move there. Keep response-local fields and hints for facts that exist only on the current result.&lt;/p&gt;

&lt;h2&gt;
  
  
  For MCP server builders
&lt;/h2&gt;

&lt;p&gt;Treat Domain Knowledge delivery as part of the API contract — Knowledge Engineering for agents, not only schema design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Author a short &lt;strong&gt;static core&lt;/strong&gt; in server instructions &lt;strong&gt;and&lt;/strong&gt; tool descriptions — do not rely on instructions alone (Codex / Claude Desktop gaps above).&lt;/li&gt;
&lt;li&gt;Design responses for agents: closed codes, next-step args, thin stubs for follow-on tools.&lt;/li&gt;
&lt;li&gt;Assemble &lt;strong&gt;contextual hints from the payload&lt;/strong&gt; — skip empty glossaries.&lt;/li&gt;
&lt;li&gt;Keep case-specific sentences out of the static manual when they can be stamped onto the response.&lt;/li&gt;
&lt;li&gt;Emit &lt;strong&gt;cross-domain bridges as data + hints&lt;/strong&gt; when a join is in scope.&lt;/li&gt;
&lt;li&gt;Document Skills / multi-agent prompts as &lt;strong&gt;optional packaging&lt;/strong&gt; of the same static core — not as the home of rare-code explanations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One MCP surface then works for a lone IDE agent, a CI eval, and a specialist inside a multi-agent orchestrator. Static Domain Knowledge can be thinned per host; the dynamic slice — including cross-domain next hops — stays on the wire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;MCP gives agents tools. Domain Knowledge teaching keeps them from misreading the answers, fuels what modern models are trained to do well — the &lt;strong&gt;agentic loop&lt;/strong&gt; over those tools — and helps that loop &lt;strong&gt;join company data across domains&lt;/strong&gt;, even when each domain is served by a different MCP server or specialist agent.&lt;/p&gt;

&lt;p&gt;That is Knowledge Engineering in practice: capture how the domain should be reasoned about, then place each piece where the agent will see it. Stable model → static channels (MCP first; Skills and system prompts optional). Case-local truth → dynamic channels on every result (structured fields primary, contextual hints as reinforcement).&lt;/p&gt;

&lt;p&gt;It also helps &lt;strong&gt;smaller and cheaper models&lt;/strong&gt; stay on the rails: they get the next hop and rare-code gloss on the payload instead of reconstructing a long playbook from a bloated static prompt. Same workflow quality, lower inference cost.&lt;/p&gt;

&lt;p&gt;Start with that split and let just-in-time Domain Knowledge ride the tool response. Feedback welcome if you try the pattern on your own domain tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;Dynamic / response-local teaching first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering/writing-tools-for-agents" rel="noopener noreferrer"&gt;Writing effective tools for agents&lt;/a&gt; (Anthropic, Sep 2025) — return meaningful context; steer agents via tool and error responses, not only descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2569" rel="noopener noreferrer"&gt;Dynamic next-action hints in tool results&lt;/a&gt; (MCP discussion, Apr 2026) — HATEOAS-style hints on the result; keep them turn-local, not in the system prompt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@kumaran.isk/agent-aware-mcp-10-patterns-for-actionable-tool-responses-54029e337941" rel="noopener noreferrer"&gt;Agent-Aware MCP: 10 patterns for actionable tool responses&lt;/a&gt; (Jan 2026) — &lt;code&gt;next_actions&lt;/code&gt;, progressive disclosure in payloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static packaging (Skills / server instructions):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://claude.com/resources/articles/skills-explained" rel="noopener noreferrer"&gt;Skills explained (vs prompts, MCP, subagents)&lt;/a&gt; — MCP connects; Skills teach procedures&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.modelcontextprotocol.io/posts/2025-11-03-using-server-instructions/" rel="noopener noreferrer"&gt;Server Instructions (MCP blog)&lt;/a&gt; — always-on server manual, and why client support varies&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Authenticated Machine-to-machine MCP without the OAuth puzzle</title>
      <dc:creator>Vlastimil Elias</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:12:52 +0000</pubDate>
      <link>https://dev.to/velias/authenticated-machine-to-machine-mcp-without-the-oauth-puzzle-31mn</link>
      <guid>https://dev.to/velias/authenticated-machine-to-machine-mcp-without-the-oauth-puzzle-31mn</guid>
      <description>&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization spec&lt;/a&gt; and most MCP clients assume a human is present: open a browser, consent, get a token, keep going until the session expires, then repeat. That model breaks as soon as you want an autonomous agent, a CI job, a daemon, or any other long-running process that must talk to an OAuth-protected MCP server with nobody at the keyboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/velias/mcp-client-credentials-auth" rel="noopener noreferrer"&gt;mcp-client-credentials-auth&lt;/a&gt;&lt;/strong&gt; is a local MCP authentication proxy for that gap. It sits between your MCP client and a remote MCP server, obtains tokens with the OAuth &lt;code&gt;client_credentials&lt;/code&gt; grant (&lt;a href="https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials" rel="noopener noreferrer"&gt;MCP OAuth Client Credentials extension&lt;/a&gt; Draft), and forwards MCP traffic with a Bearer token. Your MCP client keeps talking plain unauthenticated MCP, and the proxy handles authentication.&lt;/p&gt;

&lt;p&gt;This post is for two audiences: people who need machine access to a remote MCP server today, and MCP server providers who want a ready client path to recommend for that access.&lt;/p&gt;

&lt;p&gt;To use it, the MCP server provider must support machine auth via OAuth &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.4" rel="noopener noreferrer"&gt;&lt;code&gt;client_credentials&lt;/code&gt;&lt;/a&gt;. In practice that means a service account (sometimes labeled "API key", "machine-to-machine application", or similar) with a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; that their OAuth setup accepts for this grant. Creating those credentials is often self-service in the provider's account or developer portal, or they are handed out on request. Interactive-only servers (browser login / user consent only) are out of scope for this proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The use case
&lt;/h2&gt;

&lt;p&gt;Use it whenever the caller is a machine, not a person, &lt;strong&gt;and&lt;/strong&gt; the remote MCP provider offers that service-account style access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autonomous agents and background workers that need tools, resources, or prompts from a protected remote MCP&lt;/li&gt;
&lt;li&gt;CI/CD pipelines and ops automations&lt;/li&gt;
&lt;li&gt;Server-to-server integrations and long-running processes where human may not be at the keyboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use it when you are tired of unoptimal interactive authentication UX in MCP clients, when you can easily miss that MCP server is not authenticated, and you do not have access to the tools anymore.&lt;/p&gt;

&lt;p&gt;Once you have credentials from the provider, point the proxy at the remote MCP URL and drop it into your MCP client config. The proxy acquires acsess tokens, refreshes them proactively, reconnects when the remote MCP server flaps, and stays transparent to the protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation that stays simple (because discovery works)
&lt;/h2&gt;

&lt;p&gt;Well-behaved MCP servers publish &lt;a href="https://modelcontextprotocol.io/specification/2025-11-05/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization&lt;/a&gt; metadata (RFC 9728 / RFC 8414). The proxy follows that discovery path, so you normally do &lt;strong&gt;not&lt;/strong&gt; hand-configure a token endpoint or invent scopes.&lt;/p&gt;

&lt;p&gt;Typical setup in an MCP client such as Cursor or Claude Code is three environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"my-remote-server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-client-credentials-auth"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MCP_CC_PROXY_REMOTE_MCP_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.example.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MCP_CC_PROXY_CLIENT_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MCP_CC_PROXY_CLIENT_SECRET"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3cr3t"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole install for the common path: remote MCP server URL + &lt;code&gt;client_id&lt;/code&gt; + &lt;code&gt;client_secret&lt;/code&gt;. Discovery finds the IdP token endpoint and the baseline scopes. The proxy fails closed at startup if auth or the remote cannot be made ready, so you do not get a green local session that dies on the first real call.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx&lt;/code&gt; downloads and runs the published package on your machine, so use trusted packages only (same caution as any other &lt;code&gt;npx&lt;/code&gt; MCP server).&lt;/p&gt;

&lt;p&gt;If a server accepts Bearer tokens but does not publish discovery, you can still run with a manual token endpoint and scopes (&lt;code&gt;MCP_CC_PROXY_TOKEN_ENDPOINT&lt;/code&gt; and usually &lt;code&gt;MCP_CC_PROXY_SCOPES&lt;/code&gt;). Those values must come from the MCP server provider's documentation, do not try to invent them. Prefer auto-discovery when the MCP server supports it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two deployment options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Local stdio (default): one client, one proxy process
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3h5lgluscucbjeay4t77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3h5lgluscucbjeay4t77.png" alt="Diagram of local stdio deployment" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the install path above. Your MCP client spawns the proxy over stdio (proxy acts as local MCP Server) on the same machine, and the proxy talks outbound HTTPS to the remote server with Bearer auth. Each user (or agent host) should use their own service-account credentials, not a shared org-wide secret copied into every laptop config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Viable when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single MCP client (IDE, agent host, CLI) can spawn a local MCP Server process&lt;/li&gt;
&lt;li&gt;Each user can obtain (or be issued) their own &lt;code&gt;client_credentials&lt;/code&gt; client&lt;/li&gt;
&lt;li&gt;You want the smallest attack surface: no local network listener&lt;/li&gt;
&lt;li&gt;Credentials stay on that machine / in that client's secret store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefer stdio whenever one client can own the proxy lifecycle. It is the right default for Cursor, Claude Desktop, Claude Code, and similar local setups.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. On-premises HTTP: one shared proxy and identity for several clients
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg7sg89n15u0i9bzw538n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg7sg89n15u0i9bzw538n.png" alt="Diagram of on-premises HTTP deployment" width="799" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;MCP_CC_PROXY_TRANSPORT=http&lt;/code&gt;. The process listens for Streamable HTTP so multiple MCP clients on a private network can share one outbound &lt;code&gt;client_credentials&lt;/code&gt; identity. A container image is also available with HTTP as the default transport and k8s style health checks for easy deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Viable when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Several MCP clients on a trusted private network should share one machine identity&lt;/li&gt;
&lt;li&gt;You do not want to install or spawn the proxy on every workstation&lt;/li&gt;
&lt;li&gt;A single MCP client (IDE, agent host, CLI) can't spawn a local MCP Server process but has to use remote one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HTTP mode has no TLS or inbound authentication on the proxy itself, see Security considerations.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Choose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One client can spawn a local MCP server process, and each user has their own OAuth client&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Local stdio&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Many clients or no local MCP Server support, one shared M2M identity on a private network&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;On-premises HTTP&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public internet or untrusted callers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not recommended&lt;/strong&gt; for either mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What you get beyond "it connects"
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Transparent bidirectional MCP forwarding&lt;/li&gt;
&lt;li&gt;Identity and capability forwarding (your client sees the remote server's real name and capabilities, including server-to-client features such as sampling and elicitation)&lt;/li&gt;
&lt;li&gt;Proactive access token refresh and 401 retry behavior&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#step-up-authorization-flow" rel="noopener noreferrer"&gt;Scope step-up&lt;/a&gt; when the remote challenges with 403 &lt;code&gt;insufficient_scope&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Streamable HTTP with SSE fallback for connection-class failures&lt;/li&gt;
&lt;li&gt;Automatic reconnection and stale remote session recovery&lt;/li&gt;
&lt;li&gt;Optional call audit log (on by default in HTTP mode)&lt;/li&gt;
&lt;li&gt;Categorized errors (&lt;code&gt;authentication&lt;/code&gt; / &lt;code&gt;connection&lt;/code&gt; / &lt;code&gt;remote&lt;/code&gt;) with detailed messages for easier troubleshooting&lt;/li&gt;
&lt;li&gt;Fail-closed startup: the local transport binds only after auth is ready and the remote MCP server is reachable, so your MCP client shows a correct status&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security considerations
&lt;/h2&gt;

&lt;p&gt;A few rules of thumb matter more than the feature list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treat the service account as powerful.&lt;/strong&gt; Whoever holds &lt;code&gt;client_id&lt;/code&gt; / &lt;code&gt;client_secret&lt;/code&gt; (or can reach an HTTP-mode proxy that already has them) gets that remote privilege. Prefer per-user clients for stdio, and rotate on compromise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer vaults over plain-text MCP config.&lt;/strong&gt; The install snippet above shows secrets in environment variables for brevity. In production, inject &lt;code&gt;client_id&lt;/code&gt; / &lt;code&gt;client_secret&lt;/code&gt; at launch from 1Password, Bitwarden, a cloud secret store, or similar, instead of leaving them in an MCP config file on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the proxy private.&lt;/strong&gt; Stdio has no network listener. HTTP mode is for trusted private networks only: recommended to terminate TLS and inbound auth at a reverse proxy, ingress, or MCP gateway. Never publish the listener to the public internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP mode is one shared machine identity.&lt;/strong&gt; All MCP clients reuse the same outbound Bearer token and scopes. It is not a multi-tenant edge, so use separate deployments per trust boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proxy owns the outbound Authorization header.&lt;/strong&gt; Access tokens stay in memory and are never logged, and the local MCP client cannot supply or override the Bearer token used toward the remote server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More detail is in the repo &lt;a href="https://github.com/velias/mcp-client-credentials-auth#security" rel="noopener noreferrer"&gt;Security&lt;/a&gt; section.&lt;/p&gt;

&lt;h2&gt;
  
  
  For MCP server providers
&lt;/h2&gt;

&lt;p&gt;If you already protect your MCP server with OAuth for interactive clients, your users will still ask how agents, CI, and other headless callers should authenticate. You do not need to ship a custom M2M client for every IDE and agent host. Document this proxy as the supported way to get service-account access: users create (or receive) a &lt;code&gt;client_id&lt;/code&gt; / &lt;code&gt;client_secret&lt;/code&gt;, point the proxy at your MCP URL, and keep using their existing plain MCP client.&lt;/p&gt;

&lt;p&gt;That works best when your product already exposes machine credentials and standard MCP Authorization discovery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish protected resource / authorization server metadata so the proxy can find your token endpoint and baseline scopes&lt;/li&gt;
&lt;li&gt;Offer a service account (or equivalent) granted those baseline scopes for &lt;code&gt;client_credentials&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Prefer a short setup guide: remote URL + credentials (+ this proxy), not a DIY OAuth client walkthrough&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users then get secure, long-term, non-interactive service-account access without building token acquisition themselves. You keep one OAuth-protected MCP surface for both human consent flows and machine callers, and you point the machine path at a known, discovery-friendly client instead of fragmented one-off scripts.&lt;/p&gt;

&lt;p&gt;If discovery is not available yet, you can still recommend the proxy, but document the necessary settings yourself: the IdP token endpoint URL and the scopes to request (&lt;code&gt;MCP_CC_PROXY_TOKEN_ENDPOINT&lt;/code&gt; and &lt;code&gt;MCP_CC_PROXY_SCOPES&lt;/code&gt;). Users cannot discover those without your guidance. Auto-discovery remains the smoother story when you can publish it.&lt;/p&gt;

&lt;p&gt;For more detail see &lt;a href="https://github.com/velias/mcp-client-credentials-auth#notes-for-mcp-server-developers" rel="noopener noreferrer"&gt;notes for MCP Server Developers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/velias/mcp-client-credentials-auth" rel="noopener noreferrer"&gt;velias/mcp-client-credentials-auth&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/mcp-client-credentials-auth" rel="noopener noreferrer"&gt;mcp-client-credentials-auth&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Container: &lt;code&gt;ghcr.io/velias/mcp-client-credentials-auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Spec: &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spec: &lt;a href="https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials" rel="noopener noreferrer"&gt;MCP OAuth Client Credentials&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you run OAuth-protected MCP for agents or pipelines, or you operate an MCP server and want an easy M2M client path for your users, start with discovery plus service-account credentials and let the proxy handle the rest on the client side. Feedback and issues are welcome on the repo.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>Bridge the gap between your IdP and the MCP World</title>
      <dc:creator>Vlastimil Elias</dc:creator>
      <pubDate>Fri, 15 May 2026 14:23:40 +0000</pubDate>
      <link>https://dev.to/velias/bridge-the-gap-between-your-idp-and-the-mcp-world-3gbn</link>
      <guid>https://dev.to/velias/bridge-the-gap-between-your-idp-and-the-mcp-world-3gbn</guid>
      <description>&lt;p&gt;So you've got your corporate IdP (Keycloak, Auth0, Okta, Azure AD, whatever) and now you want your MCP servers to use it for auth. You point Claude Code or Cursor at it, aaand... things break. Scope explosions on the consent screen, missing PKCE defaults, clients demanding Dynamic Client Registration your IdP doesn't serve the way MCP expects. Sound familiar?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization spec&lt;/a&gt; expects certain OAuth behaviors that most enterprise IdPs don't provide out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP clients expect open Dynamic Client Registration.&lt;/strong&gt; Most IdPs either don't expose it or lock it behind admin credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP clients tend to request all announced scopes.&lt;/strong&gt; This isn't required by the spec - it's just how most clients (Claude Code, Cursor, others) behave in practice. They read &lt;code&gt;scopes_supported&lt;/code&gt; from discovery and request &lt;em&gt;all of them&lt;/em&gt;. Your Keycloak announces 15 internal scopes? Congrats, users now see a consent screen from hell - or the request gets rejected outright if client is not pre-approved for some announced scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Many clients add &lt;code&gt;offline_access&lt;/code&gt; unconditionally.&lt;/strong&gt; Again, not a spec requirement - just a common client behavior to ensure they get refresh tokens. This becomes a problem when your IdP restricts long-lived refresh tokens or requires client pre-approval for that scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovery metadata needs filtering.&lt;/strong&gt; IdPs expose dozens of OIDC fields (CIBA, device flow, logout endpoints...) that are irrelevant noise for MCP and can confuse clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could customize your IdP, but that's a maintenance rabbit hole - especially when you need the same IdP for non-MCP and legacy apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter mcp-auth-adapter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/velias/mcp-auth-adapter" rel="noopener noreferrer"&gt;&lt;strong&gt;mcp-auth-adapter&lt;/strong&gt;&lt;/a&gt; is a thin, stateless Node.js proxy that sits between your MCP clients and your existing IdP. It doesn't issue tokens or handle authentication - all the real work stays on your IdP. It just makes the OAuth dance MCP-compatible.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Filtered discovery&lt;/strong&gt; - Serves &lt;code&gt;/.well-known/&lt;/code&gt; IdP metadata with only the fields MCP clients actually need. Injects safe defaults (PKCE S256, authorization_code grant) when your IdP's metadata is incomplete, and own functionality where needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open DCR endpoint&lt;/strong&gt; - &lt;code&gt;POST /register&lt;/code&gt; hands out a fixed, pre-configured &lt;code&gt;client_id&lt;/code&gt; so MCP clients can self-register. No IdP-side DCR needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope filtering&lt;/strong&gt; - Control what scopes reach your IdP. Strip &lt;code&gt;offline_access&lt;/code&gt;, remove internal scopes, or use an allowlist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Only these scopes will ever reach the upstream IdP&lt;/span&gt;
&lt;span class="nv"&gt;MCP_PROXY_AUTH_SCOPES_PRESERVED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openid,api.read,api.write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CIMD support (experimental)&lt;/strong&gt; - The MCP spec defaults to &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/" rel="noopener noreferrer"&gt;Client ID Metadata Documents&lt;/a&gt; for client identification, but it's an emerging IETF draft (not yet RFC) and no major IdP supports it natively today. This adapter bridges that gap - it accepts CIMD-style &lt;code&gt;client_id&lt;/code&gt; URLs from MCP clients, validates the metadata documents, and maps them to real upstream client_ids your IdP understands. So you get CIMD compatibility without waiting for your IdP vendor to implement it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability built in&lt;/strong&gt; - Prometheus metrics at &lt;code&gt;/metrics&lt;/code&gt;, structured logging, health probes for k8s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy it in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Grab the container image and go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; mcp-auth-adapter &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MCP_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://mcp-auth.example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MCP_UPSTREAM_SSO_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://sso.example.com/auth/realms/external &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MCP_PROXY_DCR_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mcp-client &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/velias/mcp-auth-adapter:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three env vars for a basic setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MCP_BASE_URL&lt;/code&gt; - the public URL where this adapter lives&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MCP_UPSTREAM_SSO_URL&lt;/code&gt; - your IdP's issuer URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MCP_PROXY_DCR_CLIENT_ID&lt;/code&gt; - a public client pre-registered at your IdP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then point your MCP server's &lt;code&gt;authorization_servers&lt;/code&gt; to &lt;code&gt;MCP_BASE_URL&lt;/code&gt; and clients will discover everything via &lt;code&gt;.well-known&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typical production config
&lt;/h3&gt;

&lt;p&gt;For a real deployment you'll probably want scope control too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MCP_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://mcp-auth.example.com
&lt;span class="nv"&gt;MCP_UPSTREAM_SSO_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://sso.example.com/auth/realms/external
&lt;span class="nv"&gt;MCP_PROXY_DCR_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mcp-client
&lt;span class="nv"&gt;MCP_WELL_KNOWN_SCOPES_SUPPORTED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openid,api.read,api.write
&lt;span class="nv"&gt;MCP_PROXY_AUTH_SCOPES_REMOVED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;offline_access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This controls both sides: what clients &lt;em&gt;see&lt;/em&gt; in discovery and what actually &lt;em&gt;reaches&lt;/em&gt; your IdP.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;This adapter is intentionally minimal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No token issuing&lt;/strong&gt; - tokens come from your IdP, always&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No user database&lt;/strong&gt; - stateless, nothing to back up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No rate limiting&lt;/strong&gt; - put it behind your existing reverse proxy / WAF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No CORS&lt;/strong&gt; - designed for redirect-based flows, not browser fetch calls&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;p&gt;If you're running MCP servers (or planning to) and have an existing OAuth/OIDC provider, this saves you from bending your IdP config to accommodate MCP client quirks. Works with Keycloak, Auth0, Okta, Azure AD/Entra, Google Identity - anything that serves standard OIDC discovery.&lt;/p&gt;

&lt;p&gt;Tested with Claude Code, Claude Desktop, Cursor IDE, Cursor Agent, Gemini CLI, VS Code, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;MCP is moving fast, and auth is one of those things that &lt;em&gt;should&lt;/em&gt; just work but often doesn't when you try to connect real-world IdPs to real-world MCP clients. Instead of fighting your IdP config or waiting for vendors to catch up with emerging standards like CIMD, drop a lightweight adapter in between and move on to building the actual MCP tools your users care about.&lt;/p&gt;

&lt;p&gt;If you hit rough edges or have ideas, open an issue - the project is young and feedback shapes the roadmap.&lt;/p&gt;




&lt;p&gt;Apache 2.0 licensed. PRs welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/velias/mcp-auth-adapter" rel="noopener noreferrer"&gt;github.com/velias/mcp-auth-adapter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
