<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Function Tools on dotnetuniversity</title>
        <link>https://dotnetuniversity.com/tags/function-tools/</link>
        <description>Recent content in Function Tools on dotnetuniversity</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Sat, 15 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://dotnetuniversity.com/tags/function-tools/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Microsoft Agent Framework II: Tools, MCP, Memory, and RAG</title>
        <link>https://dotnetuniversity.com/microsoft-agent-framework-ii-tools-mcp-memory-and-rag/</link>
        <pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://dotnetuniversity.com/microsoft-agent-framework-ii-tools-mcp-memory-and-rag/</guid>
        <description>&lt;img src="https://dotnetuniversity.com/microsoft-agent-framework-ii-tools-mcp-memory-and-rag/taha-h5ffuwIyVVk-unsplash.jpg" alt="Featured image of post Microsoft Agent Framework II: Tools, MCP, Memory, and RAG" /&gt;&lt;h1 id=&#34;introduction&#34;&gt;Introduction
&lt;/h1&gt;&lt;p&gt;Part I created a single-turn agent that could understand a customer message and return a safe response. Real support work needs more: the agent must read trusted business data, find the right support policy, and remember the conversation without inventing facts.&lt;/p&gt;
&lt;p&gt;This article adds those capabilities with Microsoft Agent Framework. We will build a support agent that can look up an order and search a small Qdrant vector database. The sample keeps both tools read-only and uses local Ollama embeddings so it is easy to run and understand.&lt;/p&gt;
&lt;h1 id=&#34;real-world-scenario-a-pending-payment&#34;&gt;Real-World Scenario: A Pending Payment
&lt;/h1&gt;&lt;p&gt;A customer says:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt&#34;&gt;
&lt;div class=&#34;copilot-prompt__header&#34;&gt;Customer Prompt&lt;/div&gt;
&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;code data-lang=&#34;text&#34;&gt;My order ORD-1001 failed twice, but I see a pending charge. Is my order confirmed?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A model alone cannot know the order status or the company’s pending-charge policy. The agent needs two controlled sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GetOrderStatus&lt;/code&gt; reads the order system.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SearchSupportKnowledge&lt;/code&gt; retrieves approved support guidance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The model decides which tool is useful, but the application owns the tool implementation and its authorization boundary.&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart LR
  C((Customer question)) --&gt; A{{Support agent}}
  A --&gt;|order status| O[[GetOrderStatus tool]]
  A --&gt;|support policy| K[[SearchSupportKnowledge tool]]
  O --&gt; D[(Order data)]
  K --&gt; KB[(Support knowledge base)]
  D --&gt; A
  KB --&gt; A
  A --&gt; R([Grounded response])
  R --&gt; C

  classDef customer fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f
  classDef agent fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a
  classDef tool fill:#cffafe,stroke:#0891b2,stroke-width:2px,color:#164e63
  classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
  classDef response fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843

  class C customer
  class A agent
  class O,K tool
  class D,KB data
  class R response&lt;/div&gt;
&lt;h1 id=&#34;1-function-tools-let-the-agent-read-business-data&#34;&gt;1. Function Tools: Let the Agent Read Business Data
&lt;/h1&gt;&lt;h2 id=&#34;what-problem-do-tools-solve&#34;&gt;What problem do tools solve?
&lt;/h2&gt;&lt;p&gt;The model understands language, but it does not automatically know the current state of an order, account, or payment. A function tool gives it a controlled way to ask the application for that information.&lt;/p&gt;
&lt;p&gt;For the pending-payment case, the agent extracts &lt;code&gt;ORD-1001&lt;/code&gt; and calls &lt;code&gt;get_order_status&lt;/code&gt;. The application reads the order system and returns &lt;code&gt;Payment review&lt;/code&gt; and &lt;code&gt;Pending charge&lt;/code&gt;. The model explains those facts; it does not invent them.&lt;/p&gt;
&lt;div class=&#34;copilot-prompt&#34;&gt;
&lt;div class=&#34;copilot-prompt__header&#34;&gt;Tool Interaction&lt;/div&gt;
&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;code data-lang=&#34;text&#34;&gt;Customer: Is ORD-1001 confirmed?
Agent:    Call get_order_status(orderId = &#34;ORD-1001&#34;)
System:   { status: &#34;Payment review&#34;, paymentStatus: &#34;Pending charge&#34; }
Agent:    Your order is not confirmed yet. The payment is still pending.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;how-is-a-function-tool-built&#34;&gt;How is a function tool built?
&lt;/h2&gt;&lt;p&gt;A function tool is a typed application function exposed to the agent. Its name, description, and parameter schema help the model decide when to request it. The function itself remains normal application code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AIFunction orderTool = AIFunctionFactory.Create(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; orderId) =&amp;gt; orderStore.GetOrderStatus(orderId),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;get_order_status&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use narrow inputs, validate arguments, return predictable data, and start with read-only tools. A &lt;code&gt;create_refund&lt;/code&gt; tool would need authorization, idempotency, and human approval because it changes business state. Tool descriptions guide selection, but the host remains responsible for security.&lt;/p&gt;
&lt;h1 id=&#34;2-mcp-share-tools-across-applications&#34;&gt;2. MCP: Share Tools Across Applications
&lt;/h1&gt;&lt;h2 id=&#34;what-problem-does-mcp-solve&#34;&gt;What problem does MCP solve?
&lt;/h2&gt;&lt;p&gt;Suppose the company already owns a payment-support service used by its web portal, call-center application, and several agents. Copying its tool code into every agent creates duplicated integrations. The Model Context Protocol (MCP) gives that service a standard way to publish tools that different hosts can discover and call.&lt;/p&gt;
&lt;p&gt;For the same customer question, the flow can become:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt&#34;&gt;
&lt;div class=&#34;copilot-prompt__header&#34;&gt;MCP Tool Flow&lt;/div&gt;
&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;code data-lang=&#34;text&#34;&gt;Agent host -&gt; MCP client -&gt; Payment MCP server -&gt; Payment system
                   |
                   +--&gt; get_order_status&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The agent still decides that order status is needed. The MCP server owns connection details, authorization, and the actual call to the payment system. MCP changes where the capability lives and how it is discovered; it does not replace the agent, conversation memory, or RAG.&lt;/p&gt;
&lt;h2 id=&#34;local-function-tool-versus-mcp-tool&#34;&gt;Local function tool versus MCP tool
&lt;/h2&gt;&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Boundary&lt;/th&gt;
          &lt;th&gt;Function tool&lt;/th&gt;
          &lt;th&gt;MCP tool&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Tool owner&lt;/td&gt;
          &lt;td&gt;Same application process&lt;/td&gt;
          &lt;td&gt;Separate MCP server&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Discovery&lt;/td&gt;
          &lt;td&gt;Registered in application code&lt;/td&gt;
          &lt;td&gt;Discovered through MCP&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Transport&lt;/td&gt;
          &lt;td&gt;In-process call&lt;/td&gt;
          &lt;td&gt;Stdio or HTTP&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Reuse&lt;/td&gt;
          &lt;td&gt;Mainly one host&lt;/td&gt;
          &lt;td&gt;Many hosts and agents&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Security&lt;/td&gt;
          &lt;td&gt;Host authorization&lt;/td&gt;
          &lt;td&gt;Server authorization plus host policy&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A .NET host can discover MCP tools and pass them to an agent or chat client:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#fab387&#34;&gt;var&lt;/span&gt; mcpClient = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; McpClient.CreateAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt; StdioClientTransport(&lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Name = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;payment-support&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Command = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;dotnet&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Arguments = [&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;run&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;--project&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;PaymentSupport.McpServer&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; tools = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; mcpClient.ListToolsAsync();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The Part II sample runs both tools in a local &lt;code&gt;SupportMcpServer&lt;/code&gt; over stdio. The API discovers them at startup, so you can see the MCP boundary without deploying a remote service. In production, treat every discovered MCP tool as untrusted until the host applies an allow-list and authorization policy.&lt;/p&gt;
&lt;h1 id=&#34;3-conversation-memory-continue-the-case&#34;&gt;3. Conversation Memory: Continue the Case
&lt;/h1&gt;&lt;h2 id=&#34;what-problem-does-memory-solve&#34;&gt;What problem does memory solve?
&lt;/h2&gt;&lt;p&gt;Without conversation memory, every HTTP request looks like a new conversation. A customer would need to repeat the order number and the entire problem on every message.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt&#34;&gt;
&lt;div class=&#34;copilot-prompt__header&#34;&gt;Conversation Memory Example&lt;/div&gt;
&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;code data-lang=&#34;text&#34;&gt;Message 1: My order ORD-1001 failed twice, but I see a pending charge.
Agent:     ORD-1001 is in payment review and has a pending charge.
&lt;p&gt;Message 2: Should I try payment again?
Agent:     No. Based on the same case and policy, avoid repeated attempts.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The API sends a &lt;code&gt;sessionId&lt;/code&gt; with both requests. The runtime maps that ID to an &lt;code&gt;AgentSession&lt;/code&gt;, so the framework preserves previous messages and tool results:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;if&lt;/span&gt; (!_sessions.TryGetValue(sessionId, &lt;span style=&#34;color:#cba6f7&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; session))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  session = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; agent.CreateSessionAsync(cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  _sessions[sessionId] = session;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; response = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; agent.RunAsync(prompt, session,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  cancellationToken: cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is conversation memory, not a customer profile database. Long-term memory would store durable facts such as a preferred language or verified account identifier and would require its own identity, consent, retention, and access controls. The sample stores sessions in process for teaching; production APIs need durable, tenant-aware session storage.&lt;/p&gt;
&lt;h1 id=&#34;4-rag-with-qdrant-retrieve-approved-policy&#34;&gt;4. RAG with Qdrant: Retrieve Approved Policy
&lt;/h1&gt;&lt;h2 id=&#34;what-problem-does-rag-solve&#34;&gt;What problem does RAG solve?
&lt;/h2&gt;&lt;p&gt;The order tool tells us what happened to &lt;code&gt;ORD-1001&lt;/code&gt;; it does not tell us what company policy says about pending card authorizations. RAG retrieves the relevant policy passage and gives it to the model as evidence.&lt;/p&gt;
&lt;p&gt;For this case, the retrieval flow is:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt&#34;&gt;
&lt;div class=&#34;copilot-prompt__header&#34;&gt;RAG Retrieval Flow&lt;/div&gt;
&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;code data-lang=&#34;text&#34;&gt;Question: &#34;I see a pending charge. Is my order confirmed?&#34;
  |
  v
Create query embedding -&gt; Search Qdrant -&gt; Select support passage
  |
  v
Model uses passage: &#34;Pending card charges can be temporary authorization holds...&#34;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;RAG has two distinct stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Indexing:&lt;/strong&gt; split documents into passages, create embeddings, and store vectors plus source metadata.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrieval:&lt;/strong&gt; embed the user question, search the vector database, apply access filters and a similarity threshold, then provide selected passages to the model.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The sample uses Qdrant for vector storage and search, and Ollama runs the local &lt;code&gt;nomic-embed-text&lt;/code&gt; model to create 768-dimensional embeddings. This avoids requiring a second paid API while still using real semantic embeddings. The sample seeds one approved pending-charge passage when the collection is first used.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; results = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; qdrant.QueryAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;support-knowledge&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  query: Embed(query),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  limit: &lt;span style=&#34;color:#fab387&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  cancellationToken: cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;return&lt;/span&gt; results.Count == &lt;span style=&#34;color:#fab387&#34;&gt;0&lt;/span&gt; || results[&lt;span style=&#34;color:#fab387&#34;&gt;0&lt;/span&gt;].Score &amp;lt; &lt;span style=&#34;color:#fab387&#34;&gt;0.15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ? &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;No matching support guidance was found.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  : results[&lt;span style=&#34;color:#fab387&#34;&gt;0&lt;/span&gt;].Payload[&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;].StringValue;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;RAG is not simply “put documents in the prompt.” Retrieved text is evidence, not permission. Filter by tenant and user access, retain source metadata, defend against prompt injection in documents, and do not let retrieved text override application rules.&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;%%{init: {&#34;flowchart&#34;: {&#34;nodeSpacing&#34;: 12, &#34;rankSpacing&#34;: 22}, &#34;themeVariables&#34;: {&#34;fontSize&#34;: &#34;12px&#34;}}}%%
flowchart LR
  Q((Question)) --&gt; E[Embed and search]
  E --&gt; F[Filter approved evidence]
  F --&gt; M((Model))
  M --&gt; R([Grounded answer])

  classDef question fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f
  classDef search fill:#cffafe,stroke:#0891b2,stroke-width:2px,color:#164e63
  classDef model fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a
  classDef answer fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d

  class Q question
  class E,F search
  class M model
  class R answer&lt;/div&gt;
&lt;h1 id=&#34;connect-the-agent-to-mcp-tools&#34;&gt;Connect the Agent to MCP Tools
&lt;/h1&gt;&lt;p&gt;The sample discovers both tools from &lt;code&gt;SupportMcpServer&lt;/code&gt; and passes them to &lt;code&gt;AsAIAgent&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#fab387&#34;&gt;var&lt;/span&gt; mcpClient = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; McpClient.CreateAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt; StdioClientTransport(&lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Name = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;support-tools&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Command = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;dotnet&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Arguments = [&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;run&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;--project&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;SupportMcpServer/SupportMcpServer.csproj&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; tools = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; mcpClient.ListToolsAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AIAgent agent = chatClient.AsAIAgent(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    instructions: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;You are a support assistant. Use tools for order facts and policy. &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Never invent an order status or claim an action was completed.&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tools: [.. tools]);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The model may call one or both MCP tools before producing its answer. &lt;code&gt;search_support_knowledge&lt;/code&gt; calls Ollama for an embedding and Qdrant for the matching policy passage. Tool execution should be observable and bounded. Validate &lt;code&gt;orderId&lt;/code&gt;, limit query length, set timeouts, and return safe errors rather than leaking internal exceptions.&lt;/p&gt;
&lt;h1 id=&#34;the-sample-application&#34;&gt;The Sample Application
&lt;/h1&gt;&lt;p&gt;The sample is an expanded version of Part I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AgentTools&lt;/code&gt; creates the agent and exposes &lt;code&gt;AgentRuntime&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AgentTools.Api&lt;/code&gt; hosts the HTTP endpoint.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OrderStore&lt;/code&gt; contains deterministic order data for &lt;code&gt;ORD-1001&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QdrantSupportKnowledgeBase&lt;/code&gt; stores and retrieves approved pending-charge guidance.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AgentSessionStore&lt;/code&gt; keeps the conversation session in memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The endpoint accepts a session ID so the customer can ask a follow-up question:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;sessionId&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;case-1001&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;prompt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;My order ORD-1001 failed twice, but I see a pending charge. Is my order confirmed?&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The first request can use tools to answer the question. A second request with the same session ID can ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;copilot-prompt&#34;&gt;
&lt;div class=&#34;copilot-prompt__header&#34;&gt;Customer Follow-up Prompt&lt;/div&gt;
&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;code data-lang=&#34;text&#34;&gt;What should I do next, and should I try payment again?&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The session preserves the conversation. The order tool and knowledge tool provide facts; the model turns those facts into a customer-facing answer.&lt;/p&gt;
&lt;h1 id=&#34;run-the-sample&#34;&gt;Run the Sample
&lt;/h1&gt;&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites
&lt;/h2&gt;&lt;p&gt;Install the .NET 10 SDK and Docker Desktop with Docker Compose. You also need a DeepSeek API key. Compose starts Qdrant and Ollama locally and downloads the &lt;code&gt;nomic-embed-text&lt;/code&gt; embedding model on first startup, so allow time, network access, and local disk space for the initial download.&lt;/p&gt;
&lt;p&gt;From the sample directory:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f5e0dc&#34;&gt;$env:DS_KEY&lt;/span&gt; = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;&amp;lt;your-deepseek-api-key&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f5e0dc&#34;&gt;$env:QDRANT_URL&lt;/span&gt; = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;http://localhost:6334&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f5e0dc&#34;&gt;$env:EMBEDDING_URL&lt;/span&gt; = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;http://localhost:11500&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f5e0dc&#34;&gt;$env:EMBEDDING_MODEL&lt;/span&gt; = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;nomic-embed-text&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet run --project AgentTools.&lt;span style=&#34;color:#fab387&#34;&gt;Api&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Send the real-world request:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -X POST http://localhost:5000/api/agent/chat &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -H &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -d &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#39;{&amp;#34;sessionId&amp;#34;:&amp;#34;case-1001&amp;#34;,&amp;#34;prompt&amp;#34;:&amp;#34;My order ORD-1001 failed twice, but I see a pending charge. Is my order confirmed?&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The response includes the session ID and generated answer. Send another request with &lt;code&gt;case-1001&lt;/code&gt; to continue the conversation.&lt;/p&gt;
&lt;p&gt;The order data and seeded Qdrant passage make the sample reproducible at the application boundary. Compose starts Qdrant and Ollama, then pulls &lt;code&gt;nomic-embed-text&lt;/code&gt; automatically. The generated wording still varies by model provider. When &lt;code&gt;QDRANT_URL&lt;/code&gt; is omitted, the sample uses its in-memory fallback so the API and tests can run without Docker.&lt;/p&gt;
&lt;h1 id=&#34;testing-strategy&#34;&gt;Testing Strategy
&lt;/h1&gt;&lt;p&gt;Unit tests verify deterministic order and retrieval behavior. Integration tests run the real API, MCP server, DeepSeek agent, Ollama embedding service, and Qdrant database. The live scenario sends the pending-payment question, verifies the response contains the known order and pending-payment facts, sends a follow-up with the same session ID, and verifies that the agent uses conversation memory to answer the retry question.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet &lt;span style=&#34;color:#89dceb&#34;&gt;test&lt;/span&gt; --solution agent-tools-mcp-rag-sample.slnx --no-restore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The live-provider fixture loads &lt;code&gt;DS_KEY&lt;/code&gt; automatically. It is skipped when the key is unavailable.&lt;/p&gt;
&lt;h1 id=&#34;production-considerations&#34;&gt;Production Considerations
&lt;/h1&gt;&lt;p&gt;The order store and local embedding setup are educational. Production systems should add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A managed embedding service or secured local embedding deployment.&lt;/li&gt;
&lt;li&gt;A managed or containerized Qdrant deployment with backups and collection-level access control.&lt;/li&gt;
&lt;li&gt;Durable, tenant-aware session storage.&lt;/li&gt;
&lt;li&gt;Authentication and authorization for every session and tool call.&lt;/li&gt;
&lt;li&gt;Tool timeouts, retries, idempotency, and audit logs.&lt;/li&gt;
&lt;li&gt;Access-controlled retrieval with source metadata and citations.&lt;/li&gt;
&lt;li&gt;Prompt-injection defenses for documents, MCP servers, and tool results.&lt;/li&gt;
&lt;li&gt;Token, latency, and cost limits.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;Tools give agents controlled access to application behavior. MCP provides a standard boundary for tools owned by another process or service. Memory preserves conversation state, while RAG supplies relevant external knowledge for the current question.&lt;/p&gt;
&lt;p&gt;Together, these capabilities turn the Part I chat agent into a useful support assistant without giving the model unrestricted access to business systems. The next article adds Agent Harness, planning, context management, and approval-oriented execution.&lt;/p&gt;
&lt;h2 id=&#34;run-the-complete-example&#34;&gt;Run the Complete Example
&lt;/h2&gt;&lt;p&gt;The complete runnable sample contains the API, MCP server, Qdrant setup, Ollama embedding service, DeepSeek integration, and tests for the two-turn support scenario:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/meysamhadeli/blog-samples/tree/main/src/agent-tools-mcp-rag-sample&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://github.com/meysamhadeli/blog-samples/tree/main/src/agent-tools-mcp-rag-sample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/agent-framework/get-started/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Get started with Agent Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/agent-framework/agents/tools/function-tools&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Function tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/agent-framework/integrations/by-component/mcp&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;MCP integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/agent-framework/agents/conversations&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Conversations and memory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/agent-framework/overview&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Microsoft Agent Framework overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
