<?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: Mehran Davoudi</title>
    <description>The latest articles on DEV Community by Mehran Davoudi (@mehrandvd).</description>
    <link>https://dev.to/mehrandvd</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F981242%2F335468c4-a562-4321-8045-faade059a05b.jpeg</url>
      <title>DEV Community: Mehran Davoudi</title>
      <link>https://dev.to/mehrandvd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mehrandvd"/>
    <language>en</language>
    <item>
      <title>AOT: .NET vs Java</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Fri, 19 Sep 2025 12:39:44 +0000</pubDate>
      <link>https://dev.to/mehrandvd/aot-net-vs-java-3e4m</link>
      <guid>https://dev.to/mehrandvd/aot-net-vs-java-3e4m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;⚔️ AOT Showdown: .NET vs Java in 2025; Who’s really &lt;strong&gt;production-ready&lt;/strong&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the race to optimize startup time, memory footprint, and deployment simplicity, Ahead-of-Time (AOT) compilation has become a game-changer. Both .NET and Java have embraced AOT, but not equally. If you're building microservices, serverless apps, or containerized workloads, knowing which platform delivers a smoother AOT experience can save you hours of frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 What Is AOT Compilation?
&lt;/h2&gt;

&lt;p&gt;AOT compiles your code into native machine instructions &lt;em&gt;before&lt;/em&gt; runtime, unlike Just-In-Time (JIT) compilation, which happens during execution. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster startup&lt;/li&gt;
&lt;li&gt;Lower memory usage&lt;/li&gt;
&lt;li&gt;No runtime dependency on the VM or runtime engine&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;But the devil’s in the details.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  .NET Native AOT
&lt;/h2&gt;

&lt;p&gt;Microsoft introduced Native AOT as a first-class citizen in .NET 7 and refined it in .NET 8. It’s now a reliable option for production workloads.&lt;/p&gt;

&lt;p&gt;✅ Highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-contained executables&lt;/strong&gt;: No need for the .NET runtime on the target machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazing fast startup&lt;/strong&gt;: Ideal for cold-start-sensitive apps like serverless functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller footprint&lt;/strong&gt;: Trimmed assemblies and no JIT overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrated tooling&lt;/strong&gt;: Just use &lt;code&gt;dotnet publish -r linux-x64 --self-contained&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No dynamic code generation (e.g., &lt;code&gt;Reflection.Emit&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Some libraries need AOT-friendly alternatives.&lt;/li&gt;
&lt;li&gt;Limited runtime diagnostics compared to JIT.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Java GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;Java’s AOT story revolves around &lt;strong&gt;GraalVM Native Image&lt;/strong&gt;, which compiles bytecode into a native binary. It’s impressive, but not frictionless.&lt;/p&gt;

&lt;p&gt;✅ Highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No JVM required&lt;/strong&gt;: Native binary runs independently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast startup&lt;/strong&gt;: Great for CLI tools and serverless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework support&lt;/strong&gt;: Quarkus and Micronaut are designed with AOT in mind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Closed-world assumption&lt;/strong&gt;: All reachable code must be known at build time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflection and proxies&lt;/strong&gt;: Need manual configuration or substitution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Longer build times&lt;/strong&gt;: Native compilation is slower and more complex.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Head-to-Head Comparison
&lt;/h2&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;.NET Native AOT&lt;/th&gt;
&lt;th&gt;Java GraalVM Native Image&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Startup Time&lt;/td&gt;
&lt;td&gt;⚡ Very fast&lt;/td&gt;
&lt;td&gt;⚡ Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory Usage&lt;/td&gt;
&lt;td&gt;🧠 Low&lt;/td&gt;
&lt;td&gt;🧠 Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime Dependency&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic Features&lt;/td&gt;
&lt;td&gt;🚫 Limited&lt;/td&gt;
&lt;td&gt;🚫 Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tooling Integration&lt;/td&gt;
&lt;td&gt;✅ Seamless&lt;/td&gt;
&lt;td&gt;⚠️ Requires GraalVM setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem Compatibility&lt;/td&gt;
&lt;td&gt;✅ Growing&lt;/td&gt;
&lt;td&gt;⚠️ Needs manual config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🏁 Verdict: .NET Is Winning the AOT Race
&lt;/h2&gt;

&lt;p&gt;If you're choosing between the two for production workloads in 2025, &lt;strong&gt;.NET Native AOT is more mature, better integrated, and easier to adopt&lt;/strong&gt;. Java’s GraalVM is powerful, but still best suited for niche use cases or greenfield projects with AOT in mind.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Final Thoughts
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;AOT isn’t just a performance tweak; it’s a deployment philosophy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whether you're optimizing for cold starts, trimming container sizes, or building resilient edge apps, understanding the strengths and limitations of each platform can help you make smarter architectural decisions.&lt;/p&gt;

&lt;p&gt;Have you tried Native AOT in .NET or GraalVM in Java? Share your experience below, I’d love to hear how it’s working for you in real-world scenarios.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>java</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
    <item>
      <title>AI+UX: How to Bridge UI and Talk Seamlessly</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Mon, 15 Sep 2025 23:24:33 +0000</pubDate>
      <link>https://dev.to/mehrandvd/aiux-how-to-bridge-ui-and-talk-seamlessly-go8</link>
      <guid>https://dev.to/mehrandvd/aiux-how-to-bridge-ui-and-talk-seamlessly-go8</guid>
      <description>&lt;p&gt;In the evolving landscape of conversational interfaces, the line between &lt;strong&gt;UI and Talk&lt;/strong&gt; is blurring, and &lt;a href="https://crystacode.ai" rel="noopener noreferrer"&gt;CrystaCode.ai&lt;/a&gt; is a prime example of what’s possible when AI meets thoughtful UX design.&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%2Fdka76m4oq7f6nnvjhb23.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%2Fdka76m4oq7f6nnvjhb23.png" alt=" " width="800" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CrystaCode is a conversational AI platform (based in Dubai!) that interacts with users either through text-based chat or live voice. But what makes it truly exciting isn’t just the dual modality, it’s the unified infrastructure powering both experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 One Brain, Two Mouths: Unified AI Infrastructure
&lt;/h2&gt;

&lt;p&gt;At the heart of CrystaCode is &lt;strong&gt;a shared backend that serves both chat and talk interactions&lt;/strong&gt;. This isn’t just a convenience, it’s a design philosophy. By using the &lt;strong&gt;same orchestration logic&lt;/strong&gt;, prompt engineering, and AI pipelines, we ensure consistent behavior and reduce duplication across modalities.&lt;/p&gt;

&lt;p&gt;To achieve this, we leveraged a couple of technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;)&lt;/strong&gt;: A flexible orchestration layer that abstracts AI interactions into composable units.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft.Extensions.AI.Abstractions&lt;/strong&gt;: This abstraction layer allows us to plug in different AI providers while maintaining a consistent interface for both chat and voice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture means that whether a user types a question or speaks it aloud, the same intelligent backend processes the request, applies context, and returns a response.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔊 Real-Time Voice with SignalR
&lt;/h2&gt;

&lt;p&gt;Voice interactions demand low latency and real-time streaming. That’s where &lt;strong&gt;SignalR&lt;/strong&gt; comes in. We use it to stream audio between the client and server, enabling live conversations with minimal delay.&lt;/p&gt;

&lt;p&gt;SignalR’s streaming model fits perfectly with our AI pipeline, allowing us to push intermediate responses, handle interruptions, and maintain a fluid user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Blazor for UI and Code Reusability
&lt;/h2&gt;

&lt;p&gt;On the frontend, &lt;strong&gt;Blazor&lt;/strong&gt; helps us maximize code reuse across chat and voice interfaces. With Blazor’s component model, we can build shared UI elements, manage state consistently, and even reuse validation and business logic across both modalities. For Blazor, we are using &lt;a href="https://bitplatform.dev/" rel="noopener noreferrer"&gt;Bit Platform&lt;/a&gt; as we believe it has the state-of-the-art configuration to get the best from Blazor, both in functionality and performance.&lt;/p&gt;

&lt;p&gt;This means faster development cycles and fewer bugs, plus a more cohesive user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  ☁️ Azure OpenAI for Scale and Flexibility
&lt;/h2&gt;

&lt;p&gt;To power the intelligence behind CrystaCode, we use &lt;strong&gt;Azure OpenAI&lt;/strong&gt;. It gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access to cutting-edge language models&lt;/li&gt;
&lt;li&gt;Enterprise-grade scalability&lt;/li&gt;
&lt;li&gt;Fine-tuning and deployment flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined with MCP, we can route requests to different models based on context, user profile, or even modality, without changing the frontend or orchestration logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Semantic Integration Testing with skUnit
&lt;/h2&gt;

&lt;p&gt;Building a conversational AI that feels natural across both chat and voice isn’t just about infrastructure; it’s about precision. That’s why we use &lt;strong&gt;skUnit&lt;/strong&gt;, a testing framework purpose-built for semantic validation of AI interactions.&lt;/p&gt;

&lt;p&gt;With skUnit, we create &lt;strong&gt;end-to-end integration tests&lt;/strong&gt; that simulate real user conversations and validate the AI’s responses not just syntactically, but semantically. This means we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assert that the AI understands context across turns&lt;/li&gt;
&lt;li&gt;Validate that the tone, intent, and structure of responses match expectations&lt;/li&gt;
&lt;li&gt;Catch regressions in prompt behavior or orchestration logic early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level of testing is especially powerful when combined with &lt;strong&gt;MCP orchestrations&lt;/strong&gt;, allowing us to simulate full conversational flows and verify outcomes across different model configurations and user scenarios.&lt;/p&gt;

&lt;p&gt;By embedding skUnit into our CI pipeline, we’ve turned conversational quality into a measurable, repeatable discipline, raising the bar for what AI UX can deliver.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✨ The UX Payoff
&lt;/h2&gt;

&lt;p&gt;This tech stack isn’t just elegant, it’s user-centric. By unifying the backend and optimizing the frontend, we deliver:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seamless transitions between chat and voice&lt;/li&gt;
&lt;li&gt;Faster response times&lt;/li&gt;
&lt;li&gt;Consistent personality and tone across modalities&lt;/li&gt;
&lt;li&gt;A scalable foundation for future features like multimodal input or emotion-aware responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CrystaCode isn’t just an app; it’s a blueprint for how AI and UX can co-evolve.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ux</category>
      <category>ai</category>
      <category>blazor</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Mon, 01 Sep 2025 14:24:57 +0000</pubDate>
      <link>https://dev.to/mehrandvd/-34o</link>
      <guid>https://dev.to/mehrandvd/-34o</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mehrandvd/using-aifunctions-with-openai-realtime-api-in-net-261k" class="crayons-story__hidden-navigation-link"&gt;Using AIFunctions with OpenAI Realtime API in .NET&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/mehrandvd" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F981242%2F335468c4-a562-4321-8045-faade059a05b.jpeg" alt="mehrandvd profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mehrandvd" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mehran Davoudi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mehran Davoudi
                
              
              &lt;div id="story-author-preview-content-2810856" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mehrandvd" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F981242%2F335468c4-a562-4321-8045-faade059a05b.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mehran Davoudi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/mehrandvd/using-aifunctions-with-openai-realtime-api-in-net-261k" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 1 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mehrandvd/using-aifunctions-with-openai-realtime-api-in-net-261k" id="article-link-2810856"&gt;
          Using AIFunctions with OpenAI Realtime API in .NET
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dotnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dotnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/csharp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;csharp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/openai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;openai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/mehrandvd/using-aifunctions-with-openai-realtime-api-in-net-261k" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/mehrandvd/using-aifunctions-with-openai-realtime-api-in-net-261k#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>openai</category>
      <category>ai</category>
    </item>
    <item>
      <title>Using AIFunctions with OpenAI Realtime API in .NET</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Mon, 01 Sep 2025 13:51:34 +0000</pubDate>
      <link>https://dev.to/mehrandvd/using-aifunctions-with-openai-realtime-api-in-net-261k</link>
      <guid>https://dev.to/mehrandvd/using-aifunctions-with-openai-realtime-api-in-net-261k</guid>
      <description>&lt;p&gt;If you've ever tried integrating &lt;strong&gt;Azure OpenAI's Realtime API&lt;/strong&gt; into a .NET application, you've likely hit a frustrating wall: your beautifully crafted &lt;strong&gt;AIFunctions suddenly feel useless&lt;/strong&gt;. The Realtime API demands raw JSON schemas for tools, and most examples out there force you to manually duplicate function definitions, &lt;strong&gt;stripping away the elegance of strongly typed C# models&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I ran into this exact pain point. So I built &lt;a href="https://github.com/mehrandvd/openai-realtime-sample/tree/main" rel="noopener noreferrer"&gt;openai-realtime-sample&lt;/a&gt;, a comprehensive repo that shows how to &lt;strong&gt;reuse your existing AIFunctions as Realtime tools&lt;/strong&gt; without rewriting a single line of JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚨 &lt;strong&gt;The Problem&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;.NET developers using &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; often define their functions using &lt;code&gt;AIFunctionFactory.Create(...)&lt;/code&gt;, complete with &lt;code&gt;[Description]&lt;/code&gt; attributes and enums. This works beautifully for regular chat completions.&lt;/p&gt;

&lt;p&gt;But when switching to the Realtime API, you're expected to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manually define JSON schemas&lt;/strong&gt; for each tool
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate function shapes and descriptions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lose the benefits of strong typing and metadata&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to &lt;strong&gt;brittle code&lt;/strong&gt; and a lot of unnecessary duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ &lt;strong&gt;The Solution&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;My repo shows how to elegantly convert AIFunctions into &lt;code&gt;ConversationFunctionTool&lt;/code&gt; objects, making them compatible with the Realtime API. Here's the magic:&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;static&lt;/span&gt; &lt;span class="n"&gt;ConversationFunctionTool&lt;/span&gt; &lt;span class="nf"&gt;ConversationTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;AIFunction&lt;/span&gt; &lt;span class="n"&gt;function&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="n"&gt;function&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="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;function&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;Parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BinaryData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JsonSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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 extension method (found in &lt;code&gt;AIExtensions.cs&lt;/code&gt;) lets you do:&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="nf"&gt;GetTools&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// AIFunction[]&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conversationTools&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;Select&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConversationTool&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;sessionOptions&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;AddRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conversationTools&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when a tool is invoked:&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;tool&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;First&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;=&amp;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;Name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;functionName&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;result&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;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsedArgs&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RealtimeItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateFunctionCallOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callId&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="nf"&gt;ToString&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="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧪 &lt;strong&gt;Two Sample Projects&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RealtimeSample.Console&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A minimal, linear example with a single weather function. Perfect for copying into your own app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RealtimeSample.BlazorHybrid (.NET MAUI)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A richer, event-driven UI that visualizes RealtimeUpdate events like session start, speech input, delta streaming, and tool invocation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No manual JSON duplication&lt;/strong&gt;, just reuse your AIFunctions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enums and descriptions flow automatically&lt;/strong&gt; into the tool schema
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respond to tool calls&lt;/strong&gt; with &lt;code&gt;FunctionCallOutput&lt;/code&gt; referencing the original &lt;code&gt;function_call_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger a new model turn&lt;/strong&gt; with &lt;code&gt;StartResponseAsync&lt;/code&gt; after supplying tool outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠 &lt;strong&gt;Requirements&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET 9&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure OpenAI Realtime deployment&lt;/strong&gt; (via environment variables)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔁 &lt;strong&gt;Extend It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Want to add more tools? Just return them from &lt;code&gt;GetTools()&lt;/code&gt;, your enums and descriptions will be automatically included.&lt;/p&gt;




&lt;p&gt;This repo bridges the gap between &lt;strong&gt;high-level .NET abstractions&lt;/strong&gt; and the &lt;strong&gt;low-level Realtime API protocol&lt;/strong&gt;. Whether you're building a console app or a hybrid UI, this pattern gives you a &lt;strong&gt;clean, reusable way to integrate OpenAI tools&lt;/strong&gt; without sacrificing type safety or maintainability.&lt;/p&gt;

&lt;p&gt;Check out the full repo on &lt;a href="https://github.com/mehrandvd/openai-realtime-sample/tree/main" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and feel free to adapt the extension method into your own shared library.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>openai</category>
      <category>ai</category>
    </item>
    <item>
      <title>Testing MCP Orchestrations with skUnit</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Tue, 26 Aug 2025 09:37:17 +0000</pubDate>
      <link>https://dev.to/mehrandvd/testing-mcp-orchestrations-with-skunit-420n</link>
      <guid>https://dev.to/mehrandvd/testing-mcp-orchestrations-with-skunit-420n</guid>
      <description>&lt;h2&gt;
  
  
  🧠 Why Test MCP Servers?
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol (MCP) servers are the backbone of tool-augmented AI systems. They expose tools that AI models can invoke—like checking the weather, scheduling meetings, or querying databases.&lt;/p&gt;

&lt;p&gt;But when multiple MCP servers are orchestrated together, things get complex. You need to ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tools are discovered correctly&lt;/li&gt;
&lt;li&gt;Functions are invoked with the right parameters&lt;/li&gt;
&lt;li&gt;Responses are semantically accurate&lt;/li&gt;
&lt;li&gt;Servers coordinate smoothly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enter &lt;strong&gt;skUnit&lt;/strong&gt;—a powerful .NET testing framework designed for AI units like &lt;code&gt;IChatClient&lt;/code&gt; and MCP integrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive in, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A working .NET environment&lt;/li&gt;
&lt;li&gt;MCP servers (e.g., Time, Weather, Calendar) ready to launch&lt;/li&gt;
&lt;li&gt;skUnit installed via NuGet:
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛠️ Step 1: Setup MCP Transports
&lt;/h2&gt;

&lt;p&gt;Each MCP server needs a transport layer. Here's how to configure them:&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;timeTransport&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;StdioClientTransport&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;StdioClientTransportOptions&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;"Time MCP Server"&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="s"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"/c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"@smithery/cli@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"@javilujann/timemcp"&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;weatherTransport&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;StdioClientTransport&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;StdioClientTransportOptions&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;"Some Weather MCP"&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="s"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Arguments&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="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;calendarTransport&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;StdioClientTransport&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;StdioClientTransportOptions&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;"Calendar Server"&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="s"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"calendar-server.js"&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;h2&gt;
  
  
  🧩 Step 2: Create MCP Clients and Discover Tools
&lt;/h2&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;timeClient&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;McpClientFactory&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;timeTransport&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;weatherClient&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;McpClientFactory&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;weatherTransport&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;calendarClient&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;McpClientFactory&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;calendarTransport&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;AITool&lt;/span&gt;&lt;span class="p"&gt;&amp;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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;timeClient&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;weatherClient&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;calendarClient&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧠 Step 3: Build the Chat Client
&lt;/h2&gt;

&lt;p&gt;Now wire up all tools into a unified chat client:&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;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;baseChatClient&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="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="n"&gt;allTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&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="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;h2&gt;
  
  
  📜 Step 4: Write a Multi-Server Scenario
&lt;/h2&gt;

&lt;p&gt;Create a Markdown file (&lt;code&gt;multi-server-test.md&lt;/code&gt;) with your test scenario:&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;# SCENARIO Multi-Server Coordination&lt;/span&gt;

&lt;span class="gu"&gt;## [USER]&lt;/span&gt;
Check the weather in Toronto and schedule a meeting for tomorrow if it's sunny.

&lt;span class="gu"&gt;## [AGENT]&lt;/span&gt;
I checked the weather – it's going to be sunny tomorrow! I've scheduled your meeting for 2 PM.

&lt;span class="gu"&gt;### CHECK SemanticCondition&lt;/span&gt;
It mentions both weather information and confirms meeting scheduling.

&lt;span class="gu"&gt;### CHECK FunctionCall {&lt;/span&gt;
  "function_name": "get_weather"
  "arguments": {
    "location": [ "Equals", "Toronto" ]
  }
}

&lt;span class="gu"&gt;### CHECK FunctionCall {&lt;/span&gt;
  "function_name": "schedule_event",
  "arguments": {
    "dateTime": [ "SemanticCondition", "A time that refers 2 PM" ]
  }
}&lt;span class="sb"&gt;


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

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Step 5: Run the Test
&lt;/h2&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;markdown&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"multi-server-test.md"&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;scenarios&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;ChatScenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;markdown&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;ScenarioAssert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PassAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenarios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom 💥! You’ve just tested a multi-server orchestration with semantic checks and function call validations.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Bonus: Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolate First:&lt;/strong&gt; Test each MCP server individually before combining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mock External APIs:&lt;/strong&gt; Use test doubles for external dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert Everything:&lt;/strong&gt; Use &lt;code&gt;CHECK&lt;/code&gt; statements to validate function calls and semantic responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail Gracefully:&lt;/strong&gt; Simulate server errors to test fallback logic.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;skUnit turns AI orchestration testing into a breeze. Whether you're building a tool-rich assistant or validating complex workflows, this framework gives you confidence that your MCP servers are playing nicely together.&lt;/p&gt;

&lt;p&gt;Want to dive deeper? Check out the &lt;a href="https://github.com/mehrandvd/skunit/blob/main/docs/mcp-testing-guide.md" rel="noopener noreferrer"&gt;official MCP testing guide&lt;/a&gt; on GitHub.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>testing</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Assigning Tasks to Copilot</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Sun, 24 Aug 2025 15:34:54 +0000</pubDate>
      <link>https://dev.to/mehrandvd/assigning-tasks-to-copilot-5h55</link>
      <guid>https://dev.to/mehrandvd/assigning-tasks-to-copilot-5h55</guid>
      <description>&lt;p&gt;Ever had a GitHub issue linger for months because you simply didn’t have the time or bandwidth to tackle it? Same here.&lt;/p&gt;

&lt;p&gt;Back in September 2024, I created a &lt;a href="https://github.com/mehrandvd/skunit/issues/6" rel="noopener noreferrer"&gt;fairly complex issue&lt;/a&gt; that required deep focus and multiple steps to resolve:&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%2F0jn8yxjj7icq10961a11.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%2F0jn8yxjj7icq10961a11.png" alt="A GitHub issue" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ Instant Action
&lt;/h2&gt;

&lt;p&gt;Fast forward to August 2025, and I finally decided to try something different: I assigned the issue to GitHub Copilot.&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%2Fjliaph4l3tg3yt2rg1uk.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%2Fjliaph4l3tg3yt2rg1uk.png" alt="Assign to Copilot" width="392" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No sooner had I clicked “Assign to Copilot” than it sprang into action. First move? It created a &lt;a href="https://github.com/mehrandvd/skunit/pull/30" rel="noopener noreferrer"&gt;branch (copilot/fix6)&lt;/a&gt; to kick off the work and a &lt;a href="https://github.com/mehrandvd/skunit/pull/30" rel="noopener noreferrer"&gt;Pull Request&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fxsa031w2e7egv73u1zsd.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%2Fxsa031w2e7egv73u1zsd.png" alt="Copilot is working" width="498" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Thoughtful Execution
&lt;/h2&gt;

&lt;p&gt;What impressed me most wasn’t just the speed; it was the clarity. Copilot even shared its reasoning behind the changes, giving me insight into its decision-making process.&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%2Fqetnnwqi2dxn0jo980ad.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%2Fqetnnwqi2dxn0jo980ad.png" alt="Copilot Thought" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Beautiful Documentation
&lt;/h2&gt;

&lt;p&gt;And to top it off, Copilot generated clean, readable documentation that made the codebase easier to understand and maintain.&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%2Fq9n3bfjw0e6lhw0eqoak.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%2Fq9n3bfjw0e6lhw0eqoak.png" alt="Copilot Documentation" width="800" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💬 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Assigning tasks to Copilot felt like having a tireless coding partner who not only executes but explains and documents. If you’re juggling multiple priorities or just want to accelerate your workflow, this feature is worth exploring.&lt;/p&gt;

&lt;p&gt;And you can check the final result here:&lt;br&gt;
&lt;a href="https://github.com/mehrandvd/skunit/pull/30" rel="noopener noreferrer"&gt;https://github.com/mehrandvd/skunit/pull/30&lt;/a&gt;&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>mcp</category>
      <category>programming</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Unit Test for MCP!</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Tue, 15 Apr 2025 21:22:48 +0000</pubDate>
      <link>https://dev.to/mehrandvd/unit-test-for-mcp-3501</link>
      <guid>https://dev.to/mehrandvd/unit-test-for-mcp-3501</guid>
      <description>&lt;p&gt;Imagine you have an MCP server; it's likely you'd want to &lt;strong&gt;create unit tests to ensure it functions as intended&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;In this post, I'll walk through how to write unit tests for MCP servers. We'll be testing an existing MCP Server available in Smithery: &lt;strong&gt;&lt;a href="https://smithery.ai/server/@yokingma/time-mcp" rel="noopener noreferrer"&gt;Time Server MCP&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As shown, it includes the following tools:&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%2Fm5qb1dbhvg0y8siyokao.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%2Fm5qb1dbhvg0y8siyokao.png" alt="Time Server MCP" width="744" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Unit Test
&lt;/h2&gt;

&lt;p&gt;First, we create a new &lt;code&gt;xunit&lt;/code&gt; project and add &lt;code&gt;skUnit&lt;/code&gt; from NuGet.&lt;/p&gt;

&lt;p&gt;Next, according to the skUnit documentation, our only task is to configure it in the test 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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeServerMcpTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ScenarioAssert&lt;/span&gt; &lt;span class="n"&gt;ScenarioAssert&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;span class="n"&gt;IChatClient&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;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;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TimeServerMcpTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITestOutputHelper&lt;/span&gt; &lt;span class="n"&gt;output&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;configuration&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;ConfigurationBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddUserSecrets&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeServerMcpTests&lt;/span&gt;&lt;span class="p"&gt;&amp;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;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI_ApiKey"&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;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI_Endpoint"&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;deploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI_Deployment"&lt;/span&gt;&lt;span class="p"&gt;];&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;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="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientModel&lt;/span&gt;&lt;span class="p"&gt;.&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;deploymentName&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="n"&gt;ScenarioAssert&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;ScenarioAssert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteLine&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;Fact&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="nf"&gt;Tools_MustWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Our tests will be here...&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;Let's start by writing the body for our test method. The first step is to establish a connection to the MCP Server.&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;clientTransport&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;StdioClientTransport&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;StdioClientTransportOptions&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;"Time MCP Server"&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="s"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"/c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"@smithery/cli@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"@yokingma/time-mcp"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Here's our MCP ready for testing.&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;mcp&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;McpClientFactory&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;clientTransport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we built a &lt;code&gt;ChatClient&lt;/code&gt; on it:&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;await&lt;/span&gt; &lt;span class="n"&gt;mcp&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="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="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;ChatClient&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="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&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="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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and finally our 3 lines of &lt;strong&gt;skUnit&lt;/strong&gt; to test the scenario:&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;scenarioText&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;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllTextAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TestScenario.md"&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;scenario&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatScenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenarioText&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;ScenarioAssert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PassAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that, here’s the outcome!&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%2Fv7v9xvai7hmwxfk873tw.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%2Fv7v9xvai7hmwxfk873tw.png" alt="Test Result" width="800" height="913"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Scenario
&lt;/h2&gt;

&lt;p&gt;But how did we specify the test scenario, and what was in the &lt;code&gt;TestScenario.md&lt;/code&gt; file that we loaded as our scenario?&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%2Fm42dqbuhq7s91ehd2ed6.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%2Fm42dqbuhq7s91ehd2ed6.png" alt="Test Scenario" width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in this scenario, we expect the answer to "What time is it?" should be any sentence or paragraph that has this semantic condition:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Semantic Condition:&lt;/strong&gt; It should mention a time.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### CHECK SemanticCondition&lt;/span&gt;
It mentions a time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also expect it to utilize the &lt;code&gt;current_time&lt;/code&gt; tool in MCP, and by following the assertion, we are ensuring this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Function Call Check:&lt;/strong&gt; It should call a tool.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### CHECK FunctionCall&lt;/span&gt;
{
  "function_name": "current_time",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, by asking "How many days are in January this year?", we test its other capability, ensuring it correctly calls &lt;code&gt;days_in_month&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="gu"&gt;## [USER]&lt;/span&gt;
How many days are in this year's january?

&lt;span class="gu"&gt;## [AGENT]&lt;/span&gt;

&lt;span class="gu"&gt;### CHECK SemanticCondition&lt;/span&gt;
It mentions 31 days.

&lt;span class="gu"&gt;### CHECK FunctionCall&lt;/span&gt;
{
  "function_name": "days_in_month",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally!
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;skUnit&lt;/code&gt;, it's super easy to test your AI blocks like &lt;strong&gt;MCP Server&lt;/strong&gt;, &lt;strong&gt;ChatClient&lt;/strong&gt;, &lt;strong&gt;Kernel&lt;/strong&gt;, and more. The full source code is available on my GitHub: &lt;strong&gt;&lt;a href="https://github.com/mehrandvd/skunit/tree/main/demos/Demo.TddMcp" rel="noopener noreferrer"&gt;Demo.TddMcp&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>openai</category>
      <category>tdd</category>
    </item>
    <item>
      <title>MCP Missing Pieces</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Sun, 06 Apr 2025 17:33:27 +0000</pubDate>
      <link>https://dev.to/mehrandvd/mcp-missing-pieces-31n0</link>
      <guid>https://dev.to/mehrandvd/mcp-missing-pieces-31n0</guid>
      <description>&lt;p&gt;I've been working with MCPs and building MCP servers for a while now. Here are my thoughts on the missing pieces in it.&lt;/p&gt;

&lt;p&gt;While MCPs (Modular Component Protocols) present an exciting frontier in modularizing and streamlining server functionality, their current design and implementation still leave room for improvement. The key areas of dependency handling, versioning, and conflict resolution highlight the challenges we face as we aim to optimize MCP systems for more complex and interconnected use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency
&lt;/h2&gt;

&lt;p&gt;I think &lt;strong&gt;soon we will need MCPs built on top of other MCPs&lt;/strong&gt;. For example, if I want to provide a service that takes lat/lon as input, and Google Maps has an MCP that gets an address and returns the lat/lon, it would be much more convenient if I could create an MCP assuming the client already has the dependent MCP (Google Maps for addresses).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MyMCP (v0.1.2)
|
+--+ GeoMCP (v0.0.5)
|  |
|  +-- GoogleMapMCP (v0.1.5)
+--+ OrganizerMCP (v0.6.8)
   | 
   +-- SlackMCP (v0.6.8)
   +-- TeamsMCP (v0.6.8)
   +-- CalendarMCP (v0.6.8)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ability to build MCPs on top of other MCPs could revolutionize how developers create layered, collaborative systems. However, it also introduces significant challenges, such as ensuring compatibility between dependencies, managing updates across interconnected modules, and avoiding circular dependencies. A robust framework for managing these dependencies efficiently will be vital as the ecosystem grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning
&lt;/h2&gt;

&lt;p&gt;It currently has some versioning, but we need a more workable standard. Something like Semantic Versioning, but optimized for semantic components like MCP.&lt;/p&gt;

&lt;p&gt;Current versioning practices are not sufficient to address the nuanced needs of semantic component systems like MCPs. Introducing an MCP-specific versioning standard, akin to Semantic Versioning but tailored to MCPs, could resolve ambiguities, streamline compatibility checks, and ensure smoother integration of updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conflict Resolution (Priority)
&lt;/h2&gt;

&lt;p&gt;At the moment, if multiple tools are available for a specific task, there's no way to indicate which one should be given higher priority.&lt;br&gt;
For instance, consider two MCP servers that have tools for getting the time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CalendarMCP
  - GetCurrentTime()

TimeMCP
  - GetNow()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which one should be used now?&lt;/p&gt;

&lt;p&gt;As MCPs proliferate, conflicts will inevitably arise when multiple modules offer overlapping functionalities. A standardized mechanism for prioritizing tools, perhaps guided by metadata, user preferences, or usage context, could provide clarity and prevent execution errors. Incorporating intelligent arbitration methods or user-defined priorities could further enhance system reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other missing pieces?
&lt;/h2&gt;

&lt;p&gt;What other missing pieces do you think we have?&lt;/p&gt;

&lt;p&gt;What are your thoughts on these challenges? Are there other aspects of MCPs you believe need attention? Let’s continue the conversation and explore how we can shape the future of MCP development together.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>openai</category>
    </item>
    <item>
      <title>Create an MCP Server with .NET and C#</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Sun, 23 Mar 2025 21:36:06 +0000</pubDate>
      <link>https://dev.to/mehrandvd/create-an-mcp-server-with-net-and-c-251p</link>
      <guid>https://dev.to/mehrandvd/create-an-mcp-server-with-net-and-c-251p</guid>
      <description>&lt;p&gt;In this post, I'll show you how to create a simple MCP Server and test it in Cursor.&lt;/p&gt;

&lt;p&gt;For this tutorial, I'm using the official &lt;code&gt;csharp-sdk&lt;/code&gt;, which is still in its early stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/csharp-sdk" rel="noopener noreferrer"&gt;https://github.com/modelcontextprotocol/csharp-sdk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Create a simple project
&lt;/h2&gt;

&lt;p&gt;First, create an empty console project:&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;dotnet&lt;/span&gt; &lt;span class="k"&gt;new&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;n&lt;/span&gt; &lt;span class="n"&gt;Tutorial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;McpTimeServer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Install packages
&lt;/h2&gt;

&lt;p&gt;Install these 2 packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add package Microsoft.Extensions.Hosting
dotnet add package ModelContextProtocol --prerelease
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Write the code
&lt;/h2&gt;

&lt;p&gt;Replace the &lt;code&gt;Program.cs&lt;/code&gt; content with 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;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;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;ModelContextProtocol&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="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;"Hello MCP World!"&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;CreateEmptyApplicationBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&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;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;WithTools&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;host&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="k"&gt;await&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;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpToolType&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;class&lt;/span&gt; &lt;span class="nc"&gt;TimeTool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpTool&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;"Gets the current time."&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="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetCurrentTime&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;DateTimeOffset&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="nf"&gt;ToString&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;h2&gt;
  
  
  4. Run the project
&lt;/h2&gt;

&lt;p&gt;If you run the project using &lt;code&gt;dotnet run&lt;/code&gt;, you will see &lt;code&gt;Hello MCP World!&lt;/code&gt; and the program will remain open as it's listening to &lt;code&gt;stdin&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Test it on Cursor
&lt;/h2&gt;

&lt;p&gt;Now it's time to configure it on Cursor. Go to &lt;code&gt;File -&amp;gt; Preferences -&amp;gt; Cursor Settings&lt;/code&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.amazonaws.com%2Fuploads%2Farticles%2F2k9lbp2coza5mjpyogkt.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%2F2k9lbp2coza5mjpyogkt.png" alt="Cursor Settings" width="673" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on "Add new global MCP Server" or open &lt;code&gt;.cursor/mcp.json&lt;/code&gt; and add your MCP Server information like this:&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;"timemcp"&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;"cmd"&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="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/c"&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:/Users/Mehran/source/repos/Tutorial.McpTimeServer/bin/Debug/net9.0/Tutorial.McpTimeServer.exe"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your MCP Servers page should look like this:&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%2Fygm9zhnub6f0ds45hbjn.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%2Fygm9zhnub6f0ds45hbjn.png" alt="MCP Servers" width="674" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Test your MCP Server
&lt;/h2&gt;

&lt;p&gt;Now ask about the time, and it will use your tool.&lt;br&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%2Fwpt2fldhv60n981arken.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%2Fwpt2fldhv60n981arken.png" alt="Run Tool" width="453" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you click on "Run tool," it displays the time when the MCP tool we created was called.&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%2Fn03yx8609wuenhhwkvh2.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%2Fn03yx8609wuenhhwkvh2.png" alt="MCP Result" width="452" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Find the code
&lt;/h2&gt;

&lt;p&gt;You can find the complete code on my GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mehrandvd/tutorial-mcp-server-dotnet" rel="noopener noreferrer"&gt;https://github.com/mehrandvd/tutorial-mcp-server-dotnet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>openai</category>
    </item>
    <item>
      <title>Demystifying AIContents in Microsoft.Extensions.AI</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Mon, 13 Jan 2025 21:07:50 +0000</pubDate>
      <link>https://dev.to/mehrandvd/demystifying-aicontents-in-microsoftextensionsai-5hg8</link>
      <guid>https://dev.to/mehrandvd/demystifying-aicontents-in-microsoftextensionsai-5hg8</guid>
      <description>&lt;p&gt;At the core of &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; is an &lt;code&gt;IChatClient&lt;/code&gt;, which serves as a unified approach to working with various OpenAI service providers.&lt;/p&gt;

&lt;p&gt;It utilizes a list of messages (&lt;code&gt;IList&amp;lt;ChatMessage&amp;gt;&lt;/code&gt;) to provide input for &lt;code&gt;CompleteAsync()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each message has a property called &lt;code&gt;Contents&lt;/code&gt;, allowing each message have multiple contents sequentially, making it a multi-modal message.&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;IList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;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;ChatMessage&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;Contents&lt;/span&gt;&lt;span class="p"&gt;:&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="s"&gt;"Hello, what is in my image?"&lt;/span&gt;
      &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"[Image1.jpg]"&lt;/span&gt;
  &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ChatMessage&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;Contents&lt;/span&gt;&lt;span class="p"&gt;:&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="s"&gt;"There is a BMW car in your image"&lt;/span&gt;
      &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"[Voice of above sentence.]"&lt;/span&gt;
  &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ChatMessage&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;Contents&lt;/span&gt;&lt;span class="p"&gt;:&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="s"&gt;"What’s its price?"&lt;/span&gt;
  &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ChatMessage&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;Tool&lt;/span&gt;
    &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;FunctionCall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GetPrice(“BMW“)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this blog, I will clarify the types of content currently supported. Firstly, of these contents derive from a base class called &lt;code&gt;AIContent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The inheritance hierarchy &lt;code&gt;AIContent&lt;/code&gt; as follows:&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%2Fon84ey4evrkj7nd2bcek.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%2Fon84ey4evrkj7nd2bcek.png" alt="AIContent Inheritance" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Different AI Contents
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;AIContent&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TextContent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DataContent&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ImageContent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AudioContent&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;code&gt;UsageContent&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;FunctionCallContent&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;FunctionResultContent&lt;/code&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  AIContent
&lt;/h2&gt;

&lt;p&gt;Every content is an &lt;code&gt;AIContent&lt;/code&gt;. It contains the shared functionalities between different types.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AdditionalProperties&lt;/strong&gt;: Some key-value properties.
-RawRepresentation*&lt;em&gt;: The original raw representation of the content from an underlying implementation. It contains an of the proper with the underlying technology, whether it is an *OpenAI&lt;/em&gt;, &lt;em&gt;AzureOpenAI&lt;/em&gt;, &lt;em&gt;Ollama&lt;/em&gt;, or ...&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TextContent
&lt;/h2&gt;

&lt;p&gt;It's simple text content having one property: &lt;code&gt;Text&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;new&lt;/span&gt; &lt;span class="n"&gt;TextContent&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="s"&gt;"Hello, what can you do for me?"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ImageContent &amp;amp; AudioContent
&lt;/h2&gt;

&lt;p&gt;Both of these types are &lt;code&gt;DataContent&lt;/code&gt; and are very like each other.&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;new&lt;/span&gt; &lt;span class="nf"&gt;ImageContent&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="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://www.example.com/image)"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;mediaType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"image/png"&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;AudioContent&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="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://www.example.com/voice)"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;mediaType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"audio/wav"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  UsageContent
&lt;/h2&gt;

&lt;p&gt;This type of content includes data about the token consumption of 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="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UsageContent&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;UsageDetails&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="n"&gt;InputTokenCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OutputTokenCount&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="n"&gt;TotalTokenCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30&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;h2&gt;
  
  
  FunctionCallContent
&lt;/h2&gt;

&lt;p&gt;It indicates a function call that is requested by AI to be evaluated.&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;new&lt;/span&gt; &lt;span class="nf"&gt;FunctionCallContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fx12"&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;"GetFoodMenu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arguments&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;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="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s"&gt;"Happy"&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;h2&gt;
  
  
  FunctionResultContent
&lt;/h2&gt;

&lt;p&gt;It indicates that a function call been invoked by client, and the result is ready to be reported to the AI&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;new&lt;/span&gt; &lt;span class="nf"&gt;FunctionResultContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fx12"&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;"GetFoodMenu"&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="s"&gt;"Pizza, Burger, Ice Cream"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In conclusion, the &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; library offers a versatile and unified approach to working with various OpenAI service providers through its &lt;code&gt;IChatClient&lt;/code&gt;. By utilizing a list of messages (&lt;code&gt;IList&amp;lt;ChatMessage&amp;gt;&lt;/code&gt;) with a &lt;code&gt;Contents&lt;/code&gt; property, it supports multi-modal messages that can include text, images, audio, and function calls. This flexibility allows for more dynamic and interactive AI-driven applications.&lt;/p&gt;

&lt;p&gt;The different types of content supported by &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; all inherit from the base class &lt;code&gt;AIContent&lt;/code&gt;, which provides shared functionalities. These content types include &lt;code&gt;TextContent&lt;/code&gt;, &lt;code&gt;ImageContent&lt;/code&gt;, &lt;code&gt;AudioContent&lt;/code&gt;, &lt;code&gt;UsageContent&lt;/code&gt;, &lt;code&gt;FunctionCallContent&lt;/code&gt;, and &lt;code&gt;FunctionResultContent&lt;/code&gt;, each with specific properties and functionalities. The provided code examples demonstrate how these content types can be effectively used in practice, making the library a powerful tool for developers looking to integrate AI capabilities into their applications.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>openai</category>
      <category>extensions</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Parse OpenAI answers as JSON</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Thu, 20 Jun 2024 21:07:24 +0000</pubDate>
      <link>https://dev.to/mehrandvd/parse-openai-result-as-json-p2a</link>
      <guid>https://dev.to/mehrandvd/parse-openai-result-as-json-p2a</guid>
      <description>&lt;p&gt;Certainly! Here's a revised version of your blog post that is concise, simple, and friendly:&lt;/p&gt;




&lt;p&gt;Working with OpenAI can be tricky when it comes to parsing JSON responses, especially when they include extra characters like &lt;code&gt;'''&lt;/code&gt; or a leading &lt;code&gt;json:&lt;/code&gt;. To tackle this, I've developed &lt;code&gt;PowerParseJson&amp;lt;T&amp;gt;()&lt;/code&gt;, a handy tool that simplifies the process.&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;result&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;GetChatCompletionsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatCompletionsOptions&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;answer&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;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Choices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&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="c1"&gt;// No more exceptions!&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="n"&gt;SemanticUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PowerParseJson&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find &lt;code&gt;PowerParseJson&amp;lt;T&amp;gt;()&lt;/code&gt; on my GitHub: &lt;a href="https://github.com/mehrandvd/SemanticValidation/" rel="noopener noreferrer"&gt;mehrandvd/SemanticValidation&lt;/a&gt;. It is also available here as a Nuget package: &lt;a href="https://www.nuget.org/packages/SemanticValidation/" rel="noopener noreferrer"&gt;SemanticValidation Nuget&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While newer GPT models offer settings for cleaner JSON outputs, many models still lack this feature. &lt;code&gt;PowerParseJson&amp;lt;T&amp;gt;()&lt;/code&gt; is here to bridge that gap and make your OpenAI experience smoother.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>openai</category>
      <category>json</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Semantic Tests for SemanticKernel Plugins using skUnit</title>
      <dc:creator>Mehran Davoudi</dc:creator>
      <pubDate>Thu, 04 Jan 2024 14:20:28 +0000</pubDate>
      <link>https://dev.to/mehrandvd/semantic-tests-for-semantickernel-plugins-using-skunit-18a9</link>
      <guid>https://dev.to/mehrandvd/semantic-tests-for-semantickernel-plugins-using-skunit-18a9</guid>
      <description>&lt;h2&gt;
  
  
  Exploring SemanticKernel
&lt;/h2&gt;

&lt;p&gt;This week, I had the chance to explore the &lt;a href="https://github.com/microsoft/semantic-kernel" rel="noopener noreferrer"&gt;SemanticKernel&lt;/a&gt; code base, particularly the core plugins. SemanticKernel comes equipped with these &lt;strong&gt;built-in plugins&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ConversationSummaryPlugin&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;FilePlugin&lt;/li&gt;
&lt;li&gt;HttpPlugin&lt;/li&gt;
&lt;li&gt;MathPlugin&lt;/li&gt;
&lt;li&gt;TextPlugin&lt;/li&gt;
&lt;li&gt;TimePlugin&lt;/li&gt;
&lt;li&gt;WaitPlugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I looked at the &lt;code&gt;Plugins.UnitTests&lt;/code&gt; project, I noticed that all the unit tests are passing. But there's something interesting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each plugin has a corresponding test file, &lt;strong&gt;except for ConversationSummaryPlugin&lt;/strong&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You might wonder why!?&lt;/p&gt;

&lt;p&gt;Here's the thing. All the other plugins have outputs that can be tested because they're deterministic. But &lt;code&gt;ConversationSummaryPlugin&lt;/code&gt; is a different story.&lt;/p&gt;

&lt;p&gt;For instance, it has a function called &lt;code&gt;SummarizeConversation&lt;/code&gt; that does exactly what it says - it summarizes a conversation. &lt;strong&gt;But how do you test something like that?&lt;/strong&gt; You need to check the meaning of the output, not just if the strings are identical.&lt;/p&gt;

&lt;p&gt;Let's consider this test case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;USER: Is Eiffel tall?
AGENT: Yes it is
USER: What about Everest mountain?
AGENT: Yes it is tall too
USER: What about a mouse?
AGENT: No it is not tall.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you call &lt;code&gt;SummarizeConversation&lt;/code&gt; with this input, you should get something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt; The conversation is about the heights of different things. Both the Eiffel Tower and Mount Everest are considered tall, while a mouse is not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But how do you write a test for that? You need to use semantic assertions, something like:&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;SemanticAssert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="s"&gt;"It mentions that both the Eiffel Tower and Mount Everest are tall."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While you can do this now with the &lt;a href="https://github.com/mehrandvd/SemanticValidation" rel="noopener noreferrer"&gt;SemanticValidation&lt;/a&gt; library, I'm going to introduce an even simpler way in this post: using the &lt;a href="https://github.com/mehrandvd/skunit" rel="noopener noreferrer"&gt;skUnit&lt;/a&gt; library for semantic unit testing. Sounds exciting, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Dive into Testing with skUnit
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://github.com/mehrandvd/skunit" rel="noopener noreferrer"&gt;skUnit&lt;/a&gt;, you can whip up scenarios in markdown files. Here's an example:&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;# SCENARIO Height Discussion&lt;/span&gt;

&lt;span class="gu"&gt;## PARAMETER input&lt;/span&gt;
USER: Is Eiffel tall?
AGENT: Yes it is
USER: What about Everest mountain?
AGENT: Yes it is tall too
USER: What about a mouse?
AGENT: No it is not tall.&lt;span class="sb"&gt;


&lt;/span&gt;&lt;span class="gu"&gt;## ANSWER&lt;/span&gt;
The conversation revolves around the heights of different things. Both the Eiffel Tower and Mount Everest get the tall vote, while a mouse doesn't.

&lt;span class="gu"&gt;## CHECK SemanticCondition&lt;/span&gt;
It mentions that both the Eiffel Tower and Mount Everest are tall.

&lt;span class="gu"&gt;## CHECK SemanticCondition&lt;/span&gt;
It mentions that a mouse isn't tall.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, in a scenario, you can set the parameters and the expected answer. Then, you can specify the semantic conditions that the output should meet. The best part? &lt;strong&gt;skUnit can run this test for you automatically&lt;/strong&gt;, and you can see it acing the test. How cool is that?&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%2Fl13oazx2b27ejnp97hyv.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%2Fl13oazx2b27ejnp97hyv.png" alt="Test result" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What’s great is that these scenarios are valid .md files. This means they’re not just for the tech-savvy among us - anyone can read and understand them! Isn’t that neat?&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%2Fv5uw1ykjn1owxad5sg3d.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%2Fv5uw1ykjn1owxad5sg3d.png" alt="Markdown" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;I enjoy writing semantic tests for SemanticKernel plugins, and I have created a repository to share some of them: &lt;a href="https://github.com/mehrandvd/semantic-kernel-skunit-tests" rel="noopener noreferrer"&gt;https://github.com/mehrandvd/semantic-kernel-skunit-tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see an example of a test scenario for the SummarizeConversationPlugin &lt;a href="https://github.com/mehrandvd/semantic-kernel-skunit-tests/blob/main/SemanticKernel.skUnit.Tests/SemanticKernel.Plugins.Core.Tests/SummarizeConversationPluginTests/Scenarios/EiffelChat.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>openai</category>
      <category>semantickernel</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
