<?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: Juan Gómez</title>
    <description>The latest articles on DEV Community by Juan Gómez (@jgomezdev).</description>
    <link>https://dev.to/jgomezdev</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%2F1592628%2Fc54806ef-8072-4425-9b0e-edd50de74e2d.png</url>
      <title>DEV Community: Juan Gómez</title>
      <link>https://dev.to/jgomezdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jgomezdev"/>
    <language>en</language>
    <item>
      <title>Crea un servidor MCP en C#: escribe tus herramientas una vez y úsalas en cualquier Claude</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 31 Jul 2026 19:58:43 +0000</pubDate>
      <link>https://dev.to/jgomezdev/crea-un-servidor-mcp-en-c-escribe-tus-herramientas-una-vez-y-usalas-en-cualquier-claude-3gg3</link>
      <guid>https://dev.to/jgomezdev/crea-un-servidor-mcp-en-c-escribe-tus-herramientas-una-vez-y-usalas-en-cualquier-claude-3gg3</guid>
      <description>&lt;h1&gt;
  
  
  Crea un servidor MCP en C#: escribe tus herramientas una vez y úsalas en cualquier Claude
&lt;/h1&gt;

&lt;p&gt;La última vez &lt;a href="https://dev.to/jgomezdev/crea-un-agente-con-tool-use-de-claude-en-c-no-es-un-chatbot-con-esteroides-fjh"&gt;construimos un agente de tool use con Claude en C#&lt;/a&gt;. Funcionaba — pero cada herramienta vivía &lt;em&gt;dentro de esa única app de consola&lt;/em&gt;. Si querías la misma capacidad de "consultar un pedido" dentro de Claude Desktop, o de Claude Code, o del agente de un compañero, tocaba copiar y pegar la herramienta y toda su fontanería en cada sitio. Herramientas atrapadas en un solo proceso, reimplementadas por todas partes. Ese es justo el acoplamiento que MCP viene a romper.&lt;/p&gt;

&lt;p&gt;El &lt;strong&gt;Model Context Protocol&lt;/strong&gt; le da la vuelta a quién es dueño de la herramienta. La escribes &lt;em&gt;una vez&lt;/em&gt;, como un servidor independiente, y la expones sobre un protocolo estándar. Entonces cualquier cliente MCP — Claude Desktop, Claude Code, tu propio agente, lo que salga el próximo trimestre — se conecta y obtiene tus herramientas gratis. En este post cogemos las mismas herramientas de Aurora Coffee Co. de la última vez y las liberamos como un servidor MCP real en .NET, para luego enchufarlo a Claude.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué es MCP en realidad
&lt;/h2&gt;

&lt;p&gt;MCP es un pequeño protocolo cliente/servidor (JSON-RPC por debajo) para conectar clientes de IA con tus capacidades. Con tres ideas ya puedes arrancar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cliente y servidor son procesos separados.&lt;/strong&gt; El &lt;em&gt;cliente&lt;/em&gt; es la app de IA (Claude Desktop, Claude Code). El &lt;em&gt;servidor&lt;/em&gt; es tu código. Se hablan por un transporte, no por una tabla de funciones compartida.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Los servidores exponen tres cosas:&lt;/strong&gt; &lt;strong&gt;tools&lt;/strong&gt; (acciones que el modelo puede invocar — nuestro foco), &lt;strong&gt;resources&lt;/strong&gt; (datos legibles, como ficheros o filas) y &lt;strong&gt;prompts&lt;/strong&gt; (plantillas de prompt reutilizables). Hoy todo son tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dos transportes.&lt;/strong&gt; &lt;strong&gt;stdio&lt;/strong&gt; — el cliente lanza tu servidor como proceso hijo y hablan por la entrada/salida estándar; ideal para herramientas locales. &lt;strong&gt;HTTP&lt;/strong&gt; — tu servidor corre en algún sitio y los clientes se conectan por red; para herramientas compartidas o remotas.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si leíste el artículo anterior, este es el mismo bucle de tool use — declaras una herramienta, el modelo la invoca, tu código la ejecuta, devuelves un resultado — pero ahora la herramienta vive &lt;em&gt;al otro lado de una frontera de proceso&lt;/em&gt;, reutilizable por cualquier cliente en lugar de cableada dentro de una sola app.&lt;/p&gt;




&lt;h2&gt;
  
  
  El servidor que vamos a construir
&lt;/h2&gt;

&lt;p&gt;¿Te acuerdas de &lt;strong&gt;Aurora Coffee Co.&lt;/strong&gt;? Sus dos herramientas respondían preguntas de soporte sobre unos datos en memoria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_order_status(order_id)&lt;/code&gt; — consulta un pedido.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_stock(sku)&lt;/code&gt; — comprueba el inventario de un producto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La última vez esas herramientas eran métodos enterrados en un bucle de agente. Ahora se convierten en un servidor MCP que &lt;em&gt;cualquier&lt;/em&gt; Claude puede invocar. Arranca una app de consola y añade dos paquetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new console &lt;span class="nt"&gt;-o&lt;/span&gt; AuroraCoffee.Mcp
&lt;span class="nb"&gt;cd &lt;/span&gt;AuroraCoffee.Mcp
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ModelContextProtocol&lt;/code&gt; es el SDK oficial de C# (1.0.0, mantenido en colaboración con Microsoft). &lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt; nos da el host genérico — la misma historia de DI y configuración que usarías en cualquier servicio .NET.&lt;/p&gt;

&lt;h3&gt;
  
  
  Los datos, como servicio inyectable
&lt;/h3&gt;

&lt;p&gt;Guardamos la "base de datos" de Aurora en un singleton para que las herramientas la reciban por inyección en el constructor — igual que un repositorio de verdad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeShopData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-08-03"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-08-06"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Stock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ETH-250"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"COL-1KG"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Las herramientas
&lt;/h3&gt;

&lt;p&gt;Esta es la parte que sustituye al JSON Schema escrito a mano y al &lt;code&gt;switch&lt;/code&gt; de la última vez. Una clase marcada con &lt;code&gt;[McpServerToolType]&lt;/code&gt;, métodos marcados con &lt;code&gt;[McpServerTool]&lt;/code&gt;, y &lt;code&gt;[Description]&lt;/code&gt; tanto en el método como en sus parámetros. El SDK lee la &lt;strong&gt;firma de tu método&lt;/strong&gt; y te genera el JSON Schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerToolType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CoffeeShopData&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Consulta el estado y la fecha estimada de entrega de un pedido por su id."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetOrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Id del pedido, p. ej. A-1001"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"Pedido &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, ETA &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"No hay ningún pedido con ese id."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Comprueba cuántas unidades de un SKU de producto hay en stock ahora mismo."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;CheckStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SKU del producto, p. ej. ETH-250"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"SKU &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unidades en stock."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SKU desconocido."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esos atributos &lt;code&gt;[Description]&lt;/code&gt; no son adorno — son los mismos docstrings con peso del artículo anterior, solo que escritos en un sitio más bonito. El del método le dice a Claude &lt;em&gt;cuándo&lt;/em&gt; llamar a la herramienta; los de los parámetros, &lt;em&gt;qué pasarle&lt;/em&gt;. Descripciones vagas aquí son la razón número uno de que un modelo ignore una herramienta perfectamente buena.&lt;/p&gt;

&lt;p&gt;Fíjate en lo que desapareció: ni objeto &lt;code&gt;input_schema&lt;/code&gt;, ni array &lt;code&gt;required&lt;/code&gt;, ni parseo manual de argumentos. &lt;code&gt;orderId&lt;/code&gt; es un parámetro &lt;code&gt;string&lt;/code&gt; tipado, así que el SDK sabe que es un string obligatorio y te entrega el valor ya parseado. La fontanería del Enfoque 1 de la última vez ahora es trabajo del framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uniéndolo todo
&lt;/h3&gt;

&lt;p&gt;El host builder registra los datos, el servidor MCP, el transporte stdio y descubre automáticamente todos los &lt;code&gt;[McpServerToolType]&lt;/code&gt; del ensamblado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Logging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApplicationBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// stdout es el canal del protocolo — los logs DEBEN ir a stderr o corromperás el stream.&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogToStandardErrorThreshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CoffeeShopData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithStdioServerTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithToolsFromAssembly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese es el servidor entero. Lee otra vez la línea del logging, porque ahí está la trampa más común del MCP por stdio: &lt;strong&gt;stdout transporta los mensajes JSON-RPC&lt;/strong&gt;. Un solo &lt;code&gt;Console.WriteLine&lt;/code&gt; despistado a stdout y Claude ve ruido donde esperaba JSON — el equivalente en protocolo a hablar con la boca llena. Manda todos los logs a stderr y no habrá problema.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enchufándolo a Claude
&lt;/h2&gt;

&lt;p&gt;Un servidor al que nadie se conecta no es más que una app de consola muy solitaria. Vamos a conectarlo a dos clientes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Desktop&lt;/strong&gt; lee un fichero de configuración JSON (&lt;code&gt;claude_desktop_config.json&lt;/code&gt;, en &lt;code&gt;%APPDATA%\Claude\&lt;/code&gt; en Windows o &lt;code&gt;~/Library/Application Support/Claude/&lt;/code&gt; en macOS). Añade tu servidor bajo &lt;code&gt;mcpServers&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;"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;"aurora-coffee"&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;"dotnet"&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;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:/src/AuroraCoffee.Mcp"&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;El cliente ejecuta ese comando, habla stdio con el proceso hijo y tus herramientas aparecen. Reinicia Claude Desktop y pregunta &lt;em&gt;"¿El pedido A-1001 está enviado, y todavía te quedan ETH-250 en stock?"&lt;/em&gt; — llamará a ambas herramientas y responderá de verdad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; es aún más corto — un comando de CLI, sin editar ficheros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add aurora-coffee &lt;span class="nt"&gt;--&lt;/span&gt; dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; ./AuroraCoffee.Mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mismo servidor, segundo cliente, cero cambios de código. &lt;em&gt;Ese es justo el sentido de MCP&lt;/em&gt;: las herramientas no se movieron, pero ahora dos Claude distintos pueden alcanzarlas — y también el tercero que aún no has instalado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pruébalo sin cliente
&lt;/h3&gt;

&lt;p&gt;Antes de tocar la config de ningún cliente, verifica el servidor con el &lt;strong&gt;MCP Inspector&lt;/strong&gt;, una UI de navegador que habla el protocolo para que listes e invoques herramientas a mano:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; ./AuroraCoffee.Mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lanza tu servidor, lista &lt;code&gt;get_order_status&lt;/code&gt; y &lt;code&gt;check_stock&lt;/code&gt;, y te deja disparar llamadas de prueba. Si una herramienta se porta mal, te enteras aquí — no tres capas dentro de un transcript de chat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Llegando al mundo exterior: DI y HttpClient
&lt;/h2&gt;

&lt;p&gt;Los diccionarios en memoria valen para una demo, pero las herramientas de verdad llaman a sistemas de verdad. Como el servidor es un host .NET normal, la inyección de dependencias funciona tal cual esperas — incluidos los &lt;code&gt;HttpClient&lt;/code&gt; tipados. Considera que Aurora tiene una API de tostadero para notas de cata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerToolType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoastTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Obtiene las notas de tueste de hoy para un origen de café."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetRoastNotes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Nombre del origen, p. ej. Ethiopia"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"https://api.auroracoffee.example/roast-notes/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EscapeDataString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regístralo como cliente tipado y listo — el SDK inyecta el &lt;code&gt;HttpClient&lt;/code&gt; en el constructor de la herramienta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RoastTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dos cosas a subrayar. Primera, las herramientas pueden ser &lt;code&gt;async&lt;/code&gt; y devolver &lt;code&gt;Task&amp;lt;string&amp;gt;&lt;/code&gt; — sin ceremonia, solo &lt;code&gt;await&lt;/code&gt;. Segunda, usa &lt;code&gt;AddHttpClient&amp;lt;T&amp;gt;&lt;/code&gt; (o &lt;code&gt;IHttpClientFactory&lt;/code&gt;) en vez de instanciar un &lt;code&gt;HttpClient&lt;/code&gt; por llamada; la factory reutiliza conexiones para que no agotes sockets bajo carga. Aplica la higiene de siempre en .NET — MCP no cambia nada de eso.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pasando al modo remoto: el transporte HTTP
&lt;/h2&gt;

&lt;p&gt;stdio es genial cuando el cliente puede lanzar tu servidor en local. Pero si quieres &lt;em&gt;un&lt;/em&gt; servidor compartido por un equipo, o corriendo en un contenedor, quieres HTTP. Cambia el paquete de transporte y dos líneas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ModelContextProtocol.AspNetCore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CoffeeShopData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithToolsFromAssembly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los mismos &lt;code&gt;CoffeeTools&lt;/code&gt;, los mismos atributos, el mismo descubrimiento — solo cambiaron el transporte y el host. Ahora los clientes se conectan por URL en vez de lanzar un proceso, y obtienes todo lo que ASP.NET Core ya te da: middleware de auth, HTTPS, hosting, escalado. Tus herramientas ni se enteraron.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo construir un servidor MCP — y cuándo no
&lt;/h2&gt;

&lt;p&gt;Tira de MCP cuando las herramientas necesiten ser &lt;strong&gt;compartidas o reutilizadas&lt;/strong&gt;: la misma capacidad en Claude Desktop &lt;em&gt;y&lt;/em&gt; en Claude Code, un servidor al que apunte todo tu equipo, o herramientas que quieras desacopladas de cualquier app y libres de evolucionar a su propio ritmo de releases. La frontera de proceso es una ventaja — es lo que permite que un servidor sirva a muchos clientes, en cualquier lenguaje.&lt;/p&gt;

&lt;p&gt;Sáltatelo cuando exactamente una app vaya a usar las herramientas y sea esa app la que hace las llamadas al modelo. Entonces el bucle de tool use dentro de la app del artículo anterior es más simple y rápido — sin transporte, sin segundo proceso. Un servidor cuyo único cliente es él mismo no es más que una llamada a función disfrazada de protocolo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regla general: tool use dentro de la app cuando una app es dueña del modelo y de las herramientas; un servidor MCP en el momento en que más de un cliente necesita las mismas herramientas.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Puntos Clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP desacopla las herramientas de los clientes&lt;/strong&gt; — escribe una herramienta una vez como servidor y cualquier cliente MCP (Claude Desktop, Claude Code, tu propio agente) puede usarla, en vez de reimplementarla en cada app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El SDK va por atributos&lt;/strong&gt; — &lt;code&gt;[McpServerToolType]&lt;/code&gt; en la clase, &lt;code&gt;[McpServerTool]&lt;/code&gt; en el método, y el JSON Schema se genera a partir de tu firma. Sin schema a mano, sin parseo manual de argumentos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Las descripciones siguen teniendo peso&lt;/strong&gt; — &lt;code&gt;[Description]&lt;/code&gt; en el método y en cada parámetro es como Claude decide cuándo llamar a una herramienta y qué pasarle. Escríbelas como docstrings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cuida stdout&lt;/strong&gt; — en modo stdio el protocolo &lt;em&gt;es&lt;/em&gt; stdout. Manda los logs a stderr o corromperás el stream con tu propia salida de debug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mismas herramientas, cambia el transporte&lt;/strong&gt; — stdio para herramientas locales por proceso hijo, HTTP (&lt;code&gt;ModelContextProtocol.AspNetCore&lt;/code&gt;) para compartidas o remotas. El código de la herramienta no cambia.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Build an MCP Server in C#: Write Your Tools Once, Use Them in Every Claude</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 31 Jul 2026 19:58:32 +0000</pubDate>
      <link>https://dev.to/jgomezdev/build-an-mcp-server-in-c-write-your-tools-once-use-them-in-every-claude-4oa1</link>
      <guid>https://dev.to/jgomezdev/build-an-mcp-server-in-c-write-your-tools-once-use-them-in-every-claude-4oa1</guid>
      <description>&lt;h1&gt;
  
  
  Build an MCP Server in C#: Write Your Tools Once, Use Them in Every Claude
&lt;/h1&gt;

&lt;p&gt;Last time we &lt;a href="https://dev.to/jgomezdev/build-a-claude-tool-use-agent-in-c-not-a-chatbot-on-steroids-3e9g"&gt;built a Claude tool-use agent in C#&lt;/a&gt;. It worked — but every tool lived &lt;em&gt;inside that one console app&lt;/em&gt;. If you wanted the same "look up an order" ability inside Claude Desktop, or Claude Code, or a teammate's agent, you'd copy-paste the tool and its plumbing into each one. Tools trapped in a single process, re-implemented everywhere. That's the coupling MCP exists to break.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Model Context Protocol&lt;/strong&gt; flips the ownership around. You write a tool &lt;em&gt;once&lt;/em&gt;, as a standalone server, and expose it over a standard protocol. Then any MCP client — Claude Desktop, Claude Code, your own agent, whatever ships next quarter — connects and gets your tools for free. In this post we'll take the exact same Aurora Coffee Co. tools from last time and set them loose as a real MCP server in .NET, then plug it into Claude.&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%2Fm530wql1bu1k6wtzk69t.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%2Fm530wql1bu1k6wtzk69t.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP actually is
&lt;/h2&gt;

&lt;p&gt;MCP is a small client/server protocol (JSON-RPC under the hood) for connecting AI clients to your capabilities. Three ideas are all you need to start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client and server are separate processes.&lt;/strong&gt; The &lt;em&gt;client&lt;/em&gt; is the AI app (Claude Desktop, Claude Code). The &lt;em&gt;server&lt;/em&gt; is your code. They talk over a transport, not a shared function table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Servers expose three things:&lt;/strong&gt; &lt;strong&gt;tools&lt;/strong&gt; (actions the model can call — our focus), &lt;strong&gt;resources&lt;/strong&gt; (readable data, like files or rows), and &lt;strong&gt;prompts&lt;/strong&gt; (reusable prompt templates). Today it's all tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two transports.&lt;/strong&gt; &lt;strong&gt;stdio&lt;/strong&gt; — the client launches your server as a child process and talks over standard in/out; perfect for local tools. &lt;strong&gt;HTTP&lt;/strong&gt; — your server runs somewhere and clients connect over the network; for shared or remote tools.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you read the previous article, this is the same tool-use loop — declare a tool, the model calls it, your code runs it, you return a result — but the tool now lives on the &lt;em&gt;other side of a process boundary&lt;/em&gt;, reusable by any client instead of hardwired into one app.&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%2Fgcq3vnetqlau19fep4gx.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%2Fgcq3vnetqlau19fep4gx.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The server we're building
&lt;/h2&gt;

&lt;p&gt;Remember &lt;strong&gt;Aurora Coffee Co.&lt;/strong&gt;? Its two tools answered support questions over some in-memory data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_order_status(order_id)&lt;/code&gt; — looks up an order.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_stock(sku)&lt;/code&gt; — checks inventory for a product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last time those tools were methods buried in an agent loop. Now they become an MCP server that &lt;em&gt;any&lt;/em&gt; Claude can call. Start a console app and add two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new console &lt;span class="nt"&gt;-o&lt;/span&gt; AuroraCoffee.Mcp
&lt;span class="nb"&gt;cd &lt;/span&gt;AuroraCoffee.Mcp
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ModelContextProtocol&lt;/code&gt; is the official C# SDK (1.0.0, maintained with Microsoft). &lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt; gives us the generic host — the same DI and configuration story you'd use in any .NET service.&lt;/p&gt;

&lt;h3&gt;
  
  
  The data, as an injectable service
&lt;/h3&gt;

&lt;p&gt;We'll hold Aurora's "database" in a singleton so tools can take it via constructor injection — exactly like a real repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeShopData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-08-03"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-08-06"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Stock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ETH-250"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"COL-1KG"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The tools
&lt;/h3&gt;

&lt;p&gt;Here's the part that replaces last time's hand-written JSON Schema and &lt;code&gt;switch&lt;/code&gt; statement. A class marked &lt;code&gt;[McpServerToolType]&lt;/code&gt;, methods marked &lt;code&gt;[McpServerTool]&lt;/code&gt;, and &lt;code&gt;[Description]&lt;/code&gt; on both the method and its parameters. The SDK reads your &lt;strong&gt;method signature&lt;/strong&gt; and generates the JSON Schema for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerToolType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CoffeeShopData&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Look up the status and ETA of a customer order by its id."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetOrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order id, e.g. A-1001"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"Order &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, ETA &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"No order found with that id."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Check how many units of a product SKU are currently in stock."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;CheckStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product SKU, e.g. ETH-250"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"SKU &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; units in stock."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Unknown SKU."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those &lt;code&gt;[Description]&lt;/code&gt; attributes are not decoration — they're the same load-bearing docstrings from the last article, just written in a nicer place. The method one tells Claude &lt;em&gt;when&lt;/em&gt; to call the tool; the parameter ones tell it &lt;em&gt;what to pass&lt;/em&gt;. Vague descriptions here are the number-one reason a model ignores a perfectly good tool.&lt;/p&gt;

&lt;p&gt;Notice what's gone: no &lt;code&gt;input_schema&lt;/code&gt; object, no &lt;code&gt;required&lt;/code&gt; array, no manual argument parsing. &lt;code&gt;orderId&lt;/code&gt; is a typed &lt;code&gt;string&lt;/code&gt; parameter, so the SDK knows it's a required string and hands you the parsed value. The plumbing from Approach 1 last time is now the framework's job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiring it together
&lt;/h3&gt;

&lt;p&gt;The host builder registers the data, the MCP server, the stdio transport, and auto-discovers every &lt;code&gt;[McpServerToolType]&lt;/code&gt; in the assembly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Logging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApplicationBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// stdout is the protocol channel — logs MUST go to stderr or you corrupt the stream.&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogToStandardErrorThreshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CoffeeShopData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithStdioServerTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithToolsFromAssembly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole server. Read that logging line again, because it's the single biggest footgun in stdio MCP: &lt;strong&gt;stdout carries the JSON-RPC messages&lt;/strong&gt;. One stray &lt;code&gt;Console.WriteLine&lt;/code&gt; to stdout and Claude sees line noise where it expected JSON — the protocol equivalent of talking with your mouth full. Route every log to stderr and you're fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Plugging it into Claude
&lt;/h2&gt;

&lt;p&gt;A server nobody connects to is just a very lonely console app. Let's wire it into two clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Desktop&lt;/strong&gt; reads a JSON config file (&lt;code&gt;claude_desktop_config.json&lt;/code&gt;, in &lt;code&gt;%APPDATA%\Claude\&lt;/code&gt; on Windows or &lt;code&gt;~/Library/Application Support/Claude/&lt;/code&gt; on macOS). Add your server under &lt;code&gt;mcpServers&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;"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;"aurora-coffee"&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;"dotnet"&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;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:/src/AuroraCoffee.Mcp"&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;The client runs that command, speaks stdio to the child process, and your tools show up. Restart Claude Desktop and ask &lt;em&gt;"Is order A-1001 shipped, and do you still have ETH-250 in stock?"&lt;/em&gt; — it'll call both tools and answer for real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; is even shorter — one CLI command, no file editing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add aurora-coffee &lt;span class="nt"&gt;--&lt;/span&gt; dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; ./AuroraCoffee.Mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same server, second client, zero code changes. &lt;em&gt;That's the entire point of MCP&lt;/em&gt;: the tools didn't move, but now two different Claudes can reach them — and so can the third one you haven't installed yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test it without a client
&lt;/h3&gt;

&lt;p&gt;Before you touch any client config, sanity-check the server with the &lt;strong&gt;MCP Inspector&lt;/strong&gt;, a browser UI that speaks the protocol so you can list and invoke tools by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; ./AuroraCoffee.Mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It launches your server, lists &lt;code&gt;get_order_status&lt;/code&gt; and &lt;code&gt;check_stock&lt;/code&gt;, and lets you fire test calls. If a tool misbehaves, you find out here — not three layers deep inside a chat transcript.&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%2F5v2pdep7720mh3v0cgnz.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%2F5v2pdep7720mh3v0cgnz.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Reaching the outside world: DI and HttpClient
&lt;/h2&gt;

&lt;p&gt;In-memory dictionaries are fine for a demo, but real tools call real systems. Because the server is a normal .NET host, dependency injection works exactly as you'd expect — including typed &lt;code&gt;HttpClient&lt;/code&gt;s. Suppose Aurora has a roastery API for tasting notes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerToolType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoastTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetch today's roast notes for a coffee origin."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetRoastNotes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Origin name, e.g. Ethiopia"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"https://api.auroracoffee.example/roast-notes/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EscapeDataString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it as a typed client and you're done — the SDK injects the &lt;code&gt;HttpClient&lt;/code&gt; into the tool's constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RoastTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth calling out. First, tools can be &lt;code&gt;async&lt;/code&gt; and return &lt;code&gt;Task&amp;lt;string&amp;gt;&lt;/code&gt; — no ceremony, just &lt;code&gt;await&lt;/code&gt;. Second, use &lt;code&gt;AddHttpClient&amp;lt;T&amp;gt;&lt;/code&gt; (or &lt;code&gt;IHttpClientFactory&lt;/code&gt;) rather than newing up an &lt;code&gt;HttpClient&lt;/code&gt; per call; the factory pools connections so you don't exhaust sockets under load. Standard .NET hygiene applies — MCP doesn't change any of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going remote: the HTTP transport
&lt;/h2&gt;

&lt;p&gt;stdio is great when the client can launch your server locally. But if you want &lt;em&gt;one&lt;/em&gt; server shared across a team, or running in a container, you want HTTP. Swap the transport package and two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ModelContextProtocol.AspNetCore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CoffeeShopData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithToolsFromAssembly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same &lt;code&gt;CoffeeTools&lt;/code&gt;, same attributes, same discovery — only the transport and host changed. Now clients connect by URL instead of launching a process, and you get everything ASP.NET Core already gives you: auth middleware, HTTPS, hosting, scaling. Your tools didn't even notice.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to build an MCP server — and when not to
&lt;/h2&gt;

&lt;p&gt;Reach for MCP when tools need to be &lt;strong&gt;shared or reused&lt;/strong&gt;: the same capability across Claude Desktop &lt;em&gt;and&lt;/em&gt; Claude Code, a server your whole team points at, or tools you want decoupled from any single app and free to evolve on their own release cadence. The process boundary is a feature — it's what lets one server serve many clients, in any language.&lt;/p&gt;

&lt;p&gt;Skip it when exactly one app will ever use the tools and it's the app doing the model calls. Then the in-app tool-use loop from the previous article is simpler and faster — no transport, no second process. A server whose only client is itself is just a function call wearing a lanyard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb: in-app tool use when one app owns the model and the tools; an MCP server the moment more than one client needs the same tools.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP decouples tools from clients&lt;/strong&gt; — write a tool once as a server and every MCP client (Claude Desktop, Claude Code, your own agent) can use it, instead of re-implementing it in each app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The SDK is attribute-driven&lt;/strong&gt; — &lt;code&gt;[McpServerToolType]&lt;/code&gt; on the class, &lt;code&gt;[McpServerTool]&lt;/code&gt; on the method, and the JSON Schema is generated from your signature. No hand-written schema, no manual argument parsing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descriptions are still load-bearing&lt;/strong&gt; — &lt;code&gt;[Description]&lt;/code&gt; on the method and each parameter is how Claude decides when to call a tool and what to pass. Write them like docstrings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard stdout&lt;/strong&gt; — in stdio mode the protocol &lt;em&gt;is&lt;/em&gt; stdout. Send logs to stderr or you'll corrupt the stream with your own debug output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same tools, swap the transport&lt;/strong&gt; — stdio for local child-process tools, HTTP (&lt;code&gt;ModelContextProtocol.AspNetCore&lt;/code&gt;) for shared or remote ones. The tool code doesn't change.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Build a Claude Tool-Use Agent in C#: Not a Chatbot on Steroids</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 24 Jul 2026 20:09:23 +0000</pubDate>
      <link>https://dev.to/jgomezdev/build-a-claude-tool-use-agent-in-c-not-a-chatbot-on-steroids-3e9g</link>
      <guid>https://dev.to/jgomezdev/build-a-claude-tool-use-agent-in-c-not-a-chatbot-on-steroids-3e9g</guid>
      <description>&lt;h1&gt;
  
  
  Build a Claude Tool-Use Agent in C#: Not a Chatbot on Steroids
&lt;/h1&gt;

&lt;p&gt;Everyone ships "AI agents" now. Peek under the hood of most of them and you'll find a chatbot with a personality prompt and a hopeful &lt;code&gt;// TODO: make it do things&lt;/code&gt;. It can talk about your orders. It cannot actually &lt;em&gt;look one up&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A real agent closes that gap. You hand the model a set of tools — plain methods in your codebase — and it decides, mid-conversation, to call one. Your code runs the real work, hands back the result, and the model keeps going until it can answer for real. That loop is the whole trick, and you can build it in C# in about sixty lines. We'll do it twice: once by hand with &lt;code&gt;HttpClient&lt;/code&gt; so you can see the wire protocol, and once with the official Anthropic .NET SDK for the short version you'd actually ship.&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%2Fkg96bbcxuezbnc60vv0j.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%2Fkg96bbcxuezbnc60vv0j.jpg" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What "tool use" actually is
&lt;/h2&gt;

&lt;p&gt;Tool use (a.k.a. function calling) is a small, strict loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You declare tools.&lt;/strong&gt; Each is a &lt;code&gt;name&lt;/code&gt;, a &lt;code&gt;description&lt;/code&gt;, and a JSON Schema for its inputs. That's it — no code is sent to Claude, just the shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude replies with a &lt;code&gt;tool_use&lt;/code&gt; block&lt;/strong&gt; instead of text. It's saying "please run &lt;code&gt;check_stock&lt;/code&gt; with &lt;code&gt;{ "sku": "ETH-250" }&lt;/code&gt; and tell me what you get."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your code runs it.&lt;/strong&gt; This is the part people miss: Claude never executes anything. It &lt;em&gt;requests&lt;/em&gt;; your harness &lt;em&gt;executes&lt;/em&gt;. The security boundary stays on your side of the fence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You send the answer back&lt;/strong&gt; as a &lt;code&gt;tool_result&lt;/code&gt; block, tied to the request by id.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude continues&lt;/strong&gt; — maybe calling more tools, maybe answering. You loop until it stops asking.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of Claude as a very fast senior colleague who can read your API but isn't allowed to touch production: it tells you exactly which button to press, you press it, you report back. The judgment is the model's; the hands are yours.&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%2F3lb1ivydc0thl5ler5dd.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%2F3lb1ivydc0thl5ler5dd.jpg" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The agent we're building
&lt;/h2&gt;

&lt;p&gt;Meet &lt;strong&gt;Aurora Coffee Co.&lt;/strong&gt;, a fictional shop with a support agent that answers questions like &lt;em&gt;"Is my order shipped, and do you still have the Ethiopia beans?"&lt;/em&gt; It has two tools over some in-memory data, so the whole thing runs with zero external setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_order_status(order_id)&lt;/code&gt; — looks up an order.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_stock(sku)&lt;/code&gt; — checks inventory for a product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the shared part — the data and the tool executor. Both approaches below reuse this exact method; only the Claude plumbing changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Aurora Coffee Co.'s "database" — in memory, so this runs with zero setup.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-07-26"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-07-29"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ETH-250"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"COL-1KG"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Your tools are ordinary C#. Claude never runs this — it only asks you to.&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"get_order_status"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()!,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"Order &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, ETA &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"No order found with that id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"check_stock"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()!,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"SKU &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; units in stock."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Unknown SKU."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"Unknown tool: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;RunTool&lt;/code&gt; takes the tool inputs as a dictionary of &lt;code&gt;JsonElement&lt;/code&gt;. We &lt;strong&gt;parse&lt;/strong&gt; them — we never string-match the raw JSON Claude sends. That matters, and we'll come back to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach 1 — Raw HttpClient (see the protocol)
&lt;/h2&gt;

&lt;p&gt;No SDK, no dependencies — just &lt;code&gt;HttpClient&lt;/code&gt; and &lt;code&gt;System.Net.Http.Json&lt;/code&gt;. This is the version that teaches you what's really going on the wire.&lt;/p&gt;

&lt;p&gt;First, describe the tools as JSON Schema. This is the exact array that ships in the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Look up the status and ETA of a customer order by its id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;input_schema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Order id, e.g. A-1001"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"order_id"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"check_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Check how many units of a product SKU are currently in stock."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;input_schema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Product SKU, e.g. ETH-250"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"sku"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;description&lt;/code&gt; fields are load-bearing — they're how Claude decides &lt;em&gt;when&lt;/em&gt; to reach for each tool. Write them like you'd write a docstring for a junior dev, not like a variable name.&lt;/p&gt;

&lt;p&gt;Now the client and the agent loop. We keep a growing list of messages and keep calling until Claude stops asking for tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.anthropic.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ANTHROPIC_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"anthropic-version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2023-06-01"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JsonSerializerOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The conversation grows as we go. Start with the user's question.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Is order A-1001 shipped, and do you still have ETH-250 in stock?"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude-opus-4-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/messages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stopReason&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stop_reason"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Echo Claude's whole turn back — tool_use blocks included — or the next&lt;/span&gt;
    &lt;span class="c1"&gt;// request won't line up with the tool_result we're about to send.&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopReason&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnumerateArray&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Claude asked for one or more tools. Run them all; collect every result.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnumerateArray&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()!;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;

        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tool_result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tool_use_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolInput&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// All the tool_results go back in ONE user message.&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details that will bite you if you skip them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Echo the assistant turn back verbatim&lt;/strong&gt;, tool_use blocks and all. The follow-up request must contain the original &lt;code&gt;tool_use&lt;/code&gt; for every &lt;code&gt;tool_result&lt;/code&gt;, matched by &lt;code&gt;tool_use_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return every result in a single user message.&lt;/strong&gt; If Claude asks for two tools and you split the answers across two messages, it quietly learns to stop asking for tools in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name your loop variable something other than &lt;code&gt;args&lt;/code&gt;.&lt;/strong&gt; Top-level statements already define an implicit &lt;code&gt;args&lt;/code&gt;, and the compiler will not be shy about it. Ask me how I know.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a complete agent in one file — and you can see every byte that crosses the wire. Hand-rolling the JSON gets old fast, though, which is exactly why the SDK exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach 2 — The Anthropic .NET SDK (the short version)
&lt;/h2&gt;

&lt;p&gt;Same agent, far less plumbing. Add the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Anthropic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tool definitions become typed objects. &lt;code&gt;InputSchema.Type&lt;/code&gt; is set to &lt;code&gt;"object"&lt;/code&gt; for you — don't set it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.Models.Messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;AnthropicClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// reads ANTHROPIC_API_KEY&lt;/span&gt;

&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ToolUnion&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Look up the status and ETA of a customer order by its id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InputSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Order id, e.g. A-1001"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"check_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Check how many units of a product SKU are currently in stock."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InputSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Product SKU, e.g. ETH-250"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop is the same idea, now with typed blocks. One quirk worth knowing: the SDK gives you &lt;em&gt;response&lt;/em&gt; blocks (&lt;code&gt;TextBlock&lt;/code&gt;, &lt;code&gt;ToolUseBlock&lt;/code&gt;) but the request wants &lt;em&gt;param&lt;/em&gt; blocks (&lt;code&gt;TextBlockParam&lt;/code&gt;, &lt;code&gt;ToolUseBlockParam&lt;/code&gt;), and there's no &lt;code&gt;.ToParam()&lt;/code&gt; shortcut — you rebuild them. It's a few lines, and it keeps the type system honest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MessageParam&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Is order A-1001 shipped, and do you still have ETH-250 in stock?"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MessageCreateParams&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClaudeOpus4_8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContentBlockParam&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContentBlockParam&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPickText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TextBlockParam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPickToolUse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ToolUseBlock&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ToolUseBlockParam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ToolResultBlockParam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ToolUseID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Assistant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopReason&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;OfType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;RunTool&lt;/code&gt; didn't change at all — &lt;code&gt;ToolUseBlock.Input&lt;/code&gt; is already an &lt;code&gt;IReadOnlyDictionary&amp;lt;string, JsonElement&amp;gt;&lt;/code&gt;, so our executor drops right in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let the SDK drive the loop
&lt;/h3&gt;

&lt;p&gt;If you don't want to write the loop yourself, the SDK ships a beta &lt;strong&gt;tool runner&lt;/strong&gt;. You give each tool its schema &lt;em&gt;and&lt;/em&gt; the code that runs it (a &lt;code&gt;Run&lt;/code&gt; callback), and the runner handles the whole call-execute-continue cycle for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.Helpers.Beta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.Models.Beta.Messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Bundle each tool's schema with the code that answers it.&lt;/span&gt;
&lt;span class="n"&gt;BetaRunnableTool&lt;/span&gt; &lt;span class="nf"&gt;MakeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BetaTool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InputSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BetaToolResultBlockParamContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IBetaRunnableTool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;runnable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;MakeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Look up the status and ETA of an order by id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"e.g. A-1001"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;MakeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"check_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Check units in stock for a product SKU."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"e.g. ETH-250"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToolRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MessageCreateParams&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude-opus-4-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Is A-1001 shipped, and is ETH-250 in stock?"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;runnable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BetaMessage&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPickText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No manual loop, no block reconstruction — the runner calls your &lt;code&gt;Run&lt;/code&gt; callbacks and keeps the conversation going until Claude is done. Whichever version you run, the output is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Is order A-1001 shipped, and do you still have ETH-250 in stock?
Order A-1001 has shipped and is on track to arrive 2026-07-26. And yes — the
Ethiopia beans (ETH-250) are in stock, with 42 units available.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two tools, one question, and Claude figured out on its own that it needed to call &lt;em&gt;both&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use tool use — and when not to
&lt;/h2&gt;

&lt;p&gt;Tool use is the right call when the model needs something it can't have from text alone: &lt;strong&gt;live or private data&lt;/strong&gt; (your database, an internal API, the filesystem) or the ability to &lt;strong&gt;take an action&lt;/strong&gt; (send an email, create a ticket, refund an order). If the answer is genuinely locked inside your systems, tool use is how the model gets to it.&lt;/p&gt;

&lt;p&gt;It's the wrong call when a single prompt would do. If you just need JSON out of a blob of text, reach for structured outputs, not a tool loop — you'll skip the extra round trips. Not every problem is an agent; some are just a well-shaped prompt.&lt;/p&gt;

&lt;p&gt;Between the two implementations: go &lt;strong&gt;raw &lt;code&gt;HttpClient&lt;/code&gt;&lt;/strong&gt; when you're learning the protocol, want zero dependencies, or need total control over the loop — approval gates before a tool fires, custom logging, human-in-the-loop review. Go &lt;strong&gt;SDK&lt;/strong&gt; for everything you'd actually ship: typed models, less boilerplate, and the tool runner when you just want the loop handled. And if cost matters more than raw capability, swap &lt;code&gt;claude-opus-4-8&lt;/code&gt; for &lt;code&gt;claude-sonnet-5&lt;/code&gt; — same code, cheaper runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb: prompt first, tool use when the model needs to reach into your world, an agent loop only when it needs to reach in more than once.&lt;/strong&gt;&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%2F2mzucegnr64atct4mew2.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%2F2mzucegnr64atct4mew2.jpg" alt=" " width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The loop is the whole trick, not magic&lt;/strong&gt; — declare tools, Claude requests one, you run it, you return the result, repeat until it answers. Everything else is plumbing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You own execution, and that's the point&lt;/strong&gt; — Claude only &lt;em&gt;asks&lt;/em&gt; to run a tool; your code decides whether and how. The security boundary never leaves your side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse tool inputs, never string-match them&lt;/strong&gt; — read the JSON into typed values (&lt;code&gt;JsonElement&lt;/code&gt; / a dictionary). Matching against the raw serialized string is a bug waiting for a Unicode escape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native teaches the protocol, the SDK ships it&lt;/strong&gt; — &lt;code&gt;HttpClient&lt;/code&gt; shows every byte and gives you full control; the Anthropic SDK gives you typed blocks and a tool runner that drives the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start simple&lt;/strong&gt; — a plain prompt beats an agent for most tasks. Add tool use when the model needs live data or has to &lt;em&gt;do&lt;/em&gt; something, and an agent loop only when one tool call isn't enough.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Crea un agente con Tool Use de Claude en C#: no es un chatbot con esteroides</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 24 Jul 2026 20:09:21 +0000</pubDate>
      <link>https://dev.to/jgomezdev/crea-un-agente-con-tool-use-de-claude-en-c-no-es-un-chatbot-con-esteroides-fjh</link>
      <guid>https://dev.to/jgomezdev/crea-un-agente-con-tool-use-de-claude-en-c-no-es-un-chatbot-con-esteroides-fjh</guid>
      <description>&lt;h1&gt;
  
  
  Crea un agente con Tool Use de Claude en C#: no es un chatbot con esteroides
&lt;/h1&gt;

&lt;p&gt;Hoy todo el mundo lanza "agentes de IA". Levanta el capó de la mayoría y encontrarás un chatbot con un prompt de personalidad y un esperanzado &lt;code&gt;// TODO: que haga algo&lt;/code&gt;. Puede &lt;em&gt;hablar&lt;/em&gt; de tus pedidos. Lo que no puede es &lt;em&gt;consultar uno de verdad&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Un agente real cierra esa brecha. Le das al modelo un conjunto de herramientas —métodos normales de tu código— y él decide, a mitad de la conversación, llamar a una. Tu código ejecuta el trabajo real, le devuelve el resultado, y el modelo sigue hasta que puede responder de verdad. Ese bucle es todo el truco, y lo puedes montar en C# en unas sesenta líneas. Lo haremos dos veces: primero a mano con &lt;code&gt;HttpClient&lt;/code&gt; para que veas el protocolo tal cual viaja, y luego con el SDK oficial de Anthropic para .NET, que es la versión corta que de verdad pondrías en producción.&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%2Fulpl37d59nglweeb50p7.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%2Fulpl37d59nglweeb50p7.jpg" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué es realmente "tool use"
&lt;/h2&gt;

&lt;p&gt;Tool use (o &lt;em&gt;function calling&lt;/em&gt;) es un bucle pequeño y estricto:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Declaras herramientas.&lt;/strong&gt; Cada una es un &lt;code&gt;name&lt;/code&gt;, una &lt;code&gt;description&lt;/code&gt; y un JSON Schema para sus parámetros. Nada más — no le envías código a Claude, solo la forma.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude responde con un bloque &lt;code&gt;tool_use&lt;/code&gt;&lt;/strong&gt; en lugar de texto. Te está diciendo: "por favor ejecuta &lt;code&gt;check_stock&lt;/code&gt; con &lt;code&gt;{ "sku": "ETH-250" }&lt;/code&gt; y dime qué obtienes".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tu código lo ejecuta.&lt;/strong&gt; Aquí está la parte que se le escapa a mucha gente: Claude nunca ejecuta nada. Él &lt;em&gt;pide&lt;/em&gt;; tu &lt;em&gt;harness&lt;/em&gt; ejecuta. La frontera de seguridad se queda de tu lado de la valla.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Le devuelves la respuesta&lt;/strong&gt; como un bloque &lt;code&gt;tool_result&lt;/code&gt;, atado a la petición por su id.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude continúa&lt;/strong&gt; — quizá llamando a más herramientas, quizá respondiendo. Repites el bucle hasta que deja de pedir.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Piensa en Claude como un colega senior muy rápido que puede leer tu API pero no tiene permiso para tocar producción: te dice exactamente qué botón pulsar, tú lo pulsas y le reportas. El criterio es del modelo; las manos son tuyas.&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%2Fea2w6dnorewy4r21yyvs.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%2Fea2w6dnorewy4r21yyvs.jpg" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  El agente que vamos a construir
&lt;/h2&gt;

&lt;p&gt;Te presento a &lt;strong&gt;Aurora Coffee Co.&lt;/strong&gt;, una tienda ficticia con un agente de soporte que responde preguntas como &lt;em&gt;"¿Ya salió mi pedido y todavía tienen los granos de Etiopía?"&lt;/em&gt;. Tiene dos herramientas sobre datos en memoria, así que todo corre sin ninguna configuración externa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_order_status(order_id)&lt;/code&gt; — consulta un pedido.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_stock(sku)&lt;/code&gt; — revisa el inventario de un producto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esta es la parte compartida: los datos y el ejecutor de herramientas. Los dos enfoques de abajo reutilizan este mismo método; lo único que cambia es la fontanería de Claude.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// La "base de datos" de Aurora Coffee Co. — en memoria, para que corra sin setup.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-07-26"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A-1002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2026-07-29"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ETH-250"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"COL-1KG"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Tus herramientas son C# normal. Claude nunca ejecuta esto — solo te pide que lo hagas.&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"get_order_status"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()!,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"Pedido &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, ETA &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"No hay ningún pedido con ese id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"check_stock"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()!,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"SKU &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unidades en stock."&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SKU desconocido."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"Herramienta desconocida: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Eta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fíjate en que &lt;code&gt;RunTool&lt;/code&gt; recibe los parámetros como un diccionario de &lt;code&gt;JsonElement&lt;/code&gt;. Los &lt;strong&gt;parseamos&lt;/strong&gt; — nunca comparamos como texto el JSON crudo que manda Claude. Eso importa, y volveremos a ello.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enfoque 1 — HttpClient a pelo (ver el protocolo)
&lt;/h2&gt;

&lt;p&gt;Sin SDK, sin dependencias — solo &lt;code&gt;HttpClient&lt;/code&gt; y &lt;code&gt;System.Net.Http.Json&lt;/code&gt;. Esta es la versión que te enseña qué está pasando de verdad en el cable.&lt;/p&gt;

&lt;p&gt;Primero, describe las herramientas como JSON Schema. Este es el array exacto que viaja en la petición:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Consulta el estado y la ETA de un pedido por su id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;input_schema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Id del pedido, p. ej. A-1001"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"order_id"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"check_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Consulta cuántas unidades de un SKU hay en stock ahora mismo."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;input_schema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SKU del producto, p. ej. ETH-250"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"sku"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los campos &lt;code&gt;description&lt;/code&gt; cargan con todo el peso — son la forma en que Claude decide &lt;em&gt;cuándo&lt;/em&gt; echar mano de cada herramienta. Escríbelos como escribirías un docstring para alguien junior, no como el nombre de una variable.&lt;/p&gt;

&lt;p&gt;Ahora el cliente y el bucle del agente. Mantenemos una lista de mensajes que va creciendo y seguimos llamando hasta que Claude deja de pedir herramientas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.anthropic.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ANTHROPIC_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"anthropic-version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2023-06-01"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JsonSerializerOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// La conversación crece a medida que avanzamos. Empezamos con la pregunta del usuario.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"¿El pedido A-1001 ya salió, y todavía tienen ETH-250 en stock?"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude-opus-4-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/messages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stopReason&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stop_reason"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Devuelve el turno completo de Claude — con sus bloques tool_use incluidos — o la&lt;/span&gt;
    &lt;span class="c1"&gt;// siguiente petición no cuadrará con el tool_result que estamos a punto de enviar.&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopReason&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnumerateArray&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Claude pidió una o más herramientas. Ejecútalas todas; recoge cada resultado.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnumerateArray&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()!;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;

        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tool_result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tool_use_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolInput&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Todos los tool_results vuelven en UN SOLO mensaje de usuario.&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tres detalles que te van a morder si los saltas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Devuelve el turno del asistente tal cual&lt;/strong&gt;, con bloques tool_use y todo. La petición de continuación debe contener el &lt;code&gt;tool_use&lt;/code&gt; original para cada &lt;code&gt;tool_result&lt;/code&gt;, emparejados por &lt;code&gt;tool_use_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Devuelve todos los resultados en un único mensaje de usuario.&lt;/strong&gt; Si Claude pide dos herramientas y repartes las respuestas en dos mensajes, aprende calladito a dejar de pedir herramientas en paralelo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No llames &lt;code&gt;args&lt;/code&gt; a tu variable del bucle.&lt;/strong&gt; Los &lt;em&gt;top-level statements&lt;/em&gt; ya definen un &lt;code&gt;args&lt;/code&gt; implícito, y el compilador no se va a andar con timideces. No preguntes cómo lo sé.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eso es un agente completo en un solo archivo — y puedes ver cada byte que cruza el cable. Escribir el JSON a mano cansa rápido, claro, que es justo para lo que existe el SDK.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enfoque 2 — El SDK de Anthropic para .NET (la versión corta)
&lt;/h2&gt;

&lt;p&gt;El mismo agente, con muchísima menos fontanería. Añade el paquete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Anthropic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Las definiciones de herramientas pasan a ser objetos tipados. &lt;code&gt;InputSchema.Type&lt;/code&gt; se pone en &lt;code&gt;"object"&lt;/code&gt; por ti — no lo asignes a mano:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.Models.Messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;AnthropicClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// lee ANTHROPIC_API_KEY&lt;/span&gt;

&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ToolUnion&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Consulta el estado y la ETA de un pedido por su id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InputSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Id del pedido, p. ej. A-1001"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"check_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Consulta cuántas unidades de un SKU hay en stock ahora mismo."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InputSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SKU del producto, p. ej. ETH-250"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El bucle es la misma idea, ahora con bloques tipados. Un detalle que conviene saber: el SDK te da bloques de &lt;em&gt;respuesta&lt;/em&gt; (&lt;code&gt;TextBlock&lt;/code&gt;, &lt;code&gt;ToolUseBlock&lt;/code&gt;) pero la petición quiere bloques de &lt;em&gt;parámetro&lt;/em&gt; (&lt;code&gt;TextBlockParam&lt;/code&gt;, &lt;code&gt;ToolUseBlockParam&lt;/code&gt;), y no hay atajo &lt;code&gt;.ToParam()&lt;/code&gt; — los reconstruyes. Son unas pocas líneas, y mantienen honesto al sistema de tipos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MessageParam&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"¿El pedido A-1001 ya salió, y todavía tienen ETH-250 en stock?"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MessageCreateParams&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClaudeOpus4_8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContentBlockParam&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContentBlockParam&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPickText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TextBlockParam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPickToolUse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ToolUseBlock&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ToolUseBlockParam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ToolResultBlockParam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ToolUseID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Assistant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopReason&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;OfType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fíjate en que &lt;code&gt;RunTool&lt;/code&gt; no cambió en absoluto — &lt;code&gt;ToolUseBlock.Input&lt;/code&gt; ya es un &lt;code&gt;IReadOnlyDictionary&amp;lt;string, JsonElement&amp;gt;&lt;/code&gt;, así que nuestro ejecutor encaja tal cual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deja que el SDK maneje el bucle
&lt;/h3&gt;

&lt;p&gt;Si no quieres escribir el bucle tú, el SDK trae un &lt;strong&gt;tool runner&lt;/strong&gt; (en beta). A cada herramienta le das su esquema &lt;em&gt;y&lt;/em&gt; el código que la ejecuta (un callback &lt;code&gt;Run&lt;/code&gt;), y el runner se encarga de todo el ciclo llamar-ejecutar-continuar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.Helpers.Beta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.Models.Beta.Messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Empaqueta el esquema de cada herramienta junto con el código que la responde.&lt;/span&gt;
&lt;span class="n"&gt;BetaRunnableTool&lt;/span&gt; &lt;span class="nf"&gt;MakeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BetaTool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InputSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BetaToolResultBlockParamContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nf"&gt;RunTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IBetaRunnableTool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;runnable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;MakeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get_order_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Consulta el estado y la ETA de un pedido por id."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"p. ej. A-1001"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;MakeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"check_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Consulta las unidades en stock de un SKU."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"p. ej. ETH-250"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToolRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MessageCreateParams&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude-opus-4-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"¿A-1001 ya salió, y ETH-250 está en stock?"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;runnable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BetaMessage&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPickText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin bucle manual, sin reconstruir bloques — el runner llama a tus callbacks &lt;code&gt;Run&lt;/code&gt; y mantiene la conversación viva hasta que Claude termina. Corras la versión que corras, la salida es la misma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ¿El pedido A-1001 ya salió, y todavía tienen ETH-250 en stock?
El pedido A-1001 ya salió y está previsto que llegue el 2026-07-26. Y sí — los
granos de Etiopía (ETH-250) están en stock, con 42 unidades disponibles.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dos herramientas, una pregunta, y Claude dedujo por su cuenta que necesitaba llamar a &lt;em&gt;ambas&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo usar tool use — y cuándo no
&lt;/h2&gt;

&lt;p&gt;Tool use es la opción correcta cuando el modelo necesita algo que no puede sacar solo del texto: &lt;strong&gt;datos en vivo o privados&lt;/strong&gt; (tu base de datos, una API interna, el sistema de archivos) o la capacidad de &lt;strong&gt;realizar una acción&lt;/strong&gt; (mandar un correo, crear un ticket, reembolsar un pedido). Si la respuesta está de verdad encerrada dentro de tus sistemas, tool use es cómo el modelo llega a ella.&lt;/p&gt;

&lt;p&gt;Es la opción equivocada cuando bastaría con un solo prompt. Si solo necesitas JSON a partir de un texto, tira de &lt;em&gt;structured outputs&lt;/em&gt;, no de un bucle de herramientas — te ahorras los viajes de ida y vuelta. No todo problema es un agente; algunos son solo un prompt bien formado.&lt;/p&gt;

&lt;p&gt;Entre las dos implementaciones: ve con &lt;strong&gt;&lt;code&gt;HttpClient&lt;/code&gt; a pelo&lt;/strong&gt; cuando estés aprendiendo el protocolo, quieras cero dependencias o necesites control total del bucle — puertas de aprobación antes de que dispare una herramienta, logging a medida, revisión con humano en el circuito. Ve con &lt;strong&gt;el SDK&lt;/strong&gt; para todo lo que de verdad pondrías en producción: modelos tipados, menos boilerplate y el tool runner cuando solo quieres que se ocupe del bucle. Y si te pesa más el coste que la capacidad bruta, cambia &lt;code&gt;claude-opus-4-8&lt;/code&gt; por &lt;code&gt;claude-sonnet-5&lt;/code&gt; — mismo código, ejecuciones más baratas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regla de oro: primero el prompt, tool use cuando el modelo necesite meter la mano en tu mundo, y un bucle de agente solo cuando necesite meterla más de una vez.&lt;/strong&gt;&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%2Fams96k8qghesrp47b7tp.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%2Fams96k8qghesrp47b7tp.jpg" alt=" " width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ideas clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;El bucle es todo el truco, no hay magia&lt;/strong&gt; — declaras herramientas, Claude pide una, tú la ejecutas, devuelves el resultado, repites hasta que responde. Todo lo demás es fontanería.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La ejecución es tuya, y ese es el punto&lt;/strong&gt; — Claude solo &lt;em&gt;pide&lt;/em&gt; ejecutar una herramienta; tu código decide si lo hace y cómo. La frontera de seguridad nunca sale de tu lado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parsea los parámetros, no los compares como texto&lt;/strong&gt; — lee el JSON a valores tipados (&lt;code&gt;JsonElement&lt;/code&gt; / un diccionario). Comparar contra la cadena serializada cruda es un bug esperando a un escape Unicode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El nativo enseña el protocolo, el SDK lo pone en producción&lt;/strong&gt; — &lt;code&gt;HttpClient&lt;/code&gt; muestra cada byte y te da control total; el SDK de Anthropic te da bloques tipados y un tool runner que maneja el bucle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empieza simple&lt;/strong&gt; — un prompt normal le gana a un agente en la mayoría de tareas. Añade tool use cuando el modelo necesite datos en vivo o tenga que &lt;em&gt;hacer&lt;/em&gt; algo, y un bucle de agente solo cuando una llamada no baste.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Stop Squinting at .NET Stack Traces: Debug Them With Claude Inside Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:35:22 +0000</pubDate>
      <link>https://dev.to/jgomezdev/stop-squinting-at-net-stack-traces-debug-them-with-claude-inside-visual-studio-3gc5</link>
      <guid>https://dev.to/jgomezdev/stop-squinting-at-net-stack-traces-debug-them-with-claude-inside-visual-studio-3gc5</guid>
      <description>&lt;h1&gt;
  
  
  Stop Squinting at .NET Stack Traces: Debug Them With Claude Inside Visual Studio
&lt;/h1&gt;

&lt;p&gt;An unhandled exception fires, and Visual Studio drops forty lines of stack trace into your lap. Thirty-eight of them live in &lt;code&gt;Microsoft.EntityFrameworkCore.*&lt;/code&gt; or &lt;code&gt;System.Private.CoreLib&lt;/code&gt;, one is an async state-machine frame with a name like &lt;code&gt;&amp;lt;&amp;lt;Main&amp;gt;$&amp;gt;b__0_0&lt;/code&gt;, and exactly one points at code you wrote. Your job is to find that one line, understand &lt;em&gt;why&lt;/em&gt; it blew up, and fix the actual cause — not the symptom.&lt;/p&gt;

&lt;p&gt;We've all pasted a trace into a search engine and gotten back a single Stack Overflow question from 2015, zero answers, one comment that just says &lt;em&gt;"did you ever solve this?"&lt;/em&gt;. There's a better way, and it now lives right inside Visual Studio: hand the trace to Claude, get back a ranked diagnosis tied to &lt;em&gt;your&lt;/em&gt; line, and a fix you can actually reason about.&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%2Faa9e1z9p0zpdoklahs8y.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%2Faa9e1z9p0zpdoklahs8y.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why .NET Stack Traces Are Hard to Read
&lt;/h2&gt;

&lt;p&gt;Stack traces aren't hard because they're long. They're hard because modern .NET code rarely runs in a straight line, and the trace reflects the machinery, not your intent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Async unwinding.&lt;/strong&gt; &lt;code&gt;await&lt;/code&gt; compiles into a state machine. When an exception crosses an &lt;code&gt;await&lt;/code&gt;, the frames you see are &lt;code&gt;MoveNext()&lt;/code&gt; calls and &lt;code&gt;ExecutionContext.Run&lt;/code&gt;, not the tidy call chain you wrote. The method name shows up as &lt;code&gt;&amp;lt;SomethingAsync&amp;gt;d__4&lt;/code&gt;, and the line that &lt;em&gt;logically&lt;/em&gt; threw might be three awaits up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inner exceptions.&lt;/strong&gt; The message on top is often the &lt;em&gt;least&lt;/em&gt; useful one. The real cause is nested — a &lt;code&gt;DbUpdateException&lt;/code&gt; wrapping a &lt;code&gt;SqliteException&lt;/code&gt;, or a &lt;code&gt;TargetInvocationException&lt;/code&gt; wrapping the thing you actually care about. If you only read the first line, you're debugging the wrapper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AggregateException&lt;/code&gt;.&lt;/strong&gt; Anything involving &lt;code&gt;Task.WhenAll&lt;/code&gt; or parallelism can bundle several failures into one. The outer message says "One or more errors occurred" — thanks, very helpful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inlined and framework frames.&lt;/strong&gt; The JIT inlines small methods, so frames disappear. And the frames that remain are mostly library internals you can't change and don't need to read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An LLM is genuinely good at this specific chore. It has seen these exception shapes thousands of times, it can tell the framework noise from the signal, and — this is the part that matters — when it runs &lt;em&gt;inside your IDE&lt;/em&gt;, it can correlate the trace with the actual source file and line.&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%2F40bpqexhwn4kp6errap0.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%2F40bpqexhwn4kp6errap0.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Ways to Put Claude Inside Visual Studio
&lt;/h2&gt;

&lt;p&gt;You don't need to leave the IDE or paste anything into a browser. There are two solid paths, and they suit different moments.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Copilot Chat with a Claude model (in-IDE, conversational)
&lt;/h3&gt;

&lt;p&gt;Visual Studio 2022 (17.14+) ships GitHub Copilot with a &lt;strong&gt;model picker&lt;/strong&gt;. Click the Copilot badge, open the model dropdown in the chat prompt box, and pick a Claude model — Sonnet for fast everyday diagnosis, Opus when the bug is genuinely nasty. From then on, every Copilot Chat reply is generated by Claude.&lt;/p&gt;

&lt;p&gt;Two things make this path shine for debugging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When an unhandled exception pops the &lt;strong&gt;Exception Helper&lt;/strong&gt; while debugging, there's an inline &lt;strong&gt;Ask Copilot&lt;/strong&gt; link right on that dialog. With Claude selected as your model, that one-click analysis runs on Claude.&lt;/li&gt;
&lt;li&gt;In the chat box you can pull real context in with &lt;code&gt;#&lt;/code&gt; references — &lt;code&gt;#Program.cs&lt;/code&gt;, &lt;code&gt;#solution&lt;/code&gt;, or a highlighted selection — so Claude reads your &lt;em&gt;actual&lt;/em&gt; code, not a paraphrase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd rather bring your own Anthropic key instead of using Copilot's models, the same picker has &lt;strong&gt;Manage Models&lt;/strong&gt; → add Anthropic → paste your API key. Handy if you're already paying for API access.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Claude Code in the integrated terminal (agentic)
&lt;/h3&gt;

&lt;p&gt;There's no official Claude Code extension for the full Visual Studio yet, but you don't need one — Claude Code runs in any terminal, including &lt;strong&gt;View → Terminal&lt;/strong&gt; (&lt;code&gt;Ctrl&lt;/code&gt; + backtick) inside VS. Run &lt;code&gt;claude&lt;/code&gt;, paste the trace, and because Claude Code can read the whole repository, it will open the offending file itself, trace the call path, and propose the fix as a diff you approve or reject.&lt;/p&gt;

&lt;p&gt;The trade-off is simple: Copilot Chat is faster for a quick "what does this mean?", while Claude Code is better when the fix touches several files or you want it to reproduce and verify. Use whichever the bug deserves.&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%2F9jmxtctys4j3vmjhp45r.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%2F9jmxtctys4j3vmjhp45r.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Example: The EF Core Trace That Blames the Wrong Thing
&lt;/h2&gt;

&lt;p&gt;Let's debug something concrete. Here's a .NET 10 minimal API endpoint that returns an order summary. It looks reasonable, and it compiles cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs — .NET 10 minimal API&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShopContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data Source=shop.db"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders/{customerId:int}/summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ShopContext&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fire both queries "in parallel" to be fast — right?&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SumAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit the endpoint and it throws. Here's the trace Visual Studio shows (trimmed to the frames that appear):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System.InvalidOperationException: A second operation was started on this context
instance before a previous operation completed. This is usually caused by different
threads concurrently using the same instance of DbContext. For more information on
how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
   at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken ct)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken ct)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken ct)
   at Program.&amp;lt;&amp;gt;c.&amp;lt;&amp;lt;Main&amp;gt;$&amp;gt;b__0_0(Int32 customerId, ShopContext db) in C:\src\Shop.Api\Program.cs:line 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four of those five frames are EF Core internals. The one that's yours — &lt;code&gt;Program.cs:line 12&lt;/code&gt; — is the &lt;code&gt;ToListAsync()&lt;/code&gt; call, which makes it &lt;em&gt;look&lt;/em&gt; like &lt;code&gt;ToListAsync&lt;/code&gt; is the villain. It isn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feeding it to Claude the right way
&lt;/h3&gt;

&lt;p&gt;The quality of the answer depends entirely on what you paste. A one-line message gets you a one-line guess. Give Claude the whole picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.NET 10, EF Core 10, SQLite. This endpoint throws on every request under normal load
(not just concurrency). Full exception and the endpoint code below — what's the root
cause, and what's the idiomatic fix?

[paste the FULL exception via ex.ToString() — message + all inner exceptions + trace]

[paste the MapGet handler]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things make that prompt good:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The full exception, not the headline.&lt;/strong&gt; &lt;code&gt;ex.ToString()&lt;/code&gt; includes every inner exception and the complete trace. In Copilot Chat, &lt;code&gt;#Program.cs&lt;/code&gt; pulls the source in for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first frame that's &lt;em&gt;yours&lt;/em&gt;.&lt;/strong&gt; Point Claude at &lt;code&gt;Program.cs:line 12&lt;/code&gt; and the code around it. The framework frames are context; your frame is the crime scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The conditions.&lt;/strong&gt; "Every request" vs "only under load" vs "intermittent" changes the diagnosis completely. Say what you observed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What Claude tells you
&lt;/h3&gt;

&lt;p&gt;Instead of "line 12 failed", you get the actual mechanism: EF Core's &lt;code&gt;DbContext&lt;/code&gt; is &lt;strong&gt;not thread-safe and does not allow two operations on the same instance at once&lt;/strong&gt;. &lt;code&gt;Task.WhenAll&lt;/code&gt; starts the second query before the first finishes, both on the same injected &lt;code&gt;db&lt;/code&gt;, and EF's &lt;code&gt;ConcurrencyDetector&lt;/code&gt; throws. The line number was a red herring — the bug is the &lt;em&gt;pattern&lt;/em&gt;, not the call.&lt;/p&gt;

&lt;p&gt;And it gives you both fixes, with the trade-off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fix A — simplest: run them sequentially. One context, one operation at a time.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SumAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fix B — real parallelism: give each query its OWN context via a factory.&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContextFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShopContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data Source=shop.db"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders/{customerId:int}/summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IDbContextFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShopContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ordersDb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDbContextAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalDb&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDbContextAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ordersDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SumAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix A is what you want 90% of the time — two SQLite queries are fast, and the parallelism was never worth the bug. Fix B is the answer when the queries are genuinely slow and independent, because &lt;code&gt;IDbContextFactory&amp;lt;T&amp;gt;&lt;/code&gt; hands you a fresh, short-lived context per operation. &lt;em&gt;That&lt;/em&gt; distinction — not the line number — is the thing you were actually missing, and it's the kind of judgment a good explanation gives you and a search result doesn't.&lt;/p&gt;

&lt;p&gt;A .NET async stack trace has two kinds of frames: the forty that belong to the runtime, and the one that's your fault. Claude's job is to skip the forty and explain the one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verify — Don't Trust Blindly
&lt;/h2&gt;

&lt;p&gt;Claude is a very fast senior colleague who is occasionally, confidently wrong. Treat its diagnosis as a strong hypothesis, not a verdict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce and confirm.&lt;/strong&gt; Apply the fix, re-run, and check the exception is actually gone — ideally under the same conditions that triggered it. In Visual Studio, set a breakpoint on the fixed line and watch the state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask &lt;em&gt;why&lt;/em&gt;, not just &lt;em&gt;what&lt;/em&gt;.&lt;/strong&gt; "Explain why this fixes it" surfaces a wrong root cause fast. If the reasoning doesn't hold, the fix probably doesn't either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for plausible-but-wrong causes.&lt;/strong&gt; For intermittent bugs especially, Claude may propose a cause that fits the trace but isn't yours. Make it rank candidates and tell you how to distinguish them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The debugger still owns &lt;em&gt;state&lt;/em&gt;. Claude owns &lt;em&gt;meaning&lt;/em&gt;. When you need to know the actual value of a variable at the moment of the crash, that's a watch window and a breakpoint, not a chat prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Reach for Claude — and When Not To
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exception type or message is unfamiliar, or the trace is mostly framework internals.&lt;/li&gt;
&lt;li&gt;The real cause is buried in inner exceptions or an &lt;code&gt;AggregateException&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You're staring at async frames and can't tell which of your lines is the origin.&lt;/li&gt;
&lt;li&gt;You want the &lt;em&gt;idiomatic&lt;/em&gt; fix and the trade-offs, not just a patch that makes the red go away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don't bother when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You already know the cause — a &lt;code&gt;NullReferenceException&lt;/code&gt; on a line you just wrote doesn't need an LLM.&lt;/li&gt;
&lt;li&gt;You need the runtime value of something — use the debugger.&lt;/li&gt;
&lt;li&gt;The trace contains &lt;strong&gt;secrets or customer data&lt;/strong&gt;. Connection strings, tokens, and PII live in exception messages and payloads more often than you'd like. Scrub before you paste, especially with a cloud model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb: &lt;strong&gt;if you'd otherwise open a browser to search the exception, ask Claude instead — it's in the IDE, it can read your code, and it won't send you to a dead 2015 forum thread.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The trace shows machinery, not intent&lt;/strong&gt; — async state machines, inner exceptions, and framework frames bury the one line that's yours. Reading traces is pattern recognition, which is exactly where an LLM helps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude lives in Visual Studio two ways&lt;/strong&gt; — pick it in Copilot Chat's model picker for quick, conversational diagnosis; run Claude Code in the integrated terminal when the fix is agentic and spans files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context is the whole game&lt;/strong&gt; — paste the full &lt;code&gt;ex.ToString()&lt;/code&gt;, point at the first frame that's &lt;em&gt;yours&lt;/em&gt;, and describe when it happens. A headline message gets a headline guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The line number is often a red herring&lt;/strong&gt; — the EF Core example throws at &lt;code&gt;ToListAsync&lt;/code&gt;, but the bug is sharing one &lt;code&gt;DbContext&lt;/code&gt; across parallel queries. The value is the explanation, not the location.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify, then trust&lt;/strong&gt; — reproduce the fix, ask &lt;em&gt;why&lt;/em&gt; it works, and never paste secrets. The debugger owns state; Claude owns meaning.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Deja de Entrecerrar los Ojos con los Stack Traces de .NET: Depúralos con Claude en Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:23:04 +0000</pubDate>
      <link>https://dev.to/jgomezdev/deja-de-entrecerrar-los-ojos-con-los-stack-traces-de-net-depuralos-con-claude-en-visual-studio-4cp1</link>
      <guid>https://dev.to/jgomezdev/deja-de-entrecerrar-los-ojos-con-los-stack-traces-de-net-depuralos-con-claude-en-visual-studio-4cp1</guid>
      <description>&lt;h1&gt;
  
  
  Deja de Entrecerrar los Ojos con los Stack Traces de .NET: Depúralos con Claude en Visual Studio
&lt;/h1&gt;

&lt;p&gt;Salta una excepción no controlada y Visual Studio te suelta cuarenta líneas de stack trace en el regazo. Treinta y ocho viven en &lt;code&gt;Microsoft.EntityFrameworkCore.*&lt;/code&gt; o en &lt;code&gt;System.Private.CoreLib&lt;/code&gt;, una es un frame de máquina de estados asíncrona con un nombre tipo &lt;code&gt;&amp;lt;&amp;lt;Main&amp;gt;$&amp;gt;b__0_0&lt;/code&gt;, y exactamente una apunta a código que escribiste tú. Tu trabajo es encontrar esa línea, entender &lt;em&gt;por qué&lt;/em&gt; explotó y arreglar la causa real — no el síntoma.&lt;/p&gt;

&lt;p&gt;Todos hemos pegado un trace en el buscador y recibido a cambio una sola pregunta de Stack Overflow de 2015, cero respuestas, y un único comentario que dice &lt;em&gt;"¿al final lo resolviste?"&lt;/em&gt;. Hay una forma mejor, y ahora vive dentro de Visual Studio: le pasas el trace a Claude y recibes un diagnóstico ordenado por probabilidad, atado a &lt;em&gt;tu&lt;/em&gt; línea, con un arreglo que de verdad puedes razonar.&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%2F38a75kh943zg78g4kwfa.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%2F38a75kh943zg78g4kwfa.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Por Qué Cuesta Tanto Leer un Stack Trace de .NET
&lt;/h2&gt;

&lt;p&gt;Los stack traces no son difíciles por ser largos. Son difíciles porque el código moderno de .NET casi nunca corre en línea recta, y el trace refleja la maquinaria, no tu intención.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Desenrollado asíncrono.&lt;/strong&gt; Un &lt;code&gt;await&lt;/code&gt; compila a una máquina de estados. Cuando una excepción cruza un &lt;code&gt;await&lt;/code&gt;, los frames que ves son llamadas a &lt;code&gt;MoveNext()&lt;/code&gt; y &lt;code&gt;ExecutionContext.Run&lt;/code&gt;, no la cadena de llamadas ordenada que escribiste. El método aparece como &lt;code&gt;&amp;lt;AlgoAsync&amp;gt;d__4&lt;/code&gt;, y la línea que &lt;em&gt;lógicamente&lt;/em&gt; lanzó puede estar tres &lt;code&gt;await&lt;/code&gt; más arriba.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excepciones internas (inner exceptions).&lt;/strong&gt; El mensaje de arriba suele ser el &lt;em&gt;menos&lt;/em&gt; útil. La causa real está anidada — un &lt;code&gt;DbUpdateException&lt;/code&gt; envolviendo un &lt;code&gt;SqliteException&lt;/code&gt;, o un &lt;code&gt;TargetInvocationException&lt;/code&gt; envolviendo lo que de verdad importa. Si solo lees la primera línea, estás depurando el envoltorio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AggregateException&lt;/code&gt;.&lt;/strong&gt; Cualquier cosa con &lt;code&gt;Task.WhenAll&lt;/code&gt; o paralelismo puede empaquetar varios fallos en uno. El mensaje externo dice "Se produjeron uno o más errores" — gracias, utilísimo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frames inlineados y de framework.&lt;/strong&gt; El JIT hace inline de métodos pequeños, así que hay frames que desaparecen. Y los que quedan son en su mayoría internos de librerías que no puedes cambiar ni necesitas leer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un LLM es genuinamente bueno para esta tarea concreta. Ha visto estas formas de excepción miles de veces, sabe separar el ruido del framework de la señal y — esto es lo clave — cuando corre &lt;em&gt;dentro de tu IDE&lt;/em&gt;, puede correlacionar el trace con el archivo y la línea reales.&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%2F2wamllim3vm5mae83p2s.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%2F2wamllim3vm5mae83p2s.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Dos Formas de Meter a Claude Dentro de Visual Studio
&lt;/h2&gt;

&lt;p&gt;No necesitas salir del IDE ni pegar nada en un navegador. Hay dos caminos sólidos, y cada uno sirve para un momento distinto.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Copilot Chat con un modelo Claude (dentro del IDE, conversacional)
&lt;/h3&gt;

&lt;p&gt;Visual Studio 2022 (17.14+) trae GitHub Copilot con un &lt;strong&gt;selector de modelo&lt;/strong&gt;. Haz clic en el badge de Copilot, abre el desplegable de modelos en la caja del chat y elige un modelo Claude — Sonnet para el diagnóstico rápido del día a día, Opus cuando el bug es realmente feo. A partir de ahí, cada respuesta de Copilot Chat la genera Claude.&lt;/p&gt;

&lt;p&gt;Dos cosas hacen que este camino brille para depurar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cuando una excepción no controlada abre el &lt;strong&gt;Asistente de excepciones&lt;/strong&gt; durante el debug, hay un enlace &lt;strong&gt;Preguntar a Copilot&lt;/strong&gt; justo en ese diálogo. Con Claude seleccionado como modelo, ese análisis de un clic corre sobre Claude.&lt;/li&gt;
&lt;li&gt;En la caja del chat puedes traer contexto real con referencias &lt;code&gt;#&lt;/code&gt; — &lt;code&gt;#Program.cs&lt;/code&gt;, &lt;code&gt;#solution&lt;/code&gt;, o una selección resaltada — para que Claude lea tu código &lt;em&gt;de verdad&lt;/em&gt;, no una paráfrasis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si prefieres usar tu propia clave de Anthropic en vez de los modelos de Copilot, el mismo selector tiene &lt;strong&gt;Manage Models&lt;/strong&gt; → agregar Anthropic → pegar tu API key. Cómodo si ya pagas por acceso a la API.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Claude Code en la terminal integrada (agéntico)
&lt;/h3&gt;

&lt;p&gt;Todavía no hay una extensión oficial de Claude Code para el Visual Studio completo, pero no la necesitas — Claude Code corre en cualquier terminal, incluida &lt;strong&gt;Ver → Terminal&lt;/strong&gt; (&lt;code&gt;Ctrl&lt;/code&gt; + backtick) dentro de VS. Ejecuta &lt;code&gt;claude&lt;/code&gt;, pega el trace y, como Claude Code puede leer todo el repositorio, abrirá él mismo el archivo culpable, seguirá la ruta de llamadas y propondrá el arreglo como un diff que apruebas o rechazas.&lt;/p&gt;

&lt;p&gt;El intercambio es simple: Copilot Chat es más rápido para un "¿qué significa esto?", mientras que Claude Code es mejor cuando el arreglo toca varios archivos o quieres que reproduzca y verifique. Usa el que el bug se merezca.&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%2Fc5kruqyb0jxm0k939zf6.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%2Fc5kruqyb0jxm0k939zf6.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Un Ejemplo Real: El Trace de EF Core Que Culpa a Quien No Es
&lt;/h2&gt;

&lt;p&gt;Depuremos algo concreto. Este es un endpoint de minimal API en .NET 10 que devuelve el resumen de pedidos de un cliente. Se ve razonable, y compila sin quejas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs — minimal API en .NET 10&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShopContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data Source=shop.db"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders/{customerId:int}/summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ShopContext&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Disparamos ambas consultas "en paralelo" para ir rápido — ¿verdad?&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SumAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Llamas al endpoint y lanza. Este es el trace que muestra Visual Studio (recortado a los frames que aparecen):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System.InvalidOperationException: A second operation was started on this context
instance before a previous operation completed. This is usually caused by different
threads concurrently using the same instance of DbContext. For more information on
how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
   at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken ct)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken ct)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken ct)
   at Program.&amp;lt;&amp;gt;c.&amp;lt;&amp;lt;Main&amp;gt;$&amp;gt;b__0_0(Int32 customerId, ShopContext db) in C:\src\Shop.Api\Program.cs:line 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cuatro de esos cinco frames son internos de EF Core. El único que es tuyo — &lt;code&gt;Program.cs:line 12&lt;/code&gt; — es la llamada a &lt;code&gt;ToListAsync()&lt;/code&gt;, lo que hace &lt;em&gt;parecer&lt;/em&gt; que &lt;code&gt;ToListAsync&lt;/code&gt; es el villano. No lo es.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo pasárselo a Claude bien
&lt;/h3&gt;

&lt;p&gt;La calidad de la respuesta depende por completo de lo que pegues. Un mensaje de una línea te da una conjetura de una línea. Dale a Claude el panorama completo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.NET 10, EF Core 10, SQLite. Este endpoint lanza en cada request con carga normal
(no solo con concurrencia). Abajo van la excepción completa y el código del endpoint —
¿cuál es la causa raíz y cuál es el arreglo idiomático?

[pega la excepción COMPLETA con ex.ToString() — mensaje + todas las inner exceptions + trace]

[pega el handler del MapGet]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tres cosas hacen que ese prompt sea bueno:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;La excepción completa, no el titular.&lt;/strong&gt; &lt;code&gt;ex.ToString()&lt;/code&gt; incluye cada inner exception y el trace entero. En Copilot Chat, &lt;code&gt;#Program.cs&lt;/code&gt; te trae el código solo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El primer frame que es &lt;em&gt;tuyo&lt;/em&gt;.&lt;/strong&gt; Apunta a Claude a &lt;code&gt;Program.cs:line 12&lt;/code&gt; y al código a su alrededor. Los frames de framework son contexto; tu frame es la escena del crimen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Las condiciones.&lt;/strong&gt; "En cada request" vs "solo bajo carga" vs "intermitente" cambia el diagnóstico por completo. Di lo que observaste.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Lo que Claude te responde
&lt;/h3&gt;

&lt;p&gt;En vez de "la línea 12 falló", obtienes el mecanismo real: el &lt;code&gt;DbContext&lt;/code&gt; de EF Core &lt;strong&gt;no es thread-safe y no permite dos operaciones sobre la misma instancia a la vez&lt;/strong&gt;. &lt;code&gt;Task.WhenAll&lt;/code&gt; arranca la segunda consulta antes de que termine la primera, ambas sobre el mismo &lt;code&gt;db&lt;/code&gt; inyectado, y el &lt;code&gt;ConcurrencyDetector&lt;/code&gt; de EF lanza. El número de línea era una pista falsa — el bug es el &lt;em&gt;patrón&lt;/em&gt;, no la llamada.&lt;/p&gt;

&lt;p&gt;Y te da los dos arreglos, con el intercambio incluido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Arreglo A — el más simple: ejecútalas en secuencia. Un contexto, una operación a la vez.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SumAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Arreglo B — paralelismo real: dale a cada consulta su PROPIO contexto vía factory.&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContextFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShopContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data Source=shop.db"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders/{customerId:int}/summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IDbContextFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShopContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ordersDb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDbContextAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalDb&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDbContextAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ordersDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SumAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ordersTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;totalTask&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El Arreglo A es lo que quieres el 90% de las veces — dos consultas a SQLite son rápidas, y el paralelismo nunca valió el bug. El Arreglo B es la respuesta cuando las consultas son genuinamente lentas e independientes, porque &lt;code&gt;IDbContextFactory&amp;lt;T&amp;gt;&lt;/code&gt; te entrega un contexto fresco y de vida corta por operación. &lt;em&gt;Esa&lt;/em&gt; distinción — no el número de línea — es lo que de verdad te faltaba, y es el tipo de criterio que te da una buena explicación y no un resultado de búsqueda.&lt;/p&gt;

&lt;p&gt;Un stack trace asíncrono de .NET tiene dos tipos de frames: los cuarenta que le pertenecen al runtime, y el que es culpa tuya. El trabajo de Claude es saltarse los cuarenta y explicarte el uno.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifica — No Confíes a Ciegas
&lt;/h2&gt;

&lt;p&gt;Claude es un colega senior muy rápido que de vez en cuando se equivoca con toda seguridad. Trata su diagnóstico como una hipótesis fuerte, no como un veredicto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce y confirma.&lt;/strong&gt; Aplica el arreglo, vuelve a correr y comprueba que la excepción de verdad desapareció — idealmente bajo las mismas condiciones que la dispararon. En Visual Studio, pon un breakpoint en la línea corregida y observa el estado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pregunta el &lt;em&gt;por qué&lt;/em&gt;, no solo el &lt;em&gt;qué&lt;/em&gt;.&lt;/strong&gt; "Explica por qué esto lo arregla" saca a la luz una causa raíz equivocada rapidísimo. Si el razonamiento no se sostiene, el arreglo probablemente tampoco.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ojo con las causas plausibles pero falsas.&lt;/strong&gt; Sobre todo con bugs intermitentes, Claude puede proponer una causa que encaja con el trace pero que no es la tuya. Haz que ordene los candidatos y te diga cómo distinguirlos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El debugger sigue siendo el dueño del &lt;em&gt;estado&lt;/em&gt;. Claude es dueño del &lt;em&gt;significado&lt;/em&gt;. Cuando necesitas saber el valor real de una variable en el momento del crash, eso es una ventana de inspección y un breakpoint, no un prompt de chat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo Recurrir a Claude — y Cuándo No
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recurre a él cuando:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El tipo o el mensaje de la excepción te resulta desconocido, o el trace es casi todo internos de framework.&lt;/li&gt;
&lt;li&gt;La causa real está enterrada en inner exceptions o en un &lt;code&gt;AggregateException&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Estás mirando frames asíncronos y no logras saber cuál de tus líneas es el origen.&lt;/li&gt;
&lt;li&gt;Quieres el arreglo &lt;em&gt;idiomático&lt;/em&gt; y sus intercambios, no solo un parche que quite el rojo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No te molestes cuando:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ya sabes la causa — un &lt;code&gt;NullReferenceException&lt;/code&gt; en una línea que acabas de escribir no necesita un LLM.&lt;/li&gt;
&lt;li&gt;Necesitas el valor en runtime de algo — usa el debugger.&lt;/li&gt;
&lt;li&gt;El trace contiene &lt;strong&gt;secretos o datos de clientes&lt;/strong&gt;. Las cadenas de conexión, los tokens y los datos personales aparecen en mensajes de excepción y payloads más seguido de lo que te gustaría. Límpialos antes de pegar, especialmente con un modelo en la nube.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La regla de oro: &lt;strong&gt;si de otro modo abrirías el navegador para buscar la excepción, mejor pregúntale a Claude — está en el IDE, puede leer tu código, y no te mandará a un hilo de foro muerto de 2015.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Puntos Clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;El trace muestra maquinaria, no intención&lt;/strong&gt; — máquinas de estados asíncronas, inner exceptions y frames de framework entierran la única línea que es tuya. Leer traces es reconocimiento de patrones, que es justo donde ayuda un LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude vive en Visual Studio de dos formas&lt;/strong&gt; — elígelo en el selector de modelo de Copilot Chat para diagnóstico rápido y conversacional; corre Claude Code en la terminal integrada cuando el arreglo es agéntico y abarca varios archivos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El contexto lo es todo&lt;/strong&gt; — pega el &lt;code&gt;ex.ToString()&lt;/code&gt; completo, apunta al primer frame que es &lt;em&gt;tuyo&lt;/em&gt; y describe cuándo pasa. Un mensaje titular recibe una conjetura titular.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El número de línea suele ser una pista falsa&lt;/strong&gt; — el ejemplo de EF Core lanza en &lt;code&gt;ToListAsync&lt;/code&gt;, pero el bug es compartir un &lt;code&gt;DbContext&lt;/code&gt; entre consultas paralelas. El valor está en la explicación, no en la ubicación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifica, luego confía&lt;/strong&gt; — reproduce el arreglo, pregunta &lt;em&gt;por qué&lt;/em&gt; funciona y nunca pegues secretos. El debugger es dueño del estado; Claude, del significado.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>RAG local en .NET: Chatea con tu Documentación (sin nube, sin API keys)</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 10 Jul 2026 20:47:29 +0000</pubDate>
      <link>https://dev.to/jgomezdev/rag-local-en-net-chatea-con-tu-documentacion-sin-nube-sin-api-keys-4lgh</link>
      <guid>https://dev.to/jgomezdev/rag-local-en-net-chatea-con-tu-documentacion-sin-nube-sin-api-keys-4lgh</guid>
      <description>&lt;h1&gt;
  
  
  RAG local en .NET: Chatea con tu Documentación (sin nube, sin API keys)
&lt;/h1&gt;

&lt;p&gt;Pregúntale a un modelo de lenguaje sobre la documentación de &lt;em&gt;tu&lt;/em&gt; producto y míralo improvisar. Nunca leyó tus docs internas de la API, así que rellena los huecos con respuestas seguras, plausibles y completamente inventadas. Eso es una alucinación, y no es que el modelo sea tonto: es que le pediste algo para lo que jamás le diste el material.&lt;/p&gt;

&lt;p&gt;RAG lo arregla. En vez de rezar para que el modelo &lt;em&gt;memorizara&lt;/em&gt; tus docs, le entregas las páginas relevantes &lt;em&gt;en el momento de la pregunta&lt;/em&gt; — como dejar que un estudiante haga un examen a libro abierto. En este tutorial construimos un pequeño sistema RAG en .NET 10 que responde preguntas sobre la documentación de un producto, y lo hacemos &lt;strong&gt;100% local&lt;/strong&gt;: sin nube, sin API keys, sin factura a fin de mes.&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%2F2q0owz1rkc49anrvbdgw.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%2F2q0owz1rkc49anrvbdgw.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Qué es RAG, sin humo?
&lt;/h2&gt;

&lt;p&gt;RAG significa &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt; (Generación Aumentada por Recuperación). Quítale las siglas y quedan tres ideas simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval (recuperar)&lt;/strong&gt; — encontrar los pedazos de tus docs que se relacionan con la pregunta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Augmented (aumentar)&lt;/strong&gt; — pegar esos pedazos al prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation (generar)&lt;/strong&gt; — dejar que el modelo escriba la respuesta usándolos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eso es todo. Lo ingenioso está en el primer paso: &lt;em&gt;¿cómo encuentras los pedazos relevantes?&lt;/em&gt; No basta con buscar palabras iguales, porque alguien puede preguntar "¿cómo inicio sesión?" mientras tu doc dice "autenticación". Palabras distintas, mismo significado.&lt;/p&gt;

&lt;p&gt;El truco son los &lt;strong&gt;embeddings&lt;/strong&gt;. Un embedding es simplemente una lista de números (un &lt;em&gt;vector&lt;/em&gt;) que captura el &lt;em&gt;significado&lt;/em&gt; de un texto. Los textos que significan cosas parecidas obtienen números parecidos — así "iniciar sesión" y "autenticación" quedan cerca, aunque no compartan ni una letra. Encuentra los vectores más cercanos al vector de la pregunta y habrás encontrado los docs relevantes. No es magia, es geometría.&lt;/p&gt;




&lt;h2&gt;
  
  
  Los tres pasos de RAG
&lt;/h2&gt;

&lt;p&gt;Este es el pipeline completo, de principio a fin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingesta&lt;/strong&gt; (una sola vez, al inicio): parte los docs en trozos pequeños, convierte cada trozo en un embedding y guárdalos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recuperación&lt;/strong&gt; (por cada pregunta): convierte la pregunta en un embedding y busca los trozos cuyos embeddings estén más cerca.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aumento y generación&lt;/strong&gt; (por cada pregunta): mete esos trozos en el prompt y pide al modelo que responda usándolos.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Los pasos 2 y 3 corren cada vez que alguien pregunta algo. El paso 1 corre una vez y se reutiliza para siempre (o hasta que cambien tus docs).&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%2Ffsygi8ao9jgpiin89gaf.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%2Ffsygi8ao9jgpiin89gaf.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Lo que vamos a construir + Setup
&lt;/h2&gt;

&lt;p&gt;Armaremos una app de consola que ingiere la documentación de un producto y luego responde preguntas sobre ella desde la terminal. Tres herramientas hacen el trabajo pesado:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; corre los modelos de IA en tu máquina — uno para crear embeddings, otro para escribir respuestas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; guarda todo en un único archivo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sqlite-vec&lt;/strong&gt;, una extensión de SQLite, hace por nosotros la búsqueda de "los vectores más cercanos".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tu laptop está a punto de tener un segundo empleo como mini data center de IA. El único costo es un ventilador un poco más caliente.&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;# 1. Crea el proyecto&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; OrbitDocsRag
&lt;span class="nb"&gt;cd &lt;/span&gt;OrbitDocsRag
dotnet add package Microsoft.Data.Sqlite

&lt;span class="c"&gt;# 2. Instala Ollama desde https://ollama.com y descarga los dos modelos&lt;/span&gt;
ollama pull nomic-embed-text   &lt;span class="c"&gt;# convierte texto en vectores de 768 números&lt;/span&gt;
ollama pull llama3.2           &lt;span class="c"&gt;# escribe la respuesta final&lt;/span&gt;

&lt;span class="c"&gt;# 3. Coloca la librería nativa de sqlite-vec junto a tu app.&lt;/span&gt;
&lt;span class="c"&gt;#    Descarga vec0.dll (Windows) / vec0.so (Linux) / vec0.dylib (macOS)&lt;/span&gt;
&lt;span class="c"&gt;#    desde https://github.com/asg017/sqlite-vec/releases y ponla en la&lt;/span&gt;
&lt;span class="c"&gt;#    carpeta del proyecto para que aterrice en bin/ al compilar.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama expone una API REST sencilla en &lt;code&gt;http://localhost:11434&lt;/code&gt;. Hablaremos con ella usando un &lt;code&gt;HttpClient&lt;/code&gt; pelado — sin SDK — para que veas que no hay nada escondido detrás de un wrapper. De verdad son solo un par de &lt;code&gt;POST&lt;/code&gt; por HTTP.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 1 — Partir los docs en trozos (chunking)
&lt;/h2&gt;

&lt;p&gt;No puedes convertir un manual de 50 páginas en un solo vector — sería un promedio borroso de todo, inútil para cualquier cosa. Así que partimos cada documento en ventanas pequeñas y solapadas. Los trozos pequeños dan coincidencias &lt;em&gt;precisas&lt;/em&gt;; el solapamiento evita que una idea que cae justo en el borde quede cortada por la mitad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Chunker&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;[]?)&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveEmptyEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada trozo recuerda su &lt;code&gt;Source&lt;/code&gt; (el archivo del que vino) para poder citarlo después. Desliza una ventana de 120 palabras avanzando de 100 en 100, y obtienes trozos solapados que conservan su contexto.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 2 — Convertir texto en vectores (embeddings)
&lt;/h2&gt;

&lt;p&gt;Aquí Ollama se gana el sueldo. Hacemos &lt;code&gt;POST&lt;/code&gt; de un texto a &lt;code&gt;/api/embed&lt;/code&gt; y recibimos un arreglo de 768 floats — el embedding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OllamaClient&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:11434"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nomic-embed-text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/embed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esa es toda la parte de "IA" de los embeddings — una llamada HTTP. El modelo lee tu texto y devuelve su significado como 768 números. Dos trozos sobre autenticación regresarán con dos arreglos muy parecidos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 3 — Guardar los vectores en SQLite
&lt;/h2&gt;

&lt;p&gt;Ahora guardamos cada trozo &lt;em&gt;y&lt;/em&gt; su embedding. &lt;code&gt;sqlite-vec&lt;/code&gt; nos da una tabla virtual especial que sabe buscar vectores de forma nativa. Le decimos que los vectores son 768 floats y que queremos distancia &lt;strong&gt;coseno&lt;/strong&gt; (ya llegamos a eso). Las columnas &lt;code&gt;+source&lt;/code&gt; y &lt;code&gt;+chunk&lt;/code&gt; viajan como texto plano para poder recuperar el texto original tras una búsqueda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SqliteConnection&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Data Source=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vec0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// carga la librería nativa de sqlite-vec&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;VIRTUAL&lt;/span&gt; &lt;span class="n"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt; &lt;span class="n"&gt;USING&lt;/span&gt; &lt;span class="nf"&gt;vec0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;768&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;distance_metric&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;INTO&lt;/span&gt; &lt;span class="nf"&gt;vec_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$chunk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guardamos el embedding como un string JSON tipo &lt;code&gt;[0.12, -0.04, ...]&lt;/code&gt; — sqlite-vec lo interpreta sin problema.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cómo funciona eso de "el más cercano"?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sqlite-vec&lt;/code&gt; hace la búsqueda en código nativo rápido, pero no hay ningún misterio adentro. "¿Qué tan parecidos son dos vectores?" se responde con &lt;strong&gt;similitud coseno&lt;/strong&gt;, que son un puñado de líneas de C# plano:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;CosineSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dot&lt;/span&gt;  &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magB&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El resultado va de &lt;code&gt;1.0&lt;/code&gt; (significado idéntico) hasta &lt;code&gt;0&lt;/code&gt; (sin relación). sqlite-vec reporta la &lt;em&gt;distancia&lt;/em&gt; — básicamente &lt;code&gt;1 - similitud&lt;/code&gt; — así que &lt;strong&gt;más pequeño es más cercano&lt;/strong&gt;. Por eso cada consulta de abajo termina en &lt;code&gt;ORDER BY distance&lt;/code&gt;. Eso es toda la "búsqueda vectorial" de la que todos hablan: un producto punto y una raíz cuadrada.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 4 — Recuperar los trozos relevantes
&lt;/h2&gt;

&lt;p&gt;Por cada pregunta, convertimos la pregunta en embedding exactamente igual, y luego le pedimos a sqlite-vec los &lt;code&gt;k&lt;/code&gt; trozos más cercanos. La palabra clave &lt;code&gt;MATCH&lt;/code&gt; es la señal de sqlite-vec para ejecutar una búsqueda por vecinos más cercanos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// (agrega este método a VectorStore)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;        &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt;
        &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;MATCH&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="n"&gt;ORDER&lt;/span&gt; &lt;span class="n"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="s"&gt;""";
&lt;/span&gt;    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le das el vector de la pregunta y recibes los tres trozos con mayor probabilidad de contener la respuesta — ordenados del más cercano al más lejano.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paso 5 — Armar el prompt y generar la respuesta
&lt;/h2&gt;

&lt;p&gt;El último paso es la recompensa. Cosemos los trozos recuperados dentro de un prompt con una instrucción estricta: &lt;strong&gt;responde solo con este contexto&lt;/strong&gt;. Luego lo enviamos al modelo de chat vía &lt;code&gt;/api/chat&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (agrega este método a OllamaClient)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// records anidados dentro de OllamaClient&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y el prompt que lo une todo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esa única instrucción — &lt;em&gt;solo con este contexto&lt;/em&gt; — es lo que mata las alucinaciones. Sin nada en qué apoyarse, el modelo no inventará una respuesta con seguridad; admitirá que no sabe. Es la diferencia entre un estudiante que estudió y uno que improvisa frente a la clase. Y como cada trozo lleva su etiqueta &lt;code&gt;[source.md]&lt;/code&gt;, puedes mostrarle al usuario exactamente de dónde salió la respuesta.&lt;/p&gt;




&lt;h2&gt;
  
  
  El ejemplo completo
&lt;/h2&gt;

&lt;p&gt;Este es el &lt;code&gt;Program.cs&lt;/code&gt; de nivel superior. Trae tres "docs" diminutas embebidas para que puedas correrlo sin ningún archivo, pero cambia &lt;code&gt;SampleDocs.All&lt;/code&gt; por una carpeta de &lt;code&gt;.md&lt;/code&gt; reales y no cambia nada más. Mantén los tipos &lt;code&gt;OllamaClient&lt;/code&gt;, &lt;code&gt;VectorStore&lt;/code&gt;, &lt;code&gt;Chunker&lt;/code&gt;, &lt;code&gt;DocChunk&lt;/code&gt; y &lt;code&gt;SearchHit&lt;/code&gt; de los pasos anteriores en el mismo proyecto (debajo del código de nivel superior, o en sus propios archivos) y compila tal cual.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OllamaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orbit-docs.db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// --- Paso 1 + 2 + 3: ingesta de los docs (trocear -&amp;gt; embeber -&amp;gt; guardar) ---&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SampleDocs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Chunker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pregunta sobre la API de Orbit: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"¿Cómo me autentico?"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// --- Paso 4: recuperar ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;questionEmbedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;questionEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// --- Paso 5: aumentar + generar ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\n&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Fuentes: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SampleDocs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;All&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"auth.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;Authorization&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Bearer&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt; &lt;span class="n"&gt;never&lt;/span&gt; &lt;span class="n"&gt;expire&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt;
            &lt;span class="n"&gt;revoked&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Rate&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"webhooks.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;pushes&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;
            &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Every&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Orbit&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;trusting&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Failed&lt;/span&gt; &lt;span class="n"&gt;deliveries&lt;/span&gt;
            &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"errors.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;429&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt;
            &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
            &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;422&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;"""
&lt;/span&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Córrelo, pregunta &lt;em&gt;"¿cómo inicio sesión?"&lt;/em&gt; y, aunque esas palabras exactas no aparecen en ningún lado de los docs, el embedding de tu pregunta aterriza junto al trozo de autenticación — y el modelo responde con las instrucciones de la API key, citando &lt;code&gt;auth.md&lt;/code&gt;. Eso es RAG funcionando.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo usar RAG — y cuándo no
&lt;/h2&gt;

&lt;p&gt;RAG es la herramienta correcta cuando tu conocimiento es &lt;strong&gt;grande, privado o cambiante&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Usa RAG&lt;/strong&gt; para docs de empresa, manuales de producto, tickets de soporte, una base de conocimiento — cualquier cosa que el modelo base nunca vio y que se actualiza seguido. ¿Docs nuevos? Solo los embebes. Sin reentrenar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sáltate RAG y pega todo en el prompt&lt;/strong&gt; cuando el conocimiento completo cabe cómodamente en la ventana de contexto. Si son dos párrafos, la recuperación es matar moscas a cañonazos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recurre a fine-tuning&lt;/strong&gt; cuando necesitas cambiar el &lt;em&gt;estilo, tono o formato&lt;/em&gt; del modelo — no sus hechos. El fine-tuning enseña comportamiento; RAG aporta conocimiento. Resuelven problemas distintos, y muchos sistemas reales usan ambos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Algunas limitaciones honestas a tener en cuenta: tus respuestas son tan buenas como tus trozos, así que un mal chunking significa mala recuperación. Las tablas y los números se embeben mal (su significado vive en la estructura, no en la prosa). Y si la respuesta simplemente no está en los docs, un sistema RAG bien instruido debería decirlo — lo cual es una virtud, no un defecto.&lt;/p&gt;

&lt;p&gt;La regla de oro: &lt;strong&gt;si tendrías que copiar y pegar un documento en el chat para obtener una buena respuesta, ese es trabajo para RAG.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Puntos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG son tres pasos, no magia&lt;/strong&gt; — recuperar los trozos relevantes, aumentar el prompt, generar la respuesta. Todo lo demás es detalle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Los embeddings convierten significado en geometría&lt;/strong&gt; — un texto parecido se vuelve un vector parecido, así encuentras docs relevantes aunque las palabras no coincidan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La "búsqueda vectorial" es solo similitud coseno&lt;/strong&gt; — un producto punto y una raíz cuadrada; sqlite-vec solo lo hace rápido, no misterioso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anclar al contexto mata las alucinaciones&lt;/strong&gt; — decirle al modelo que responda &lt;em&gt;solo&lt;/em&gt; con el contexto recuperado convierte el disparate seguro en un honesto "no sé", y te deja citar fuentes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No necesitas la nube&lt;/strong&gt; — Ollama más SQLite corren un pipeline RAG completo en tu laptop, con cero API keys y cero costo por token.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>csharp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build a Local RAG in .NET: Chat With Your Docs (No Cloud, No API Keys)</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Fri, 10 Jul 2026 20:47:26 +0000</pubDate>
      <link>https://dev.to/jgomezdev/build-a-local-rag-in-net-chat-with-your-docs-no-cloud-no-api-keys-3nki</link>
      <guid>https://dev.to/jgomezdev/build-a-local-rag-in-net-chat-with-your-docs-no-cloud-no-api-keys-3nki</guid>
      <description>&lt;h1&gt;
  
  
  Build a Local RAG in .NET: Chat With Your Docs (No Cloud, No API Keys)
&lt;/h1&gt;

&lt;p&gt;Ask a language model about &lt;em&gt;your&lt;/em&gt; product's documentation and watch it improvise. It has never read your internal API docs, so it fills the gaps with confident, plausible, completely made-up answers. That is hallucination, and it is not the model being dumb — it is the model being asked a question it was never given the material to answer.&lt;/p&gt;

&lt;p&gt;RAG fixes this. Instead of hoping the model &lt;em&gt;memorized&lt;/em&gt; your docs, you hand it the relevant pages &lt;em&gt;at question time&lt;/em&gt; — like letting a student take an open-book exam. In this tutorial we build a small RAG system in .NET 10 that answers questions about a product's documentation, and we do it &lt;strong&gt;100% locally&lt;/strong&gt;: no cloud, no API keys, no invoice at the end of the month.&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%2Fpf0ku1od2y357jbws5gw.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%2Fpf0ku1od2y357jbws5gw.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is RAG, Without the Hype?
&lt;/h2&gt;

&lt;p&gt;RAG stands for &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt;. Strip away the acronym and it is three plain-English words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval&lt;/strong&gt; — find the pieces of your docs that relate to the question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Augmented&lt;/strong&gt; — glue those pieces onto the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation&lt;/strong&gt; — let the model write the answer using them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. The clever part is step one: &lt;em&gt;how do you find the relevant pieces?&lt;/em&gt; You can't just search for matching keywords, because someone might ask "how do I log in?" while your docs say "authentication." Different words, same meaning.&lt;/p&gt;

&lt;p&gt;The trick is &lt;strong&gt;embeddings&lt;/strong&gt;. An embedding is just a list of numbers (a &lt;em&gt;vector&lt;/em&gt;) that captures the &lt;em&gt;meaning&lt;/em&gt; of a piece of text. Texts that mean similar things get similar numbers — so "log in" and "authentication" land close together, even though they share no letters. Find the closest vectors to the question's vector, and you've found the relevant docs. No magic, just geometry.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Steps of RAG
&lt;/h2&gt;

&lt;p&gt;Here is the entire pipeline, start to finish:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingest&lt;/strong&gt; (done once, up front): chop the docs into small chunks, turn each chunk into an embedding, and store them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve&lt;/strong&gt; (done per question): turn the question into an embedding, then find the chunks whose embeddings are closest to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Augment &amp;amp; generate&lt;/strong&gt; (done per question): drop those chunks into the prompt and ask the model to answer using them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 2 and 3 run every time someone asks something. Step 1 runs once and gets reused forever (or until your docs change).&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%2Fqmw6zqe8gnneqfmm0jg2.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%2Fqmw6zqe8gnneqfmm0jg2.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building + Setup
&lt;/h2&gt;

&lt;p&gt;We'll build a console app that ingests a product's docs and then answers questions about them from the terminal. Three tools do the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; runs the AI models on your machine — one to create embeddings, one to write answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; stores everything in a single file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sqlite-vec&lt;/strong&gt;, a SQLite extension, does the "find the closest vectors" search for us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your laptop is about to moonlight as a tiny AI data center. The only cost is a slightly warmer fan.&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;# 1. Create the project&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; OrbitDocsRag
&lt;span class="nb"&gt;cd &lt;/span&gt;OrbitDocsRag
dotnet add package Microsoft.Data.Sqlite

&lt;span class="c"&gt;# 2. Install Ollama from https://ollama.com, then pull the two models&lt;/span&gt;
ollama pull nomic-embed-text   &lt;span class="c"&gt;# turns text into 768-number vectors&lt;/span&gt;
ollama pull llama3.2           &lt;span class="c"&gt;# writes the final answer&lt;/span&gt;

&lt;span class="c"&gt;# 3. Drop the sqlite-vec native library next to your app.&lt;/span&gt;
&lt;span class="c"&gt;#    Grab vec0.dll (Windows) / vec0.so (Linux) / vec0.dylib (macOS)&lt;/span&gt;
&lt;span class="c"&gt;#    from https://github.com/asg017/sqlite-vec/releases and place it&lt;/span&gt;
&lt;span class="c"&gt;#    in the project folder so it lands in bin/ at build time.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama exposes a plain REST API at &lt;code&gt;http://localhost:11434&lt;/code&gt;. We'll talk to it with a bare &lt;code&gt;HttpClient&lt;/code&gt; — no SDK — so you can see there is nothing hiding behind a wrapper. It really is just a couple of HTTP &lt;code&gt;POST&lt;/code&gt;s.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Chop the Docs Into Chunks
&lt;/h2&gt;

&lt;p&gt;You can't embed a whole 50-page manual as one vector — it would be a blurry average of everything, useful for nothing. So we split each document into small, overlapping windows. Small chunks give &lt;em&gt;precise&lt;/em&gt; matches; the overlap makes sure an idea that straddles a boundary isn't cut in half.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Chunker&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;[]?)&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveEmptyEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;maxWords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DocChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each chunk remembers its &lt;code&gt;Source&lt;/code&gt; (the file it came from) so we can cite it later. Slide a 120-word window forward 100 words at a time, and you get overlapping chunks that keep their context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Turn Text Into Vectors (Embeddings)
&lt;/h2&gt;

&lt;p&gt;This is where Ollama earns its keep. We &lt;code&gt;POST&lt;/code&gt; a string to &lt;code&gt;/api/embed&lt;/code&gt; and get back an array of 768 floats — the embedding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OllamaClient&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:11434"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nomic-embed-text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/embed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;EmbedResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;Embeddings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole "AI" part of embeddings — one HTTP call. The model reads your text and returns its meaning as 768 numbers. Two chunks about authentication will come back with two very similar arrays.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Store the Vectors in SQLite
&lt;/h2&gt;

&lt;p&gt;Now we save each chunk &lt;em&gt;and&lt;/em&gt; its embedding. &lt;code&gt;sqlite-vec&lt;/code&gt; gives us a special virtual table that can search vectors natively. We tell it the vectors are 768 floats and that we want &lt;strong&gt;cosine&lt;/strong&gt; distance (more on that in a second). The &lt;code&gt;+source&lt;/code&gt; and &lt;code&gt;+chunk&lt;/code&gt; columns ride along as plain text so we can read the original text back after a search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SqliteConnection&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Data Source=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vec0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// loads the sqlite-vec native library&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;VIRTUAL&lt;/span&gt; &lt;span class="n"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt; &lt;span class="n"&gt;USING&lt;/span&gt; &lt;span class="nf"&gt;vec0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;768&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;distance_metric&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DocChunk&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;INTO&lt;/span&gt; &lt;span class="nf"&gt;vec_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$chunk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We store the embedding as a JSON string like &lt;code&gt;[0.12, -0.04, ...]&lt;/code&gt; — sqlite-vec happily parses that.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does "closest" actually work?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sqlite-vec&lt;/code&gt; does the search in fast native code, but there's no mystery inside. "How similar are two vectors?" is answered by &lt;strong&gt;cosine similarity&lt;/strong&gt;, which is a handful of lines of plain C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;CosineSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dot&lt;/span&gt;  &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magB&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result runs from &lt;code&gt;1.0&lt;/code&gt; (identical meaning) down to &lt;code&gt;0&lt;/code&gt; (unrelated). sqlite-vec reports the &lt;em&gt;distance&lt;/em&gt; — basically &lt;code&gt;1 - similarity&lt;/code&gt; — so &lt;strong&gt;smaller is closer&lt;/strong&gt;. That's why every query below ends with &lt;code&gt;ORDER BY distance&lt;/code&gt;. That's the whole "vector search" everyone talks about: a dot product and a square root.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Retrieve the Relevant Chunks
&lt;/h2&gt;

&lt;p&gt;Per question, we embed the question the exact same way, then ask sqlite-vec for the &lt;code&gt;k&lt;/code&gt; nearest chunks. The &lt;code&gt;MATCH&lt;/code&gt; keyword is sqlite-vec's signal to run a nearest-neighbor search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// (add this method to VectorStore)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;        &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vec_chunks&lt;/span&gt;
        &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;MATCH&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="n"&gt;ORDER&lt;/span&gt; &lt;span class="n"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
        &lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="s"&gt;""";
&lt;/span&gt;    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it the question's vector, get back the three chunks most likely to contain the answer — ranked from closest to furthest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Build the Prompt and Generate the Answer
&lt;/h2&gt;

&lt;p&gt;The final step is the payoff. We stitch the retrieved chunks into a prompt with one strict instruction: &lt;strong&gt;answer only from this context&lt;/strong&gt;. Then we send it to the chat model via &lt;code&gt;/api/chat&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (add this method to OllamaClient)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// nested records inside OllamaClient&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the prompt that ties it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one instruction — &lt;em&gt;only from this context&lt;/em&gt; — is what kills hallucinations. With nothing to lean on, the model won't confidently invent an answer; it'll admit it doesn't know. It's the difference between a student who studied and one improvising in front of the class. And because each chunk carries its &lt;code&gt;[source.md]&lt;/code&gt; tag, you can show the user exactly where the answer came from.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Example
&lt;/h2&gt;

&lt;p&gt;Here's the top-level &lt;code&gt;Program.cs&lt;/code&gt;. It ships with three tiny "docs" inline so you can run it without any files, but swap &lt;code&gt;SampleDocs.All&lt;/code&gt; for a folder of real &lt;code&gt;.md&lt;/code&gt; files and nothing else changes. Keep the &lt;code&gt;OllamaClient&lt;/code&gt;, &lt;code&gt;VectorStore&lt;/code&gt;, &lt;code&gt;Chunker&lt;/code&gt;, &lt;code&gt;DocChunk&lt;/code&gt;, and &lt;code&gt;SearchHit&lt;/code&gt; types from the steps above in the same project (below the top-level code, or in their own files) and it compiles as-is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OllamaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orbit-docs.db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// --- Step 1 + 2 + 3: ingest the docs (chunk -&amp;gt; embed -&amp;gt; store) ---&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SampleDocs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Chunker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ask about the Orbit API: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"How do I authenticate?"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// --- Step 4: retrieve ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;questionEmbedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EmbedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;questionEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// --- Step 5: augment + generate ---&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="n"&gt;Answer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="s"&gt;"I don't know based on the docs."&lt;/span&gt;

    &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\n&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Sources: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SampleDocs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;All&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"auth.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;Authorization&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Bearer&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt; &lt;span class="n"&gt;never&lt;/span&gt; &lt;span class="n"&gt;expire&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt;
            &lt;span class="n"&gt;revoked&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Rate&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"webhooks.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;pushes&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;
            &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Every&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
            &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Orbit&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;trusting&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Failed&lt;/span&gt; &lt;span class="n"&gt;deliveries&lt;/span&gt;
            &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"errors.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Orbit&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;429&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt;
            &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
            &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="m"&gt;422&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;"""
&lt;/span&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it, ask &lt;em&gt;"how do I log in?"&lt;/em&gt;, and even though those exact words appear nowhere in the docs, the embedding for your question lands next to the authentication chunk — and the model answers with the API-key instructions, citing &lt;code&gt;auth.md&lt;/code&gt;. That's RAG working.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use RAG — and When Not To
&lt;/h2&gt;

&lt;p&gt;RAG is the right tool when your knowledge is &lt;strong&gt;large, private, or changing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use RAG&lt;/strong&gt; for company docs, product manuals, support tickets, a knowledge base — anything the base model never saw and that updates often. New docs? Just embed them. No retraining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip RAG and just paste it into the prompt&lt;/strong&gt; when the whole knowledge fits comfortably in the context window. If it's two paragraphs, retrieval is overkill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reach for fine-tuning instead&lt;/strong&gt; when you need to change the model's &lt;em&gt;style, tone, or format&lt;/em&gt; — not its facts. Fine-tuning teaches behavior; RAG supplies knowledge. They solve different problems, and plenty of real systems use both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few honest limitations to keep in mind: your answers are only as good as your chunks, so bad chunking means bad retrieval. Tables and numbers embed poorly (their meaning lives in structure, not prose). And if the answer simply isn't in the docs, a well-instructed RAG system should say so — which is a feature, not a bug.&lt;/p&gt;

&lt;p&gt;The rule of thumb: &lt;strong&gt;if you'd have to copy-paste a document into the chat to get a good answer, that's a job for RAG.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG is three steps, not magic&lt;/strong&gt; — retrieve relevant chunks, augment the prompt, generate the answer. Everything else is detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings turn meaning into geometry&lt;/strong&gt; — similar text becomes similar vectors, so you can find relevant docs even when the words don't match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Vector search" is just cosine similarity&lt;/strong&gt; — a dot product and a square root; sqlite-vec only makes it fast, not mysterious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounding kills hallucinations&lt;/strong&gt; — telling the model to answer &lt;em&gt;only&lt;/em&gt; from the retrieved context turns confident nonsense into honest "I don't know," and lets you cite sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You don't need the cloud&lt;/strong&gt; — Ollama plus SQLite runs a complete RAG pipeline on your laptop, with zero API keys and zero per-token cost.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>csharp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Claude Code for .NET Developers: From Zero to Productive in VS Code and Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Mon, 15 Jun 2026 22:57:25 +0000</pubDate>
      <link>https://dev.to/jgomezdev/claude-code-for-net-developers-from-zero-to-productive-in-vs-code-and-visual-studio-3cm6</link>
      <guid>https://dev.to/jgomezdev/claude-code-for-net-developers-from-zero-to-productive-in-vs-code-and-visual-studio-3cm6</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code for .NET Developers: From Zero to Productive in VS Code and Visual Studio
&lt;/h1&gt;

&lt;p&gt;Here is the pattern that plays out constantly with AI coding tools: you try one, it blows your mind. You use it for a week, and it starts feeling random. Sometimes it's brilliant; sometimes it confidently suggests code from three framework versions ago. You stop trusting it, and it fades into the "cool but not practical" category.&lt;/p&gt;

&lt;p&gt;The problem is not the model. The problem is that you are using a powerful tool without understanding how it works. Claude Code is different from a chat window, and once you understand its mechanics — how it discovers context, how you shape its behavior, how you extend it with custom commands — it becomes genuinely reliable for .NET work.&lt;/p&gt;

&lt;p&gt;This article covers everything: installation, VS Code integration, the honest story about Visual Studio 2022 and 2026, CLAUDE.md (the most important file you are not writing yet), context management, Skills, and MCP. Whether you are new to AI tooling or already use it daily, there is something here for you.&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.amazonaws.com%2Fuploads%2Farticles%2Fu3ltb8z6bp2yuaocphah.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.amazonaws.com%2Fuploads%2Farticles%2Fu3ltb8z6bp2yuaocphah.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Claude Code, Exactly?
&lt;/h2&gt;

&lt;p&gt;Claude Code is not a chatbot that happens to know about code. It is a CLI-first, agentic coding assistant built by Anthropic. The key distinction: it operates on your actual repository. It can read files, run commands, edit code, and chain multiple steps together without you narrating every move.&lt;/p&gt;

&lt;p&gt;The mental model that helps most: Claude Code is a skilled contractor who just walked into your codebase. Give them the right context, and they can work independently. Give them nothing, and they will ask questions — or worse, make assumptions.&lt;/p&gt;

&lt;p&gt;There are two surfaces you will use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The CLI&lt;/strong&gt; (&lt;code&gt;claude&lt;/code&gt;) — runs in any terminal, works with any editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The VS Code extension&lt;/strong&gt; (&lt;code&gt;anthropic.claude-code&lt;/code&gt;) — embeds the CLI experience directly into VS Code, with file awareness, selection sharing, and inline diffs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both use the same model and the same context system. The extension is additive convenience on top of the CLI, not a separate product.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;You need Node.js 22 LTS or later. Check with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Claude Code globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first run opens a browser for OAuth with your Anthropic account. After that, your credentials are stored locally and the CLI works offline-first (the model calls still go to Anthropic's API, of course).&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code Integration
&lt;/h3&gt;

&lt;p&gt;Install the extension from the marketplace. The ID is &lt;code&gt;anthropic.claude-code&lt;/code&gt;. You need VS Code 1.98 or later.&lt;/p&gt;

&lt;p&gt;Once installed, a Claude icon appears in the activity bar. Open it, sign in, and you have a chat panel that is deeply aware of your open files, your current selection, and your workspace structure. You can also invoke it inline with &lt;code&gt;Ctrl+Shift+C&lt;/code&gt; (Windows/Linux) or &lt;code&gt;Cmd+Shift+C&lt;/code&gt; (Mac) to open a quick input without leaving your editor flow.&lt;/p&gt;

&lt;p&gt;The extension does something important automatically: when you highlight code and ask a question, it attaches that selection as context. You do not have to copy-paste. This alone saves significant friction on large codebases.&lt;/p&gt;

&lt;p&gt;If you use &lt;strong&gt;JetBrains Rider&lt;/strong&gt;, there is also full Claude Code integration available through the JetBrains Marketplace. The experience is comparable to VS Code. Rider users are covered — you can follow this entire article and apply it directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Studio 2022 and 2026: The Honest Story
&lt;/h3&gt;

&lt;p&gt;Visual Studio has been the king of .NET IDEs for decades — it just has not heard of Claude yet.&lt;/p&gt;

&lt;p&gt;As of June 2026, there is no official Claude Code extension for Visual Studio 2022 or 2026. GitHub Copilot has deep VS integration; Claude Code does not. This is not a temporary oversight — the extension model for Visual Studio is substantially different from VS Code, and Anthropic has focused on the VS Code surface.&lt;/p&gt;

&lt;p&gt;The workaround is simple and it works well: use the integrated terminal.&lt;/p&gt;

&lt;p&gt;Open it with &lt;code&gt;View &amp;gt; Terminal&lt;/code&gt; (or &lt;code&gt;Ctrl&lt;/code&gt; + backtick). Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code starts in your repo's root directory. It reads everything — your &lt;code&gt;.cs&lt;/code&gt; files, your &lt;code&gt;.csproj&lt;/code&gt;, your &lt;code&gt;appsettings.json&lt;/code&gt;, your &lt;code&gt;CLAUDE.md&lt;/code&gt;. The terminal-based experience is the same CLI you would use anywhere. You lose the inline diff rendering and selection-sharing that the VS Code extension provides, but the model's capabilities are identical.&lt;/p&gt;

&lt;p&gt;Practical tip: keep Visual Studio open for debugging, the designer, and refactoring tooling (where it is unmatched), and open VS Code or a terminal alongside it for Claude interactions. Many .NET developers already do this. It is not a compromise — it is using each tool for what it is best at.&lt;/p&gt;




&lt;h2&gt;
  
  
  CLAUDE.md — The Brain of the Operation
&lt;/h2&gt;

&lt;p&gt;This is the most important section in the article.&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.amazonaws.com%2Fuploads%2Farticles%2Fk5gjiermmkwva1ws2wem.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.amazonaws.com%2Fuploads%2Farticles%2Fk5gjiermmkwva1ws2wem.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every time Claude Code starts in a directory, it performs a filesystem walk. It moves upward from the current working directory, looking for &lt;code&gt;CLAUDE.md&lt;/code&gt; files. It also lazily loads &lt;code&gt;CLAUDE.md&lt;/code&gt; files found in subdirectories as it encounters them during work. The result is a layered context system: a root &lt;code&gt;CLAUDE.md&lt;/code&gt; for the whole repo, and optionally per-project or per-module files for specific concerns.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is not magic. It is just a Markdown file. But it is the file Claude reads before doing anything else. Think of it as your onboarding document for a contractor who starts fresh every session.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Claude Does NOT Know Without CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;Without any instruction file, Claude Code will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guess your build system from file structure&lt;/li&gt;
&lt;li&gt;Assume generic conventions&lt;/li&gt;
&lt;li&gt;Ask clarifying questions repeatedly&lt;/li&gt;
&lt;li&gt;Make mistakes that a three-minute onboarding would prevent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a good &lt;code&gt;CLAUDE.md&lt;/code&gt;, Claude knows your exact &lt;code&gt;dotnet&lt;/code&gt; commands, your test runner, your architecture decisions, your naming conventions, and any constraints specific to your project. It stops guessing and starts working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovery in Visual Studio: A Common Gotcha
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; lives at the root of your repository — the same directory as your &lt;code&gt;.sln&lt;/code&gt; file. Visual Studio's Solution Explorer shows files based on what is referenced in &lt;code&gt;.csproj&lt;/code&gt; or &lt;code&gt;.sln&lt;/code&gt; files. Since &lt;code&gt;CLAUDE.md&lt;/code&gt; is not referenced by either, it will not appear in Solution Explorer.&lt;/p&gt;

&lt;p&gt;This trips people up. They create the file, it does not show up in VS, they assume something is wrong. Nothing is wrong. Claude loads it perfectly. Open it directly in VS Code or a plain text editor to edit it.&lt;/p&gt;

&lt;p&gt;To verify what Claude has loaded, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lists all context files currently active in the session. If &lt;code&gt;CLAUDE.md&lt;/code&gt; is loaded, it appears here. This is your ground truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Practical CLAUDE.md for a .NET Project
&lt;/h3&gt;

&lt;p&gt;Here is an example for a realistic ASP.NET Core project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# OrderService&lt;/span&gt;

ASP.NET Core 10 minimal API backed by PostgreSQL via EF Core 10.
Authentication: JWT with ASP.NET Core Identity.
Deployed to Azure Container Apps via GitHub Actions.

&lt;span class="gu"&gt;## Build&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;dotnet build OrderService.sln
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## Tests&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;&lt;span class="c"&gt;# Run all tests&lt;/span&gt;
dotnet &lt;span class="nb"&gt;test &lt;/span&gt;OrderService.sln

&lt;span class="c"&gt;# Run only unit tests&lt;/span&gt;
dotnet &lt;span class="nb"&gt;test &lt;/span&gt;tests/OrderService.UnitTests/

&lt;span class="c"&gt;# Run with coverage&lt;/span&gt;
dotnet &lt;span class="nb"&gt;test &lt;/span&gt;OrderService.sln &lt;span class="nt"&gt;--collect&lt;/span&gt;:&lt;span class="s2"&gt;"XPlat Code Coverage"&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="sb"&gt;`src/OrderService.Api/`&lt;/span&gt; — minimal API endpoints, no business logic here
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`src/OrderService.Domain/`&lt;/span&gt; — domain models, value objects, interfaces
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`src/OrderService.Application/`&lt;/span&gt; — CQRS handlers (MediatR), validators (FluentValidation)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`src/OrderService.Infrastructure/`&lt;/span&gt; — EF Core, repositories, external HTTP clients
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`tests/OrderService.UnitTests/`&lt;/span&gt; — xUnit, Moq, domain and application layer only
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`tests/OrderService.IntegrationTests/`&lt;/span&gt; — xUnit, Testcontainers, full API stack

&lt;span class="gu"&gt;## Conventions&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Use &lt;span class="sb"&gt;`record`&lt;/span&gt; types for immutable domain models and DTOs
&lt;span class="p"&gt;-&lt;/span&gt; Use C# 14 &lt;span class="sb"&gt;`field`&lt;/span&gt; keyword instead of explicit backing fields where applicable
&lt;span class="p"&gt;-&lt;/span&gt; All nullable reference types enabled; annotate accordingly
&lt;span class="p"&gt;-&lt;/span&gt; No &lt;span class="sb"&gt;`var`&lt;/span&gt; — use explicit types everywhere
&lt;span class="p"&gt;-&lt;/span&gt; Async all the way down; no &lt;span class="sb"&gt;`.Result`&lt;/span&gt; or &lt;span class="sb"&gt;`.Wait()`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Repository interfaces in Domain layer; implementations in Infrastructure
&lt;span class="p"&gt;-&lt;/span&gt; Endpoint classes in &lt;span class="sb"&gt;`Features/`&lt;/span&gt; folders grouped by domain concept (vertical slice)

&lt;span class="gu"&gt;## EF Core&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Migrations in &lt;span class="sb"&gt;`src/OrderService.Infrastructure/Migrations/`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Add migration: &lt;span class="sb"&gt;`dotnet ef migrations add &amp;lt;Name&amp;gt; --project src/OrderService.Infrastructure --startup-project src/OrderService.Api`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Apply: &lt;span class="sb"&gt;`dotnet ef database update --project src/OrderService.Infrastructure --startup-project src/OrderService.Api`&lt;/span&gt;

&lt;span class="gu"&gt;## Do Not&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Do not add business logic to endpoint handlers
&lt;span class="p"&gt;-&lt;/span&gt; Do not use &lt;span class="sb"&gt;`dynamic`&lt;/span&gt; or &lt;span class="sb"&gt;`object`&lt;/span&gt; as return types
&lt;span class="p"&gt;-&lt;/span&gt; Do not skip FluentValidation for command/query inputs
&lt;span class="p"&gt;-&lt;/span&gt; Do not add new NuGet packages without noting them here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file takes about fifteen minutes to write and saves hours of back-and-forth over the lifetime of a project. The investment is obvious once you have it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating a Starter CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;If you are not starting from scratch, Claude Code can inspect your repo and generate a first draft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads your solution structure, existing documentation, and common patterns, then writes a &lt;code&gt;CLAUDE.md&lt;/code&gt; that reflects what it found. Treat the output as a starting point — review it, correct anything wrong, and add the domain knowledge Claude cannot infer (architecture decisions, team conventions, known pitfalls).&lt;/p&gt;




&lt;h2&gt;
  
  
  Giving Claude Context
&lt;/h2&gt;

&lt;p&gt;Even with a great &lt;code&gt;CLAUDE.md&lt;/code&gt;, you will often need to point Claude at specific files during a conversation. The &lt;code&gt;@&lt;/code&gt; mention syntax does this precisely.&lt;/p&gt;

&lt;h3&gt;
  
  
  File and Directory Mentions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@Program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attaches the full content of &lt;code&gt;Program.cs&lt;/code&gt; to the current message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@src/OrderService.Application/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attaches a directory listing. Claude will then read specific files from it as needed, rather than loading everything upfront.&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;@appsettings.json#&lt;/span&gt;&lt;span class="mi"&gt;1-20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attaches only lines 1 through 20. Useful when you care about a specific section of a large config file and do not want to burn context on the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code Selection Sharing
&lt;/h3&gt;

&lt;p&gt;When you use the VS Code extension, any code you highlight in the editor is automatically shared when you open the Claude panel. You do not need &lt;code&gt;@&lt;/code&gt; mentions for your active selection — it is already there. This makes "explain this method" or "what does this LINQ query do" workflows completely frictionless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Long Contexts
&lt;/h3&gt;

&lt;p&gt;Long sessions accumulate context. After several hours of work, Claude's context window fills with earlier conversation turns that are no longer relevant. When responses start feeling less sharp, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This summarizes the conversation history into a compressed form, freeing up context space without losing the thread of what you have been working on. Think of it as telling the contractor to forget the small talk and remember only the decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Skills — Custom Commands for Your Workflow
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2Fi224kok5dtjqssagvfwf.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.amazonaws.com%2Fuploads%2Farticles%2Fi224kok5dtjqssagvfwf.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skills are reusable, invocable commands that extend Claude Code's default behavior. They live as Markdown files and you invoke them with a &lt;code&gt;/&lt;/code&gt; prefix, just like built-in commands. There are two scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal skills&lt;/strong&gt; — stored in &lt;code&gt;~/.claude/skills/&amp;lt;skill-name&amp;gt;/SKILL.md&lt;/code&gt;. Available in every project on your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project skills&lt;/strong&gt; — stored in &lt;code&gt;.claude/skills/&amp;lt;skill-name&amp;gt;/SKILL.md&lt;/code&gt; in your repo root. Checked into source control, shared with the team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A skill file is a Markdown document that tells Claude what to do when invoked. It can include steps, decision logic, expected outputs, and even inline prompts.&lt;/p&gt;

&lt;p&gt;Here are three practical examples for .NET development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skill: &lt;code&gt;/dotnet-test&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;.claude/skills/dotnet-test/SKILL.md&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# dotnet-test&lt;/span&gt;

Run the test suite for this project and summarize the results.

&lt;span class="gu"&gt;## Steps&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Run &lt;span class="sb"&gt;`dotnet test`&lt;/span&gt; using the command from CLAUDE.md (check the "Tests" section).
&lt;span class="p"&gt;2.&lt;/span&gt; Parse the output for failed tests.
&lt;span class="p"&gt;3.&lt;/span&gt; For each failure:
&lt;span class="p"&gt;   -&lt;/span&gt; Show the test name
&lt;span class="p"&gt;   -&lt;/span&gt; Show the failure message
&lt;span class="p"&gt;   -&lt;/span&gt; Show the stack trace trimmed to the first 5 lines
&lt;span class="p"&gt;4.&lt;/span&gt; If all tests pass, confirm with a short success message.
&lt;span class="p"&gt;5.&lt;/span&gt; If build errors prevent tests from running, show the build errors instead.

&lt;span class="gu"&gt;## Output Format&lt;/span&gt;

Group failures by project. Keep the summary concise — one paragraph max,
followed by a numbered list of failures if any exist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invoke with &lt;code&gt;/dotnet-test&lt;/code&gt;. Claude runs your test command, reads the output, and gives you a clean summary instead of a wall of terminal text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skill: &lt;code&gt;/csharp-review&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;.claude/skills/csharp-review/SKILL.md&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# csharp-review&lt;/span&gt;

Review the provided C# code for modern idiom usage and correctness.

&lt;span class="gu"&gt;## What to Check&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Records**&lt;/span&gt;: Are mutable classes used where &lt;span class="sb"&gt;`record`&lt;/span&gt; or &lt;span class="sb"&gt;`record struct`&lt;/span&gt; would be more appropriate?
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Init-only properties**&lt;/span&gt;: Are setters used where &lt;span class="sb"&gt;`init`&lt;/span&gt; would suffice?
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Pattern matching**&lt;/span&gt;: Are &lt;span class="sb"&gt;`if`&lt;/span&gt;/&lt;span class="sb"&gt;`switch`&lt;/span&gt; chains that could use &lt;span class="sb"&gt;`switch`&lt;/span&gt; expressions or positional patterns?
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Nullable annotations**&lt;/span&gt;: Are reference types properly annotated? Any &lt;span class="sb"&gt;`!`&lt;/span&gt; suppressors hiding problems?
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Primary constructors**&lt;/span&gt;: Could a class use C# 12+ primary constructor syntax?
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**`field` keyword**&lt;/span&gt;: Are there explicit backing fields that C# 14's &lt;span class="sb"&gt;`field`&lt;/span&gt; keyword could eliminate?
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Async**&lt;/span&gt;: Any &lt;span class="sb"&gt;`.Result`&lt;/span&gt;, &lt;span class="sb"&gt;`.Wait()`&lt;/span&gt;, or &lt;span class="sb"&gt;`async void`&lt;/span&gt; outside of event handlers?
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**LINQ**&lt;/span&gt;: Any imperative loops that would read more clearly as LINQ?

&lt;span class="gu"&gt;## Output Format&lt;/span&gt;

List each finding as:
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**[Category]**&lt;/span&gt; Description of the issue
&lt;span class="p"&gt;-&lt;/span&gt; Suggested rewrite (code block, 10 lines max)

End with a one-sentence overall assessment.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invoke with &lt;code&gt;/csharp-review&lt;/code&gt; while a file is open (or with &lt;code&gt;@filename&lt;/code&gt; if using the CLI). You get actionable, .NET-specific feedback rather than generic suggestions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skill: &lt;code&gt;/ef-migrate&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;.claude/skills/ef-migrate/SKILL.md&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ef-migrate&lt;/span&gt;

Guide through EF Core migration management for this project.

&lt;span class="gu"&gt;## Steps&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Run the &lt;span class="sb"&gt;`dotnet ef migrations list`&lt;/span&gt; command from CLAUDE.md to show existing migrations.
   Use the correct &lt;span class="sb"&gt;`--project`&lt;/span&gt; and &lt;span class="sb"&gt;`--startup-project`&lt;/span&gt; flags from CLAUDE.md.
&lt;span class="p"&gt;2.&lt;/span&gt; Show the migration list, noting which have been applied (indicated by [applied]).
&lt;span class="p"&gt;3.&lt;/span&gt; Ask the user: "Do you want to add a new migration, or apply pending ones?"
&lt;span class="p"&gt;4.&lt;/span&gt; If adding a migration:
&lt;span class="p"&gt;   -&lt;/span&gt; Ask for the migration name (PascalCase, descriptive, e.g., AddOrderStatusIndex)
&lt;span class="p"&gt;   -&lt;/span&gt; Run &lt;span class="sb"&gt;`dotnet ef migrations add &amp;lt;Name&amp;gt;`&lt;/span&gt; with correct flags
&lt;span class="p"&gt;   -&lt;/span&gt; Show which files were created
&lt;span class="p"&gt;   -&lt;/span&gt; Ask if they want to review the generated migration before applying
&lt;span class="p"&gt;5.&lt;/span&gt; If applying pending migrations:
&lt;span class="p"&gt;   -&lt;/span&gt; Confirm the number of pending migrations
&lt;span class="p"&gt;   -&lt;/span&gt; Run &lt;span class="sb"&gt;`dotnet ef database update`&lt;/span&gt; with correct flags
&lt;span class="p"&gt;   -&lt;/span&gt; Report success or errors

&lt;span class="gu"&gt;## Safety Note&lt;/span&gt;

Always mention that migrations modify the database schema. Remind the user to review
generated migrations before applying to staging or production environments.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This skill turns what is normally a multi-step, flag-heavy CLI workflow into a guided conversation. It uses the migration commands already defined in your &lt;code&gt;CLAUDE.md&lt;/code&gt;, so it adapts to each project automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP — Connecting External Tools
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol (MCP) is a standard that lets Claude connect to external data sources and tools beyond your local filesystem. Where Skills extend what Claude &lt;em&gt;does&lt;/em&gt;, MCP extends what Claude can &lt;em&gt;see&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A few examples relevant to .NET teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub MCP&lt;/strong&gt; — Claude can read PR diffs, check CI status, and leave review comments directly, without you copy-pasting GitHub URLs into the chat&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure DevOps MCP&lt;/strong&gt; — Claude can read your current sprint, query work items, and understand the backlog when you ask it to help prioritize or plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database MCP&lt;/strong&gt; — Claude can query your local or dev database schema to inform code generation, without you having to paste CREATE TABLE statements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP servers are configured in your Claude Code settings and are available across all sessions. For .NET teams working in Azure DevOps or GitHub, setting up the corresponding MCP server once pays dividends on every PR review and sprint planning task after that.&lt;/p&gt;




&lt;h2&gt;
  
  
  VS Code vs Visual Studio: An Honest Comparison
&lt;/h2&gt;

&lt;p&gt;Both IDEs are legitimate for .NET work. Here is where each one stands with Claude Code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;VS Code + Claude Extension&lt;/th&gt;
&lt;th&gt;Visual Studio 2022/2026 + Terminal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code integration&lt;/td&gt;
&lt;td&gt;Native extension, inline diffs, selection sharing&lt;/td&gt;
&lt;td&gt;Terminal only, CLI-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging experience&lt;/td&gt;
&lt;td&gt;Good (but not VS-level)&lt;/td&gt;
&lt;td&gt;Best in class for .NET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLAUDE.md support&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full (file not shown in Solution Explorer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills / MCP&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full (terminal invocation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Designer tools (WinForms, WPF)&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refactoring tools&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent (ReSharper-level built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot Reload / Edit and Continue&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Best in class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extension ecosystem&lt;/td&gt;
&lt;td&gt;Vast&lt;/td&gt;
&lt;td&gt;.NET-focused, deep OS integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Community free; Pro/Enterprise paid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pragmatic answer for most .NET developers: use both. VS Code for AI-assisted coding, exploration, and file editing; Visual Studio for debugging sessions, UI designers, and deep refactoring. They open the same project files and there is no conflict. Many developers keep both open simultaneously without issue.&lt;/p&gt;

&lt;p&gt;If you have to pick one: pure backend services (ASP.NET Core APIs, worker services, console apps) work well with VS Code as your primary. Anything with Windows Forms, WPF, or complex debugger needs warrants Visual Studio as primary with Claude Code in the terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLAUDE.md is not optional&lt;/strong&gt; — it is the difference between an AI that guesses and one that knows your codebase. Write it, commit it, keep it current. Run &lt;code&gt;/init&lt;/code&gt; to generate a starting point from your existing repo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual Studio is fully usable&lt;/strong&gt; — the lack of a native extension is a real limitation, but &lt;code&gt;View &amp;gt; Terminal&lt;/code&gt; and the &lt;code&gt;claude&lt;/code&gt; CLI give you full Claude Code functionality. The file-awareness and Skills system work identically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context is everything&lt;/strong&gt; — use &lt;code&gt;@&lt;/code&gt; mentions to target specific files, use line ranges to stay precise, and use &lt;code&gt;/compact&lt;/code&gt; when sessions get long. Precision in context leads to precision in output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skills are multipliers&lt;/strong&gt; — a fifteen-minute investment writing a project skill pays back immediately. Codify your team's review criteria, your test workflow, your migration process. Claude Code executes them consistently every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The model is not random — your context is&lt;/strong&gt; — when Claude gives you a bad answer, the fix is almost always better context: a clearer &lt;code&gt;CLAUDE.md&lt;/code&gt;, a more specific &lt;code&gt;@&lt;/code&gt; mention, or a well-written skill. Understanding the context system turns a frustrating tool into a reliable one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The architecture is simple once you see it: CLAUDE.md tells Claude who you are and how your project works, &lt;code&gt;@&lt;/code&gt; mentions tell Claude what to look at right now, and Skills tell Claude how to handle your recurring tasks. Stack those three things on top of a capable model and the "random magic" feeling goes away entirely. What you get instead is a coding assistant that behaves like someone who has actually read the codebase.&lt;/p&gt;

&lt;p&gt;Which, thanks to CLAUDE.md, it has.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code para desarrolladores .NET: De cero a productivo en VS Code y Visual Studio</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Mon, 15 Jun 2026 22:57:12 +0000</pubDate>
      <link>https://dev.to/jgomezdev/claude-code-para-desarrolladores-net-de-cero-a-productivo-en-vs-code-y-visual-studio-d4j</link>
      <guid>https://dev.to/jgomezdev/claude-code-para-desarrolladores-net-de-cero-a-productivo-en-vs-code-y-visual-studio-d4j</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code para desarrolladores .NET: De cero a productivo en VS Code y Visual Studio
&lt;/h1&gt;

&lt;p&gt;Las herramientas de IA se sienten mágicas los primeros días. Le preguntas algo, te responde bien, y piensas: "esto va a cambiar todo". Luego pasan unas semanas, el contexto se confunde, las respuestas se vuelven genéricas, y empiezas a sentir que estás teniendo una conversación con alguien que tiene amnesia selectiva.&lt;/p&gt;

&lt;p&gt;El problema no es la IA. El problema es que nadie te explicó cómo funciona realmente.&lt;/p&gt;

&lt;p&gt;Este artículo desmitifica Claude Code para desarrolladores .NET. No es un tutorial de "mira qué cool es esto" — es una guía práctica de cómo funciona, por qué funciona así, y cómo sacarle el máximo provecho en proyectos C# reales, tanto en VS Code como en Visual Studio 2022/2026.&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.amazonaws.com%2Fuploads%2Farticles%2Fey9jvse69x8fetk1p198.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.amazonaws.com%2Fuploads%2Farticles%2Fey9jvse69x8fetk1p198.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué es Claude Code (y qué no es)
&lt;/h2&gt;

&lt;p&gt;Claude Code &lt;strong&gt;no es&lt;/strong&gt; una ventana de chat glorificada. Es una CLI (interfaz de línea de comandos) que corre directamente en tu terminal y tiene acceso real a tu sistema de archivos, puede ejecutar comandos, leer y editar archivos, y mantener contexto sobre tu proyecto.&lt;/p&gt;

&lt;p&gt;La diferencia con "preguntarle a Claude en el navegador" es enorme:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En el navegador, pegas código manualmente, pierdes contexto entre sesiones, y Claude no sabe nada de tu proyecto más allá de lo que tú le dices.&lt;/li&gt;
&lt;li&gt;Con Claude Code, Claude &lt;strong&gt;vive en tu repositorio&lt;/strong&gt;. Lee archivos, ejecuta &lt;code&gt;dotnet build&lt;/code&gt;, corre tus tests, y entiende la arquitectura de tu solución.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Además de la CLI, existe una extensión para VS Code que integra Claude Code directamente en el editor — con contexto automático de lo que tienes abierto y seleccionado. Para Visual Studio, la historia es diferente (y la contaremos honestamente más adelante).&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalación y setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerrequisitos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 22 LTS o superior&lt;/li&gt;
&lt;li&gt;Una cuenta en &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt; con acceso a Claude Code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Instalar la CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifica la instalación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para iniciar una sesión interactiva en tu proyecto, navega al directorio raíz y ejecuta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VS Code — integración completa
&lt;/h3&gt;

&lt;p&gt;Instala la extensión oficial desde el Marketplace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nombre: &lt;strong&gt;Claude Code&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ID: &lt;code&gt;anthropic.claude-code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Requiere VS Code &lt;strong&gt;1.98 o superior&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una vez instalada, Claude Code aparece en la barra lateral. La integración es profunda: cuando tienes un archivo abierto o código seleccionado, Claude lo recibe automáticamente como contexto. No necesitas copiar y pegar nada.&lt;/p&gt;

&lt;p&gt;JetBrains Rider también cuenta con integración completa a través de su plugin oficial — si tu equipo usa Rider, el workflow es muy similar al de VS Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Studio 2022/2026 — la verdad sin rodeos
&lt;/h3&gt;

&lt;p&gt;Visual Studio ha sido el rey del desarrollo .NET por décadas. Simplemente aún no se ha enterado de que existe Claude.&lt;/p&gt;

&lt;p&gt;A junio de 2026, &lt;strong&gt;no existe una extensión oficial de Claude Code para Visual Studio&lt;/strong&gt;. Punto. No hay workaround mágico que cambie eso.&lt;/p&gt;

&lt;p&gt;Lo que sí funciona, y funciona bien, es usar la &lt;strong&gt;terminal integrada de Visual Studio&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ve a &lt;strong&gt;Ver &amp;gt; Terminal&lt;/strong&gt; (o &lt;code&gt;Ctrl&lt;/code&gt; + backtick)&lt;/li&gt;
&lt;li&gt;Navega al directorio raíz de tu solución (normalmente ya estás ahí)&lt;/li&gt;
&lt;li&gt;Ejecuta &lt;code&gt;claude&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Claude lee tu repositorio exactamente igual que en VS Code. La diferencia es que no tienes el contexto visual automático — tendrás que mencionar archivos explícitamente con &lt;code&gt;@&lt;/code&gt; (lo veremos en detalle más adelante). Para muchas tareas de refactoring, generación de código y revisión de arquitectura, la terminal es más que suficiente.&lt;/p&gt;

&lt;p&gt;Muchos desarrolladores .NET tienen ambos IDEs abiertos en paralelo: Visual Studio para debugging avanzado, diseñadores de formularios, y herramientas de profiling; VS Code para trabajar con Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLAUDE.md — el cerebro de la operación
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2F50uz0mgy30s8r44te03z.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.amazonaws.com%2Fuploads%2Farticles%2F50uz0mgy30s8r44te03z.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Aquí está el insight más importante del artículo, así que léelo despacio.&lt;/p&gt;

&lt;p&gt;Claude Code no funciona como un chatbot con memoria persistente. Cada vez que inicias una sesión, parte de cero. Entonces, ¿cómo "conoce" tu proyecto? A través de un archivo llamado &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo Claude descubre CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;Cuando ejecutas &lt;code&gt;claude&lt;/code&gt; desde un directorio, Claude realiza un &lt;strong&gt;recorrido del filesystem hacia arriba&lt;/strong&gt; desde tu directorio de trabajo actual (CWD), buscando archivos &lt;code&gt;CLAUDE.md&lt;/code&gt;. Además, carga &lt;code&gt;CLAUDE.md&lt;/code&gt; de los subdirectorios de forma lazy a medida que accede a ellos.&lt;/p&gt;

&lt;p&gt;Esto significa dos cosas importantes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No está basado en archivos de proyecto&lt;/strong&gt; — Claude no lee tu &lt;code&gt;.csproj&lt;/code&gt; para descubrir CLAUDE.md. Lo descubre por ubicación en el filesystem. Si tu &lt;code&gt;CLAUDE.md&lt;/code&gt; está en la raíz del repo y ejecutas &lt;code&gt;claude&lt;/code&gt; desde ahí, se carga. Si lo ejecutas desde un subdirectorio, también lo cargará (porque sube hacia arriba).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;En Visual Studio, CLAUDE.md no aparece en el Explorador de Soluciones&lt;/strong&gt; — porque no está referenciado en ningún &lt;code&gt;.csproj&lt;/code&gt;. Pero Claude &lt;strong&gt;sí lo carga&lt;/strong&gt;. No necesitas agregarlo al proyecto. De hecho, es mejor que no lo hagas — es un archivo de contexto para la IA, no un recurso de tu aplicación.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verificar qué está cargado
&lt;/h3&gt;

&lt;p&gt;Puedes verificar en cualquier momento qué archivos CLAUDE.md tiene cargados Claude con el comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto muestra el contenido actual de memoria, incluyendo todos los CLAUDE.md activos en la sesión.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generar un CLAUDE.md inicial
&lt;/h3&gt;

&lt;p&gt;Si tu proyecto no tiene CLAUDE.md, Claude puede generarlo leyendo el repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto analiza tu código, estructura de carpetas, y archivos de configuración, y produce un CLAUDE.md base que puedes refinar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ejemplo práctico: CLAUDE.md para un proyecto .NET
&lt;/h3&gt;

&lt;p&gt;Aquí un ejemplo completo y realista para una API de e-commerce en .NET 10:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ECommerceApi — Contexto para Claude Code&lt;/span&gt;

&lt;span class="gu"&gt;## Descripción del proyecto&lt;/span&gt;
API REST para gestión de catálogo y órdenes de compra.
Stack: .NET 10, C# 14, Entity Framework Core 9, PostgreSQL, MassTransit (RabbitMQ).
Arquitectura: Clean Architecture con CQRS via MediatR.

&lt;span class="gu"&gt;## Comandos esenciales&lt;/span&gt;

&lt;span class="gu"&gt;### Build y tests&lt;/span&gt;
dotnet build ECommerceApi.sln
dotnet test ECommerceApi.sln --logger "console;verbosity=normal"
dotnet test --filter "Category=Unit"
dotnet test --filter "Category=Integration"

&lt;span class="gu"&gt;### Migraciones EF Core&lt;/span&gt;
cd src/ECommerceApi.Infrastructure
dotnet ef migrations add &lt;span class="nt"&gt;&amp;lt;NombreMigracion&amp;gt;&lt;/span&gt; --startup-project ../ECommerceApi.Api
dotnet ef database update --startup-project ../ECommerceApi.Api

&lt;span class="gu"&gt;### Ejecución local&lt;/span&gt;
cd src/ECommerceApi.Api
dotnet run

&lt;span class="gu"&gt;## Estructura del proyecto&lt;/span&gt;
src/
  ECommerceApi.Api/           # Controllers, middleware, DI setup
  ECommerceApi.Application/   # Commands, queries, DTOs, validators
  ECommerceApi.Domain/        # Entities, value objects, domain events
  ECommerceApi.Infrastructure/ # EF Core, repositories, external services
tests/
  ECommerceApi.UnitTests/
  ECommerceApi.IntegrationTests/

&lt;span class="gu"&gt;## Convenciones de código&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Usa records para DTOs y value objects
&lt;span class="p"&gt;-&lt;/span&gt; Usa init-only properties en domain entities cuando sea posible
&lt;span class="p"&gt;-&lt;/span&gt; Pattern matching exhaustivo en switches sobre discriminated unions
&lt;span class="p"&gt;-&lt;/span&gt; Nullable reference types habilitados — sin supresiones con !
&lt;span class="p"&gt;-&lt;/span&gt; No uses var — tipado explícito siempre
&lt;span class="p"&gt;-&lt;/span&gt; Nombres en inglés para código, comentarios pueden ser en español

&lt;span class="gu"&gt;## Decisiones de arquitectura importantes&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Los controllers SOLO llaman a MediatR.Send() — lógica cero en controllers
&lt;span class="p"&gt;-&lt;/span&gt; Validaciones con FluentValidation en el pipeline de MediatR (no en controllers)
&lt;span class="p"&gt;-&lt;/span&gt; Los domain events se despachan DESPUÉS de SaveChanges (outbox pattern)
&lt;span class="p"&gt;-&lt;/span&gt; Los integration tests levantan PostgreSQL en Docker via Testcontainers

&lt;span class="gu"&gt;## Lo que NO hacer&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; No agregar lógica de negocio en Infrastructure
&lt;span class="p"&gt;-&lt;/span&gt; No inyectar DbContext directamente en Application — solo via repositorios
&lt;span class="p"&gt;-&lt;/span&gt; No usar AutoMapper — mappings explícitos con métodos ToDto() / ToDomain()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este archivo transforma a Claude de "asistente genérico de C#" a "colega que conoce tu proyecto". La diferencia en calidad de respuestas es notable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dando contexto a Claude
&lt;/h2&gt;

&lt;p&gt;Tener un buen CLAUDE.md es el 50% del trabajo. El otro 50% es saber cómo dar contexto durante la sesión.&lt;/p&gt;

&lt;h3&gt;
  
  
  Menciones con @
&lt;/h3&gt;

&lt;p&gt;La sintaxis &lt;code&gt;@&lt;/code&gt; te permite referenciar archivos y directorios específicos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Program.cs                    # Archivo completo
@src/Application/              # Directorio entero
@appsettings.json#1-20         # Líneas 1 a 20 del archivo
@src/Domain/Entities/Order.cs  # Archivo en ruta relativa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ejemplos de uso en conversación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Revisa @src/Application/Orders/Commands/CreateOrderCommand.cs
y dime si hay algo que mejorar con los nuevos features de C# 14.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tengo un bug en @src/Infrastructure/Repositories/OrderRepository.cs#45-80.
El método GetPendingOrders() devuelve órdenes canceladas. ¿Ves el problema?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Contexto automático en VS Code
&lt;/h3&gt;

&lt;p&gt;En VS Code con la extensión instalada, el archivo que tienes abierto en el editor activo se comparte automáticamente como contexto. Si seleccionas un bloque de código, solo ese bloque se incluye. Esto elimina mucho del trabajo manual de menciones &lt;code&gt;@&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En Visual Studio (terminal), necesitas ser explícito con &lt;code&gt;@&lt;/code&gt; — lo cual en realidad te obliga a ser más preciso sobre qué quieres mostrarle a Claude, lo cual no es necesariamente malo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gestionar el contexto largo con /compact
&lt;/h3&gt;

&lt;p&gt;Las sesiones largas tienen un límite de tokens. Cuando el contexto se alarga demasiado, Claude puede empezar a "olvidar" cosas del inicio de la conversación.&lt;/p&gt;

&lt;p&gt;El comando &lt;code&gt;/compact&lt;/code&gt; resume el historial de la sesión actual, preservando los puntos clave pero reduciendo el tamaño del contexto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Úsalo proactivamente cuando llevas un rato en la misma sesión, antes de llegar al límite. Es como hacer un checkpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills — comandos personalizados para tu flujo
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2F751yhzrhzjyr1jgnl0ob.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.amazonaws.com%2Fuploads%2Farticles%2F751yhzrhzjyr1jgnl0ob.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los Skills son la característica más poderosa de Claude Code para equipos y proyectos recurrentes. Un Skill es esencialmente un &lt;strong&gt;comando personalizado&lt;/strong&gt; que encapsula un flujo de trabajo completo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dónde viven los Skills
&lt;/h3&gt;

&lt;p&gt;Hay dos ubicaciones:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ubicación&lt;/th&gt;
&lt;th&gt;Alcance&lt;/th&gt;
&lt;th&gt;Ruta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Personal&lt;/td&gt;
&lt;td&gt;Solo tú, todos tus proyectos&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/skills/&amp;lt;nombre&amp;gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proyecto&lt;/td&gt;
&lt;td&gt;Todo el equipo, este repo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/skills/&amp;lt;nombre&amp;gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Los Skills de proyecto se versiona junto al código — cuando alguien hace &lt;code&gt;git clone&lt;/code&gt;, obtiene los Skills también. Ideal para workflows específicos del equipo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo invocar un Skill
&lt;/h3&gt;

&lt;p&gt;Simplemente escribe &lt;code&gt;/nombre-del-skill&lt;/code&gt; en Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dotnet-test
/csharp-review
/ef-migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude lee el archivo &lt;code&gt;SKILL.md&lt;/code&gt; correspondiente y ejecuta el flujo definido en él.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ejemplo 1: &lt;code&gt;/dotnet-test&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Crea el archivo &lt;code&gt;.claude/skills/dotnet-test/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# dotnet-test&lt;/span&gt;

Ejecuta la suite de tests del proyecto y presenta los resultados de forma clara.

&lt;span class="gu"&gt;## Pasos&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Ejecuta &lt;span class="sb"&gt;`dotnet test ECommerceApi.sln --logger "console;verbosity=normal"`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Si hay fallos, muestra SOLO los tests fallidos con:
&lt;span class="p"&gt;   -&lt;/span&gt; Nombre completo del test
&lt;span class="p"&gt;   -&lt;/span&gt; El mensaje de error
&lt;span class="p"&gt;   -&lt;/span&gt; La línea del código donde falló
&lt;span class="p"&gt;3.&lt;/span&gt; Si todos pasan, confirma con un resumen: total de tests, tiempo de ejecución
&lt;span class="p"&gt;4.&lt;/span&gt; Si hay errores de compilación, muéstralos primero antes de hablar de tests

&lt;span class="gu"&gt;## Notas&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; No muestres los tests que pasaron a menos que el usuario lo pida
&lt;span class="p"&gt;-&lt;/span&gt; Si el build falla, no intentes interpretar qué tests fallaron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ejemplo 2: &lt;code&gt;/csharp-review&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Crea &lt;code&gt;.claude/skills/csharp-review/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# csharp-review&lt;/span&gt;

Revisa código C# buscando oportunidades de modernización con C# 14 / .NET 10.

&lt;span class="gu"&gt;## Qué revisar&lt;/span&gt;

&lt;span class="gu"&gt;### Records y tipos de valor&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se puede reemplazar una clase inmutable con un record?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Los DTOs usan record en vez de class?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se usan init-only properties donde corresponde?

&lt;span class="gu"&gt;### Pattern matching&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay cadenas de if/else que se pueden reemplazar con switch expressions?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Los switches son exhaustivos sobre discriminated unions?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se usa list patterns donde aplica?

&lt;span class="gu"&gt;### Nullable reference types&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay supresiones con ! sin justificación?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Los parámetros nullable están correctamente anotados?

&lt;span class="gu"&gt;### field keyword (C# 14)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay propiedades con backing fields explícitos que se pueden simplificar con field?

&lt;span class="gu"&gt;### Async/await&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ¿Hay .Result o .Wait() que deberían ser await?
&lt;span class="p"&gt;-&lt;/span&gt; ¿Se usa ConfigureAwait correctamente en librerías?

&lt;span class="gu"&gt;## Formato de respuesta&lt;/span&gt;
Para cada issue encontrado:
&lt;span class="p"&gt;-&lt;/span&gt; Código original
&lt;span class="p"&gt;-&lt;/span&gt; Código mejorado
&lt;span class="p"&gt;-&lt;/span&gt; Una línea explicando por qué es mejor

No reportes lo que ya está bien — solo oportunidades de mejora.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ejemplo 3: &lt;code&gt;/ef-migrate&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Crea &lt;code&gt;.claude/skills/ef-migrate/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ef-migrate&lt;/span&gt;

Guía para agregar una migración de Entity Framework Core al proyecto.

&lt;span class="gu"&gt;## Pasos&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Ejecuta &lt;span class="sb"&gt;`dotnet ef migrations list --project src/ECommerceApi.Infrastructure --startup-project src/ECommerceApi.Api`&lt;/span&gt;
   para mostrar las migraciones existentes
&lt;span class="p"&gt;
2.&lt;/span&gt; Pregunta al usuario:
&lt;span class="p"&gt;   -&lt;/span&gt; ¿Qué cambio hiciste en el modelo de dominio?
&lt;span class="p"&gt;   -&lt;/span&gt; ¿Qué nombre quieres para la migración? (sugerencia: usa PascalCase descriptivo, ej: AddOrderShippingAddress)
&lt;span class="p"&gt;
3.&lt;/span&gt; Ejecuta:
   &lt;span class="sb"&gt;`dotnet ef migrations add &amp;lt;NombreMigracion&amp;gt; --project src/ECommerceApi.Infrastructure --startup-project src/ECommerceApi.Api`&lt;/span&gt;
&lt;span class="p"&gt;
4.&lt;/span&gt; Lee el archivo de migración generado y verifica:
&lt;span class="p"&gt;   -&lt;/span&gt; Que el Up() tiene el cambio esperado
&lt;span class="p"&gt;   -&lt;/span&gt; Que el Down() revierte correctamente
&lt;span class="p"&gt;   -&lt;/span&gt; Que no hay columnas &lt;span class="sb"&gt;`nvarchar(max)`&lt;/span&gt; donde debería haber un tamaño específico
&lt;span class="p"&gt;
5.&lt;/span&gt; Si el usuario quiere aplicar la migración:
   &lt;span class="sb"&gt;`dotnet ef database update --project src/ECommerceApi.Infrastructure --startup-project src/ECommerceApi.Api`&lt;/span&gt;

&lt;span class="gu"&gt;## Notas&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Nunca ejecutes database update en producción desde aquí — solo desarrollo local
&lt;span class="p"&gt;-&lt;/span&gt; Si hay conflictos de migración, muestra el error completo antes de sugerir soluciones
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Estos tres Skills solos ya justifican el setup. Imagina tener un equipo de 10 personas donde todos invocan &lt;code&gt;/csharp-review&lt;/code&gt; y obtienen el mismo estándar de revisión.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP — conectando herramientas externas
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) es el mecanismo que permite a Claude Code conectarse con servicios externos como si fueran herramientas nativas.&lt;/p&gt;

&lt;p&gt;En términos simples: un servidor MCP expone capacidades (leer PRs, consultar tickets, buscar documentación) y Claude puede usarlas durante la sesión.&lt;/p&gt;

&lt;p&gt;Dos ejemplos relevantes para equipos .NET:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub MCP&lt;/strong&gt; — Claude puede leer los PRs abiertos del repo, ver los comentarios de revisión, y ayudarte a resolverlos sin que copies nada manualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;¿Qué cambios pendientes tengo en el PR #247?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Azure DevOps MCP&lt;/strong&gt; — Claude puede ver el contexto del sprint actual, las user stories asignadas, y entender qué estás construyendo sin que tengas que explicarlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;¿Cuáles son los criterios de aceptación de la historia US-1089?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los servidores MCP se configuran en &lt;code&gt;~/.claude/mcp.json&lt;/code&gt; o &lt;code&gt;.claude/mcp.json&lt;/code&gt; a nivel de proyecto. La documentación oficial de Claude Code tiene la lista de servidores MCP disponibles y cómo configurarlos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo usar VS Code vs Visual Studio
&lt;/h2&gt;

&lt;p&gt;La pregunta que todo desarrollador .NET acaba haciendo. La respuesta honesta:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterio&lt;/th&gt;
&lt;th&gt;VS Code + Claude Code&lt;/th&gt;
&lt;th&gt;Visual Studio 2022/2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Integración con Claude Code&lt;/td&gt;
&lt;td&gt;Completa — contexto automático, extensión nativa&lt;/td&gt;
&lt;td&gt;Funcional — terminal integrada, sin extensión&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging de aplicaciones .NET&lt;/td&gt;
&lt;td&gt;Bueno (con extensión C#)&lt;/td&gt;
&lt;td&gt;Excelente — el mejor de la industria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IntelliSense y refactoring&lt;/td&gt;
&lt;td&gt;Bueno&lt;/td&gt;
&lt;td&gt;Excepcional — Roslyn completo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diseñadores visuales (WinForms, WPF, MAUI)&lt;/td&gt;
&lt;td&gt;No disponible&lt;/td&gt;
&lt;td&gt;Completos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profiling y diagnósticos&lt;/td&gt;
&lt;td&gt;Limitado&lt;/td&gt;
&lt;td&gt;Herramientas de clase mundial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup y rendimiento del IDE&lt;/td&gt;
&lt;td&gt;Rápido&lt;/td&gt;
&lt;td&gt;Lento en proyectos grandes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desarrollo multiplataforma&lt;/td&gt;
&lt;td&gt;Ideal&lt;/td&gt;
&lt;td&gt;Limitado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trabajo con soluciones grandes (50+ proyectos)&lt;/td&gt;
&lt;td&gt;Puede ser lento&lt;/td&gt;
&lt;td&gt;Optimizado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills y workflows de equipo&lt;/td&gt;
&lt;td&gt;Integración nativa&lt;/td&gt;
&lt;td&gt;Via terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Recomendación práctica:&lt;/strong&gt; No elijas uno. Úsalos en paralelo.&lt;/p&gt;

&lt;p&gt;Visual Studio sigue siendo insustituible para debugging pesado, profiling de memoria, y diseñadores de UI de escritorio. VS Code con Claude Code es donde vas a escribir código más rápido, refactorizar más limpio, y entender más fácil código ajeno.&lt;/p&gt;

&lt;p&gt;Muchos equipos .NET han adoptado el siguiente flujo: VS Code para el día a día con Claude Code activo, Visual Studio cuando necesitan el debugger avanzado o las herramientas de diagnóstico. Los dos conviven perfectamente apuntando al mismo proyecto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Puntos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLAUDE.md es tu punto de partida&lt;/strong&gt; — sin él, Claude no conoce tu proyecto. Con él, se convierte en un colaborador que entiende tu arquitectura, tus convenciones y tus decisiones técnicas. Ejecuta &lt;code&gt;/init&lt;/code&gt; para generarlo y refínalo con el tiempo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La integración de VS Code es completa; Visual Studio usa la terminal&lt;/strong&gt; — en VS Code, la extensión &lt;code&gt;anthropic.claude-code&lt;/code&gt; (requiere 1.98+) te da contexto automático. En Visual Studio, usa &lt;code&gt;Ver &amp;gt; Terminal&lt;/code&gt; y corre &lt;code&gt;claude&lt;/code&gt; ahí. Funciona igual de bien para la mayoría de tareas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Las menciones &lt;code&gt;@&lt;/code&gt; son tu herramienta de precisión&lt;/strong&gt; — &lt;code&gt;@archivo.cs&lt;/code&gt;, &lt;code&gt;@directorio/&lt;/code&gt;, y &lt;code&gt;@archivo.cs#10-40&lt;/code&gt; te permiten dar a Claude exactamente el contexto que necesita. En VS Code, el archivo activo se comparte automáticamente; en Visual Studio, sé explícito.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Los Skills son tu ventaja de equipo&lt;/strong&gt; — crea Skills en &lt;code&gt;.claude/skills/&lt;/code&gt; y versiónalos junto al código. Un &lt;code&gt;/csharp-review&lt;/code&gt; consistente para todo el equipo es más valioso que cualquier guideline de código que nadie lee.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gestiona el contexto activamente&lt;/strong&gt; — usa &lt;code&gt;/compact&lt;/code&gt; antes de que se alargue demasiado la sesión, y usa &lt;code&gt;/memory&lt;/code&gt; para verificar qué CLAUDE.md tiene cargado. Entender cómo Claude descubre el contexto (recorrido del filesystem, no archivos de proyecto) elimina mucha confusión.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Claude Code no reemplaza tu conocimiento de .NET — lo amplifica. La diferencia entre usarlo mediocremente y usarlo bien está en esas tres cosas: un buen &lt;code&gt;CLAUDE.md&lt;/code&gt;, contexto preciso con &lt;code&gt;@&lt;/code&gt;, y Skills que encapsulan tu flujo de trabajo. Con eso, la magia deja de parecer aleatoria.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>C# 14: The `field` Keyword — Cleaner Properties, Zero Boilerplate</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Wed, 10 Jun 2026 21:20:58 +0000</pubDate>
      <link>https://dev.to/jgomezdev/c-14-the-field-keyword-cleaner-properties-zero-boilerplate-1eac</link>
      <guid>https://dev.to/jgomezdev/c-14-the-field-keyword-cleaner-properties-zero-boilerplate-1eac</guid>
      <description>&lt;h1&gt;
  
  
  C# 14: The &lt;code&gt;field&lt;/code&gt; Keyword — Cleaner Properties, Zero Boilerplate
&lt;/h1&gt;

&lt;p&gt;Every C# developer has been there. You start with a clean auto-property, then requirements change and you need to add a tiny bit of validation. Suddenly that one-liner explodes into six lines of boilerplate — a private backing field, a getter that just returns it, a setter that assigns it. The logic is two words. The ceremony is everything else.&lt;/p&gt;

&lt;p&gt;C# 14 fixes this with the &lt;code&gt;field&lt;/code&gt; keyword: a contextual keyword that refers to the compiler-synthesized backing field of a property, letting you write custom accessor logic without ever declaring an explicit field.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Boilerplate Tax on Simple Properties
&lt;/h2&gt;

&lt;p&gt;Auto-properties are one of C#'s best quality-of-life features. This is clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the moment you need to trim whitespace on assignment, that cleanness evaporates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have six lines — and four of them exist only to hold the shape of the pattern together. The backing field &lt;code&gt;_username&lt;/code&gt; is not carrying any meaningful design weight. Its only job is to be a storage slot that &lt;code&gt;Username&lt;/code&gt; uses privately. You already &lt;em&gt;know&lt;/em&gt; the compiler creates one for auto-properties. You are just forced to make it visible so you can reference it.&lt;/p&gt;

&lt;p&gt;This is the boilerplate tax. You pay it every time you add even the smallest piece of logic to a property.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;field&lt;/code&gt; Exists
&lt;/h2&gt;

&lt;p&gt;The C# language team has discussed this friction for years. The challenge was finding syntax that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unambiguous&lt;/strong&gt; — no conflict with existing identifiers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Familiar&lt;/strong&gt; — consistent with how &lt;code&gt;value&lt;/code&gt; works in setters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped&lt;/strong&gt; — only meaningful inside a property accessor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution landed in C# 14: the contextual keyword &lt;code&gt;field&lt;/code&gt;. Just like &lt;code&gt;value&lt;/code&gt; refers to the incoming assignment in a setter, &lt;code&gt;field&lt;/code&gt; refers to the hidden backing storage the compiler manages for the property.&lt;/p&gt;

&lt;p&gt;It is contextual, which means it only acts as a keyword inside a property accessor. Everywhere else in your code, &lt;code&gt;field&lt;/code&gt; is still a valid identifier name (with one gotcha covered later).&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Syntax
&lt;/h3&gt;

&lt;p&gt;Here is the same username property rewritten with &lt;code&gt;field&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;get&lt;/code&gt; accessor is still auto-implemented — it just returns &lt;code&gt;field&lt;/code&gt;. The &lt;code&gt;set&lt;/code&gt; accessor does its one job: trim the incoming value before storing it. The private backing field is gone. The compiler synthesizes it behind the scenes, and &lt;code&gt;field&lt;/code&gt; lets you reach into it.&lt;/p&gt;

&lt;p&gt;You can also go the other way — implement &lt;code&gt;get&lt;/code&gt; and auto-implement &lt;code&gt;set&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or implement both sides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Age cannot be negative."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Works with &lt;code&gt;init&lt;/code&gt; Too
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;init&lt;/code&gt;-only properties work exactly the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;init&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful in immutable record-style classes where you want normalization on construction but no mutation afterwards.&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.amazonaws.com%2Fuploads%2Farticles%2Fmghfnswxr5wdhxj6ccqz.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.amazonaws.com%2Fuploads%2Farticles%2Fmghfnswxr5wdhxj6ccqz.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Usage Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Input Normalization
&lt;/h3&gt;

&lt;p&gt;The most common use case. Trim strings, normalize casing, sanitize user input — all without a backing field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three properties look almost as clean as auto-properties, and each enforces its own invariant at the point of assignment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Range Validation
&lt;/h3&gt;

&lt;p&gt;Numeric properties that must stay within bounds are a perfect fit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductListing&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;99_999.99m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNegative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;StockQuantity&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Quietly clamp negatives — intentional product decision&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;ArgumentOutOfRangeException.ThrowIfNegative&lt;/code&gt; and &lt;code&gt;ThrowIfGreaterThan&lt;/code&gt; — those are .NET 8+ helpers that are cleaner than manual &lt;code&gt;if&lt;/code&gt; throws. Use the modern APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Lazy Initialization
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; shines for lazy-loaded properties. The classic pattern used to need a nullable backing field and a null-check dance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before C# 14 — the old dance&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;field&lt;/code&gt;, you write the same logic right inside the property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. The compiler owns the backing field. You own the logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Change Notification (INotifyPropertyChanged)
&lt;/h3&gt;

&lt;p&gt;ViewModel properties in MVVM apps often look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.ViewModels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotifyPropertyChanged&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;PropertyChangedEventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CustomerName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomerName&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No private fields. No name collision risks. The pattern is still verbose (that is just the nature of &lt;code&gt;INotifyPropertyChanged&lt;/code&gt;), but &lt;code&gt;field&lt;/code&gt; removes one moving part from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;field&lt;/code&gt; Gotcha: When Your Property Is Named &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here is where things get interesting. Because &lt;code&gt;field&lt;/code&gt; is a &lt;em&gt;contextual&lt;/em&gt; keyword, a property actually named &lt;code&gt;Field&lt;/code&gt; (or &lt;code&gt;field&lt;/code&gt; in a case-sensitive context) can shadow the keyword. Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This property is named "field" — uncommon, but legal&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Inside here, does "field" mean the keyword or the property itself?&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Ambiguous!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler will warn you about the ambiguity. The fix is the verbatim identifier &lt;code&gt;@field&lt;/code&gt;, which forces C# to treat it as an identifier, not the keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Explicit: this is the identifier, not the keyword&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Realistically, naming a property &lt;code&gt;field&lt;/code&gt; is unusual. But if you work with code that maps to external schemas or databases that use reserved names, now you know the escape hatch.&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.amazonaws.com%2Fuploads%2Farticles%2Fj05pmckv9shu56vjy8v6.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.amazonaws.com%2Fuploads%2Farticles%2Fj05pmckv9shu56vjy8v6.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;field&lt;/code&gt; whenever you need &lt;em&gt;some&lt;/em&gt; logic in a property accessor but the property is still the sole owner of its value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalization on assignment&lt;/strong&gt; — trimming, casing, formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation on assignment&lt;/strong&gt; — range checks, null guards, format checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy initialization&lt;/strong&gt; — compute-on-first-access patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple change tracking&lt;/strong&gt; — equality check before raising events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;init&lt;/code&gt;-only normalization&lt;/strong&gt; — immutable objects that need clean input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb: if you would previously have written a private backing field that &lt;em&gt;nothing else in the class references&lt;/em&gt;, replace it with &lt;code&gt;field&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When NOT to Use &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; is not a silver bullet. There are cases where an explicit backing field is still the right tool:&lt;/p&gt;

&lt;h3&gt;
  
  
  The constructor bypass problem
&lt;/h3&gt;

&lt;p&gt;Suppose you have a property that raises an event when assigned. You want the constructor to set the initial value &lt;em&gt;silently&lt;/em&gt;, without firing the event. With &lt;code&gt;field&lt;/code&gt;, you cannot do that — the only way into the backing storage is through the property accessor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sensor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The event fires on every set — we want to skip it in the constructor&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Temperature&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Sensor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This fires ReadingChanged during construction — probably not what you want&lt;/span&gt;
        &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need the constructor to bypass the property's logic, keep the explicit backing field and set it directly from the constructor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple properties sharing state
&lt;/h3&gt;

&lt;p&gt;If two or more properties read from or write to the same underlying value — for example a &lt;code&gt;Radius&lt;/code&gt; and a &lt;code&gt;Diameter&lt;/code&gt; that are computed from each other — you need an explicit field that both properties reference. &lt;code&gt;field&lt;/code&gt; is per-property; it cannot be shared.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complex derived state
&lt;/h3&gt;

&lt;p&gt;If the "backing storage" is not a simple value but involves multiple fields, caches, or objects, an explicit field with a descriptive name communicates intent far better than the anonymous &lt;code&gt;field&lt;/code&gt; keyword.&lt;/p&gt;

&lt;h3&gt;
  
  
  When a name is important for debugging
&lt;/h3&gt;

&lt;p&gt;Explicit backing fields appear in debuggers with their declared name. The compiler-synthesized backing field for &lt;code&gt;field&lt;/code&gt;-based properties gets a generated name (like &lt;code&gt;&amp;lt;Username&amp;gt;k__BackingField&lt;/code&gt;) that is less readable in memory dumps or diagnostic tools. For hot paths under heavy diagnostics, you might prefer the explicit field.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;field&lt;/code&gt; is a contextual keyword in C# 14 that refers to the compiler-synthesized backing field of a property — the same one auto-properties already create.&lt;/li&gt;
&lt;li&gt;It lets you add logic to one or both accessors without promoting the backing field to an explicit private member.&lt;/li&gt;
&lt;li&gt;It works in &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;, and &lt;code&gt;init&lt;/code&gt; accessors, and you can mix auto-implementation with &lt;code&gt;field&lt;/code&gt;-based implementation in the same property.&lt;/li&gt;
&lt;li&gt;The gotcha to remember: if a property is named &lt;code&gt;field&lt;/code&gt;, use &lt;code&gt;@field&lt;/code&gt; to disambiguate the identifier from the keyword.&lt;/li&gt;
&lt;li&gt;Prefer an explicit backing field when: the constructor needs to bypass property logic, multiple properties share one storage slot, or the field name matters for diagnostics.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>C# 14: La keyword `field` — Propiedades más limpias, cero boilerplate</title>
      <dc:creator>Juan Gómez</dc:creator>
      <pubDate>Wed, 10 Jun 2026 21:18:31 +0000</pubDate>
      <link>https://dev.to/jgomezdev/c-14-la-keyword-field-propiedades-mas-limpias-cero-boilerplate-579i</link>
      <guid>https://dev.to/jgomezdev/c-14-la-keyword-field-propiedades-mas-limpias-cero-boilerplate-579i</guid>
      <description>&lt;h1&gt;
  
  
  C# 14: La keyword &lt;code&gt;field&lt;/code&gt; — Propiedades más limpias, cero boilerplate
&lt;/h1&gt;

&lt;p&gt;Todo desarrollador de C# lo ha vivido. Empiezas con una auto-propiedad limpia, llegan nuevos requerimientos y necesitas agregar una pequeña validación. De repente esa línea explota en seis líneas de boilerplate — un campo privado, un getter que solo lo retorna, un setter que lo asigna. La lógica son dos palabras. La ceremonia es todo lo demás.&lt;/p&gt;

&lt;p&gt;C# 14 resuelve esto con la keyword &lt;code&gt;field&lt;/code&gt;: una keyword contextual que hace referencia al backing field sintetizado por el compilador de una propiedad, permitiéndote escribir lógica personalizada en los accessors sin necesidad de declarar un campo explícito.&lt;/p&gt;




&lt;h2&gt;
  
  
  El problema: el impuesto del boilerplate
&lt;/h2&gt;

&lt;p&gt;Las auto-propiedades son una de las mejores mejoras de calidad de vida en C#. Esto es limpio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pero en el momento en que necesitas hacer un trim del espacio en blanco al asignar, esa limpieza desaparece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora tienes seis líneas — y cuatro de ellas existen únicamente para mantener la forma del patrón. El campo privado &lt;code&gt;_username&lt;/code&gt; no aporta ningún peso de diseño significativo. Su único trabajo es ser un slot de almacenamiento que &lt;code&gt;Username&lt;/code&gt; usa de forma privada. Ya &lt;em&gt;sabes&lt;/em&gt; que el compilador crea uno para las auto-propiedades. Solo estás obligado a hacerlo visible para poder referenciarlo.&lt;/p&gt;

&lt;p&gt;Este es el impuesto del boilerplate. Lo pagas cada vez que agregas la más mínima lógica a una propiedad.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por qué existe &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;El equipo de C# ha discutido esta fricción durante años. El desafío era encontrar una sintaxis que fuera:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inequívoca&lt;/strong&gt; — sin conflicto con identificadores existentes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Familiar&lt;/strong&gt; — consistente con cómo funciona &lt;code&gt;value&lt;/code&gt; en los setters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acotada&lt;/strong&gt; — con significado solo dentro de un accessor de propiedad&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La solución llegó en C# 14: la keyword contextual &lt;code&gt;field&lt;/code&gt;. Igual que &lt;code&gt;value&lt;/code&gt; hace referencia al valor entrante en un setter, &lt;code&gt;field&lt;/code&gt; hace referencia al almacenamiento interno que el compilador administra para la propiedad.&lt;/p&gt;

&lt;p&gt;Es contextual, lo que significa que solo actúa como keyword dentro de un accessor de propiedad. En el resto de tu código, &lt;code&gt;field&lt;/code&gt; sigue siendo un nombre de identificador válido (con una trampa que veremos más adelante).&lt;/p&gt;




&lt;h2&gt;
  
  
  Cómo funciona
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sintaxis básica
&lt;/h3&gt;

&lt;p&gt;La misma propiedad &lt;code&gt;Username&lt;/code&gt; reescrita con &lt;code&gt;field&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El accessor &lt;code&gt;get&lt;/code&gt; sigue siendo auto-implementado — simplemente retorna &lt;code&gt;field&lt;/code&gt;. El accessor &lt;code&gt;set&lt;/code&gt; hace su único trabajo: hacer trim al valor entrante antes de almacenarlo. El campo privado desapareció. El compilador lo sintetiza en segundo plano, y &lt;code&gt;field&lt;/code&gt; te permite acceder a él.&lt;/p&gt;

&lt;p&gt;También puedes hacerlo al revés — implementar &lt;code&gt;get&lt;/code&gt; y auto-implementar &lt;code&gt;set&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O implementar ambos lados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"La edad no puede ser negativa."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  También funciona con &lt;code&gt;init&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Las propiedades de solo inicialización funcionan exactamente igual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;init&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto es especialmente útil en clases inmutables estilo record donde quieres normalización al construir pero sin mutación posterior.&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.amazonaws.com%2Fuploads%2Farticles%2Ff3t22hhlf1bgcr8k04yo.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.amazonaws.com%2Fuploads%2Farticles%2Ff3t22hhlf1bgcr8k04yo.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Patrones de uso en el mundo real
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Normalización de entrada
&lt;/h3&gt;

&lt;p&gt;El caso de uso más común. Trim de strings, normalización de mayúsculas, saneamiento de input del usuario — todo sin un backing field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Las tres propiedades se ven casi tan limpias como auto-propiedades, y cada una aplica su propio invariante en el punto de asignación.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Validación de rango
&lt;/h3&gt;

&lt;p&gt;Las propiedades numéricas que deben mantenerse dentro de límites son un caso perfecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductListing&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;99_999.99m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNegative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;StockQuantity&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Clamp silencioso — decisión intencional de producto&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nota &lt;code&gt;ArgumentOutOfRangeException.ThrowIfNegative&lt;/code&gt; y &lt;code&gt;ThrowIfGreaterThan&lt;/code&gt; — helpers de .NET 8+ que son más limpios que los &lt;code&gt;if&lt;/code&gt; manuales con throw. Usa las APIs modernas.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Inicialización lazy
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; brilla en propiedades de carga diferida. El patrón clásico requería un backing field nullable y una danza de null-check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Antes de C# 14 — el baile de siempre&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_tags&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con &lt;code&gt;field&lt;/code&gt;, escribes la misma lógica directamente dentro de la propiedad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una línea. El compilador administra el backing field. Tú administras la lógica.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Notificación de cambios (INotifyPropertyChanged)
&lt;/h3&gt;

&lt;p&gt;Las propiedades de ViewModel en aplicaciones MVVM suelen verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Blog.ViewModels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotifyPropertyChanged&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;PropertyChangedEventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CustomerName&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomerName&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin campos privados. Sin riesgo de colisión de nombres. El patrón sigue siendo verboso (esa es la naturaleza de &lt;code&gt;INotifyPropertyChanged&lt;/code&gt;), pero &lt;code&gt;field&lt;/code&gt; elimina una pieza móvil del rompecabezas.&lt;/p&gt;




&lt;h2&gt;
  
  
  La trampa de &lt;code&gt;field&lt;/code&gt;: cuando tu propiedad se llama &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Aquí las cosas se ponen interesantes. Como &lt;code&gt;field&lt;/code&gt; es una keyword &lt;em&gt;contextual&lt;/em&gt;, una propiedad que se llame efectivamente &lt;code&gt;field&lt;/code&gt; puede entrar en conflicto con la keyword. Considera esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Esta propiedad se llama "field" — inusual, pero legal&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Dentro de aquí, ¿"field" es la keyword o la propiedad misma?&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ¡Ambiguo!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El compilador te advertirá sobre la ambigüedad. La solución es el identificador verbatim &lt;code&gt;@field&lt;/code&gt;, que le indica a C# que lo trate como un identificador y no como la keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseRecord&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;@field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Explícito: este es el identificador, no la keyword&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En la práctica, nombrar una propiedad &lt;code&gt;field&lt;/code&gt; es inusual. Pero si trabajas con código que mapea a esquemas externos o bases de datos que usan nombres reservados, ahora conoces la salida de emergencia.&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.amazonaws.com%2Fuploads%2Farticles%2Fyp1c172epk34irufkm82.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.amazonaws.com%2Fuploads%2Farticles%2Fyp1c172epk34irufkm82.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo usar &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Usa &lt;code&gt;field&lt;/code&gt; cuando necesitas &lt;em&gt;algo&lt;/em&gt; de lógica en un accessor pero la propiedad sigue siendo la única dueña de su valor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalización en asignación&lt;/strong&gt; — trim, mayúsculas, formato&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validación en asignación&lt;/strong&gt; — rangos, null guards, formato&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inicialización lazy&lt;/strong&gt; — patrones de cálculo en primer acceso&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change tracking simple&lt;/strong&gt; — verificación de igualdad antes de disparar eventos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalización en &lt;code&gt;init&lt;/code&gt;&lt;/strong&gt; — objetos inmutables que necesitan entrada limpia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La regla de oro: si antes hubieras escrito un backing field privado al que &lt;em&gt;nada más en la clase hace referencia&lt;/em&gt;, reemplázalo con &lt;code&gt;field&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo NO usar &lt;code&gt;field&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;field&lt;/code&gt; no es una bala de plata. Hay casos donde un backing field explícito sigue siendo la herramienta correcta:&lt;/p&gt;

&lt;h3&gt;
  
  
  El problema del bypass en el constructor
&lt;/h3&gt;

&lt;p&gt;Supón que tienes una propiedad que dispara un evento al asignarse. Quieres que el constructor establezca el valor inicial &lt;em&gt;silenciosamente&lt;/em&gt;, sin disparar el evento. Con &lt;code&gt;field&lt;/code&gt; no puedes hacerlo — la única forma de acceder al almacenamiento interno es a través del accessor de la propiedad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sensor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// El evento se dispara en cada set — queremos saltarlo en el constructor&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Temperature&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ReadingChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Sensor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Esto dispara ReadingChanged durante la construcción — probablemente no es lo que quieres&lt;/span&gt;
        &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialTemperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si el constructor necesita saltarse la lógica de la propiedad, mantén el backing field explícito y asígnalo directamente desde el constructor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Múltiples propiedades que comparten estado
&lt;/h3&gt;

&lt;p&gt;Si dos o más propiedades leen o escriben el mismo valor subyacente — por ejemplo &lt;code&gt;Radius&lt;/code&gt; y &lt;code&gt;Diameter&lt;/code&gt; calculadas una a partir de la otra — necesitas un campo explícito al que ambas propiedades hagan referencia. &lt;code&gt;field&lt;/code&gt; es por propiedad; no puede compartirse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Estado derivado complejo
&lt;/h3&gt;

&lt;p&gt;Si el "almacenamiento interno" no es un valor simple sino que involucra múltiples campos, cachés u objetos, un campo explícito con un nombre descriptivo comunica la intención mucho mejor que la keyword anónima &lt;code&gt;field&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cuando el nombre importa para el diagnóstico
&lt;/h3&gt;

&lt;p&gt;Los backing fields explícitos aparecen en los debuggers con su nombre declarado. El backing field sintetizado por el compilador para propiedades basadas en &lt;code&gt;field&lt;/code&gt; recibe un nombre generado (como &lt;code&gt;&amp;lt;Username&amp;gt;k__BackingField&lt;/code&gt;) que es menos legible en memory dumps o herramientas de diagnóstico. Para rutas críticas bajo diagnósticos intensivos, puede que prefieras el campo explícito.&lt;/p&gt;




&lt;h2&gt;
  
  
  Puntos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;field&lt;/code&gt; es una keyword contextual de C# 14 que hace referencia al backing field sintetizado por el compilador — el mismo que las auto-propiedades ya crean.&lt;/li&gt;
&lt;li&gt;Permite agregar lógica a uno o ambos accessors sin promover el backing field a un miembro privado explícito.&lt;/li&gt;
&lt;li&gt;Funciona en accessors &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt; e &lt;code&gt;init&lt;/code&gt;, y puedes mezclar auto-implementación con implementación basada en &lt;code&gt;field&lt;/code&gt; en la misma propiedad.&lt;/li&gt;
&lt;li&gt;La trampa a recordar: si una propiedad se llama &lt;code&gt;field&lt;/code&gt;, usa &lt;code&gt;@field&lt;/code&gt; para distinguir el identificador de la keyword.&lt;/li&gt;
&lt;li&gt;Prefiere un backing field explícito cuando: el constructor necesita saltarse la lógica de la propiedad, múltiples propiedades comparten un slot de almacenamiento, o el nombre del campo importa para el diagnóstico.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
