<?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: Hamed Hajiloo</title>
    <description>The latest articles on DEV Community by Hamed Hajiloo (@hamedhajiloo).</description>
    <link>https://dev.to/hamedhajiloo</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%2F991578%2F1ee1c48b-1f25-4129-b72f-e802ed663897.jpg</url>
      <title>DEV Community: Hamed Hajiloo</title>
      <link>https://dev.to/hamedhajiloo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hamedhajiloo"/>
    <language>en</language>
    <item>
      <title>Centralized Rule Structures</title>
      <dc:creator>Hamed Hajiloo</dc:creator>
      <pubDate>Thu, 30 Jul 2026 08:38:53 +0000</pubDate>
      <link>https://dev.to/hamedhajiloo/the-agent-constitution-1gmk</link>
      <guid>https://dev.to/hamedhajiloo/the-agent-constitution-1gmk</guid>
      <description>&lt;p&gt;&lt;strong&gt;Enforcing AI Agent Adherence Through Single-Source Architecture in CrystaCode projects&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI agents require strict directory structures to execute project rules without logic divergence. Rule duplication causes execution failure. Establish a single source of truth. Define each rule in exactly one location. Secondary files function exclusively as routing parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AGENTS.md                        # Primary entry file
CLAUDE.md                        # Pointer to AGENTS.md

agents/
  rules/
    productA.md                  # Component directives
    productB.md
  skills/
    styling-guidelines-a/SKILL.md
    styling-guidelines-b/SKILL.md
  hooks/
    styling-guard.ps1            # Interceptor execution script

docs/
  Conventions/
    ProductA/styling-guidelines.md   # Absolute rule text
    ProductB/styling-guidelines.md

.claude/
  settings.json                  # Claude hook directive
  skills/*/SKILL.md              # Auto-generated artifacts

.github/
  instructions/*.instructions.md # GitHub agent pointers
  hooks/preToolUse.json          # GitHub hook directive

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Component Specifications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Primary Node
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AGENTS.md&lt;/strong&gt;: The mandatory initialization file. Contains routing matrices. Outlines file extension triggers and maps them to specific requisite documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redirection Nodes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt;: Contains a single pointer instructing the parser to evaluate &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;.github/instructions/&lt;/strong&gt;: Pointers redirecting GitHub Copilot to module-specific rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational Directives
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;agents/rules/&lt;/strong&gt;: Isolated procedural steps and checklists mapped to specific project modules. Defers rule definitions to the skills directory.&lt;br&gt;
&lt;strong&gt;agents/skills/&lt;/strong&gt;: Contextual triggers dictating exact pre-conditions for operation. Defers absolute rule text to the &lt;code&gt;docs/&lt;/code&gt; repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Absolute Documentation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;docs/&lt;/strong&gt;: The definitive repository. Contains complete technical specifications and rule text. The sole location for rule definitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated Mirrors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;.claude/skills/&lt;/strong&gt;: Automated mirror of &lt;code&gt;agents/skills&lt;/code&gt;. Manual modification is strictly prohibited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interceptor Script Protocol
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;styling-guard.ps1&lt;/code&gt; hook enforces compliance by gating execution based on session markers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intercept Request:&lt;/strong&gt;&lt;br&gt;
The script intercepts the AI file modification request pre-execution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Evaluate Target Parameters:&lt;/strong&gt;&lt;br&gt;
The file extension is evaluated against target parameters (&lt;code&gt;.scss&lt;/code&gt;, &lt;code&gt;.razor&lt;/code&gt;). Unmatched files bypass interception.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ascertain Product Association:&lt;/strong&gt;&lt;br&gt;
The script evaluates the target file path to identify the associated project module.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query Session Data:&lt;/strong&gt;&lt;br&gt;
The script checks session data for an existing execution marker corresponding to the file type and module. If present, the AI bypasses interception.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Halt Execution:&lt;/strong&gt;&lt;br&gt;
If the marker is absent, execution is blocked. The AI receives an explicit directive to parse the required skill documentation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write Execution Marker:&lt;/strong&gt;&lt;br&gt;
The script writes the execution marker to session data. Subsequent modification attempts in the same session proceed without interruption.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Interceptor Implementation Logic&lt;/strong&gt;&lt;br&gt;
The interception mechanism outputs explicit standard error directives to halt execution when session markers are absent:&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Checklist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'scss'&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="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Error.WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"MANDATORY styling gate: this is the first .scss edit in this session for the "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="s2"&gt;' product. Invoke the '&lt;/span&gt;&lt;span class="nv"&gt;$skill&lt;/span&gt;&lt;span class="s2"&gt;' skill (canonical rules: &lt;/span&gt;&lt;span class="nv"&gt;$doc&lt;/span&gt;&lt;span class="s2"&gt;), apply its "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"rules -- &lt;/span&gt;&lt;span class="nv"&gt;$checklist&lt;/span&gt;&lt;span class="s2"&gt; -- then retry this exact edit. &lt;/span&gt;&lt;span class="nv"&gt;$forbidden&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"This gate fires once per session per product per file category."&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="kr"&gt;else&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="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Error.WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"MANDATORY component gate: this is the first .razor edit in this session for the "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="s2"&gt;' product. Before this edit: (1) invoke the '&lt;/span&gt;&lt;span class="nv"&gt;$skill&lt;/span&gt;&lt;span class="s2"&gt;' skill (canonical "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"rules: &lt;/span&gt;&lt;span class="nv"&gt;$doc&lt;/span&gt;&lt;span class="s2"&gt;) for color/typography/component conventions -- use &lt;/span&gt;&lt;span class="nv"&gt;$components&lt;/span&gt;&lt;span class="s2"&gt;. "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"(2) invoke the 'localize-app-strings' skill -- never hardcode user-facing text, "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"always route through IStringLocalizer&amp;lt;AppStrings&amp;gt;. Then retry this exact edit. "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"This gate fires once per session per product per file category."&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;ul&gt;
&lt;li&gt;[ ] Initialize root entry node (&lt;code&gt;AGENTS.md&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;[ ] Define routing matrix mapping file extensions to required rules.&lt;/li&gt;
&lt;li&gt;[ ] Isolate module-specific rules into distinct files.&lt;/li&gt;
&lt;li&gt;[ ] Establish definitive rule repository (&lt;code&gt;docs/&lt;/code&gt;) as the single source of truth.&lt;/li&gt;
&lt;li&gt;[ ] Define skill triggers with explicit operational pre-conditions.&lt;/li&gt;
&lt;li&gt;[ ] Deploy interceptor script to halt execution and mandate rule review.&lt;/li&gt;
&lt;li&gt;[ ] Restrict tool-specific directories (&lt;code&gt;.claude&lt;/code&gt;, &lt;code&gt;.github&lt;/code&gt;) to pointer artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enjoy it: &lt;a href="https://crystacode.ai/" rel="noopener noreferrer"&gt;CrystaCode&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>githubcopilot</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>MCP for .NET: AI Agents That Can Actually Do Things</title>
      <dc:creator>Hamed Hajiloo</dc:creator>
      <pubDate>Wed, 18 Feb 2026 08:08:02 +0000</pubDate>
      <link>https://dev.to/hamedhajiloo/mcp-for-net-ai-agents-that-can-actually-do-things-50in</link>
      <guid>https://dev.to/hamedhajiloo/mcp-for-net-ai-agents-that-can-actually-do-things-50in</guid>
      <description>&lt;p&gt;Crysta is an AI app where you can build your own AI agents for real business use.&lt;/p&gt;

&lt;p&gt;What makes it powerful is this: you are not limited to the default behavior of a chatbot.&lt;br&gt;
You can connect different MCP servers and give your agent practical capabilities like scheduling, reservations, and data access.&lt;/p&gt;

&lt;p&gt;In short, Crysta helps you build an AI agent that does more than talk, it can actually get work done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Users Can Connect
&lt;/h2&gt;

&lt;p&gt;With MCP, an agent can connect to tools such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Calendar workflows&lt;/li&gt;
&lt;li&gt;Reservation and scheduling systems&lt;/li&gt;
&lt;li&gt;Business data tools exposed through MCP&lt;/li&gt;
&lt;li&gt;Knowledge tools like DeepWiki (through MCP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of only answering a request like "book a meeting," the agent can actually do it through a connected tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for .NET Developers
&lt;/h2&gt;

&lt;p&gt;MCP gives .NET developers a straightforward way to add real capabilities to an AI agent without building every integration yourself. Instead of wiring each API directly into your app, you connect MCP‑compatible tools and let the agent use them through the same function‑calling flow.&lt;/p&gt;

&lt;p&gt;This leads to two big benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster development:&lt;/strong&gt; new features come from adding MCP tools, not writing new integrations.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner architecture:&lt;/strong&gt; your agent grows by adding tools, not by adding more prompt logic or custom glue code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How We Built It (High Level)
&lt;/h2&gt;

&lt;p&gt;We kept the flow simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User adds an MCP server link to an agent.&lt;/li&gt;
&lt;li&gt;System validates the link and reads tool metadata.&lt;/li&gt;
&lt;li&gt;Connected MCP tools become part of that agent’s available actions.&lt;/li&gt;
&lt;li&gt;During chat, the model can call those tools when needed.&lt;/li&gt;
&lt;li&gt;We track failures and connection health so users can fix broken links quickly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Packages We Use for This
&lt;/h2&gt;

&lt;p&gt;These are the key packages behind this MCP workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ModelContextProtocol&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Microsoft.Extensions.AI.OpenAI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Azure.AI.OpenAI&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination gives us MCP tool discovery plus a solid function-calling pipeline in .NET.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small Code Examples (Based on Our Code)
&lt;/h2&gt;

&lt;p&gt;Here’s a sample snippet that reflects the structure we use in our production pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) MCP client + validation service
&lt;/h3&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;ModelContextProtocol.Client&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;class&lt;/span&gt; &lt;span class="nc"&gt;McpServerService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMcpServerService&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="n"&gt;StoredMcpServer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ValidateAndGetMcpInfoAsync&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;mcpUrl&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;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UriKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Absolute&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;_&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="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;StoredMcpServer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;IsValid&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="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Invalid URL format"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;try&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;transport&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;HttpClientTransport&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;HttpClientTransportOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Endpoint&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="n"&gt;mcpUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;loggerFactory&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="kt"&gt;var&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;await&lt;/span&gt; &lt;span class="n"&gt;McpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&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;serverInfo&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;ServerInfo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;StoredMcpServer&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcpUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serverInfo&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;"Unknown MCP Server"&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;serverInfo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"No description available"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;AddedDate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;IsValid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serverInfo&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;null&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&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="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;StoredMcpServer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;IsValid&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="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ex&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="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="n"&gt;McpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateClientAsync&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;mcpUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&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;transport&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;HttpClientTransport&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;HttpClientTransportOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Endpoint&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="n"&gt;mcpUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;loggerFactory&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;McpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&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;h3&gt;
  
  
  2) Load MCP tools from agent skills (runtime)
&lt;/h3&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.AI&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.Client&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;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="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AIFunction&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetMcpToolsAsync&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;interactiveAgent&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Skills&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;return&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;allTools&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;AIFunction&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;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ServiceScopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&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;mcpServerService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMcpServerService&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;skill&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;interactiveAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Skills&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;skill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkillType&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;SkillType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkillJson&lt;/span&gt; &lt;span class="k"&gt;is&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;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;check&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;mcpServerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateAndGetMcpInfoAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkillJson&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;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&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;client&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;mcpServerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateClientAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkillJson&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;tools&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="nf"&gt;ListToolsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;allTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRange&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;allTools&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;
  
  
  3) Attach MCP tools to &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; chat pipeline
&lt;/h3&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.ClientModel&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;Azure.AI.OpenAI&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.AI&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;azureChatClient&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;AzureOpenAIClient&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="n"&gt;endpoint&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;ApiKeyCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsIChatClient&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;mcpTools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetMcpToolsAsync&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;chatClient&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;ChatClientBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;azureChatClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureOptions&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="p"&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;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;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;tool&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mcpTools&lt;/span&gt;&lt;span class="p"&gt;)&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;.&lt;/span&gt;&lt;span class="n"&gt;Tools&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;tool&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="nf"&gt;UseFunctionInvocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AllowConcurrentInvocation&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="nf"&gt;Build&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 the exact idea we use: validate MCP links, discover tools, then pass those tools into the chat client so the agent can call them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;MCP moved our product from "AI that answers" to "AI that acts."&lt;/p&gt;

&lt;p&gt;That is the difference users notice immediately.&lt;/p&gt;

&lt;p&gt;If you want to see the end result, check it here: &lt;a href="https://crystacode.ai" rel="noopener noreferrer"&gt;https://crystacode.ai&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>How I Fixed a Web Audio Echo Problem with a 5-Second Delay</title>
      <dc:creator>Hamed Hajiloo</dc:creator>
      <pubDate>Sat, 01 Nov 2025 14:28:10 +0000</pubDate>
      <link>https://dev.to/hamedhajiloo/how-i-fixed-a-web-audio-echo-problem-with-a-5-second-delay-384h</link>
      <guid>https://dev.to/hamedhajiloo/how-i-fixed-a-web-audio-echo-problem-with-a-5-second-delay-384h</guid>
      <description>&lt;h2&gt;
  
  
  How I fixed a web audio echo problem with a 5-second delay
&lt;/h2&gt;

&lt;p&gt;if you want to do anything with &lt;strong&gt;audio in the browser&lt;/strong&gt;, you’ll have to deal with the &lt;strong&gt;Web Audio API&lt;/strong&gt;.  it’s what powers everything — from music apps to AI voice chats.&lt;/p&gt;

&lt;p&gt;recently I was building a real-time &lt;strong&gt;user ↔ AI voice conversation&lt;/strong&gt; app &lt;a href="https://crystacode.ai/" rel="noopener noreferrer"&gt;CrystaCode.ai&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
everything worked fine &lt;em&gt;except one weird thing&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;👉 during the &lt;strong&gt;first few seconds&lt;/strong&gt; of each conversation, the AI’s voice got picked up by the mic. after that? &lt;strong&gt;no echo at all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I spent days trying everything different routing, gain nodes, worklets, custom dsp, RNNoise — nothing fixed it.  then, out of frustration, i added one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;between starting the mic and sending audio to the AI. and boom — echo gone forever.&lt;/p&gt;

&lt;p&gt;wait... why did this work?&lt;br&gt;
when you call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMedia&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;echoCancellation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;noiseSuppression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;autoGainControl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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 browser gives you a mic stream with built-in acoustic echo cancellation (aec).&lt;br&gt;
that aec runs natively inside chrome, firefox, safari, or edge usually the same code used in webrtc (like webrtc::EchoCanceller in chrome).&lt;/p&gt;

&lt;p&gt;the browser AEC works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;it watches what’s playing from your speakers&lt;/li&gt;
&lt;li&gt;it listens to your microphone at the same time&lt;/li&gt;
&lt;li&gt;it learns the relationship between the two — basically:
“when I play this, the mic hears that”&lt;/li&gt;
&lt;li&gt;it builds a model of your room acoustics and device path&lt;/li&gt;
&lt;li&gt;after a few seconds, it knows how to subtract the speaker sound from the mic signal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;so it needs time to adapt — normally 2–5 seconds. during that time, the AEC hasn’t finished learning yet, so a little bit of echo leaks through. the bug wasn’t a bug&lt;/p&gt;

&lt;p&gt;my web audio code was fine. the browser just needed time to adapt.&lt;/p&gt;

&lt;p&gt;by delaying the start of sending mic audio to the ai for 5 seconds, I let the AEC finish adapting. after that, no more echo.&lt;/p&gt;

&lt;h2&gt;
  
  
  why browsers behave like this
&lt;/h2&gt;

&lt;p&gt;browser AEC uses adaptive algorithms. they constantly adjust to changing acoustics speaker position, device, user movement, etc.&lt;/p&gt;

&lt;p&gt;so, every time you start the mic, the algorithm resets and starts learning again. the first few seconds are a "training phase". if you stream mic audio too early, it still contains echo. after it converges — everything becomes clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;problem: ai voice was picked up by mic in first seconds&lt;/li&gt;
&lt;li&gt;reason: browser AEC needs time to adapt&lt;/li&gt;
&lt;li&gt;fix: delay sending mic data for ~5s before starting conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  More info
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API" rel="noopener noreferrer"&gt;MDN: Web Audio API&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia" rel="noopener noreferrer"&gt;MDN: getUserMedia constraints&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webaudio</category>
      <category>javascript</category>
      <category>webrtc</category>
      <category>audio</category>
    </item>
  </channel>
</rss>
