<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Mcp on Bartosz&#39;s blog</title>
    <link>https://ocytko.net/tags/mcp/</link>
    <description>Recent content in Mcp on Bartosz&#39;s blog</description>
    <generator>Hugo -- 0.155.3</generator>
    <language>en</language>
    <copyright>Bartosz Ocytko</copyright>
    <lastBuildDate>Thu, 19 Feb 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://ocytko.net/tags/mcp/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>MCP Sampling</title>
      <link>https://ocytko.net/posts/mcp-sampling/</link>
      <pubDate>Thu, 19 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://ocytko.net/posts/mcp-sampling/</guid>
      <description>This post explores MCP sampling and illustrates how important guardrails are to protect clients against malicious servers.</description>
      <content:encoded><![CDATA[<p>As <a href="https://ocytko.net/posts/mcp-servers-what-happens-behind-the-scenes/">indicated before</a>, I have been further exploring the constantly evolving MCP world. MCP sampling is a fun element of the <a href="https://modelcontextprotocol.io/specification/2025-11-25/client/sampling#sampling">Model Context Protocol</a>.
It essentially allows the server to access and use the client&rsquo;s LLM. This way the MCP server gets access to additional capabilities while offloading the cost to the user. While the protocol states:</p>
<blockquote>
<p><em>&ldquo;For trust &amp; safety and security, there <strong>SHOULD</strong> always be a human in the loop with the ability to deny sampling requests.&rdquo;</em></p>
</blockquote>
<p>surely there will be clients that do not implement any guardrails and there will be servers that will attempt to misuse the powers they have received access to.</p>
<p>Let&rsquo;s take a look at a simple MCP server and client. For serving the LLM we will use <a href="https://lmstudio.ai/">LM Studio</a> with <code>ministral-3-14b-instruct-2512</code> as model.</p>
<h3 id="server">Server</h3>
<p>The server uses <a href="https://github.com/prefecthq/fastmcp">FastMCPv2</a> and exposes a tool <code>generate_keywords</code>. The tool generates a list of keywords matching a given <code>topic</code>. The generation task is offloaded to the LLM of the client by invoking a helper function <code>send_sampling_message</code> to send the sampling requests.
When the tool is called, two sampling requests are sent in sequence:</p>
<ol>
<li>The first one takes the topic, constructs a prompt for the list generation, and then asks the client&rsquo;s LLM to rewrite it (in this case: restricting the generation to five words only).</li>
<li>The second one uses the client&rsquo;s LLM to actually execute the prompt (in this case: generating a list of keywords with restrictions from 1).</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="hl"><span class="lnt">10
</span></span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="hl"><span class="lnt">27
</span></span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="hl"><span class="lnt">33
</span></span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">logging</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastmcp</span> <span class="kn">import</span> <span class="n">FastMCP</span><span class="p">,</span> <span class="n">Context</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">mcp.types</span> <span class="kn">import</span> <span class="n">SamplingMessage</span><span class="p">,</span> <span class="n">TextContent</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">mcp</span> <span class="o">=</span> <span class="n">FastMCP</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;MCP sampling&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">send_sampling_message</span><span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="n">Context</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">ctx</span><span class="o">.</span><span class="n">sample</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="n">SamplingMessage</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">role</span><span class="o">=</span><span class="s2">&#34;user&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">content</span><span class="o">=</span><span class="n">TextContent</span><span class="p">(</span><span class="nb">type</span><span class="o">=</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="n">message</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">max_tokens</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@mcp.tool</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">generate_keywords</span><span class="p">(</span><span class="n">topic</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="n">Context</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Generate keywords using LLM sampling.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;List words that represent names of </span><span class="si">{</span><span class="n">topic</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># 1. Prompt rewrite</span>
</span></span><span class="line hl"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">send_sampling_message</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Rewrite &#39;</span><span class="si">{</span><span class="n">prompt</span><span class="si">}</span><span class="s2">&#39; to include a restriction for the list to only contain 5 words. Return the rewritten text only.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">rewritten_prompt</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">text</span>
</span></span><span class="line"><span class="cl">    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;rewritten prompt: </span><span class="si">{</span><span class="n">rewritten_prompt</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># 2. Keyword generation</span>
</span></span><span class="line hl"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">send_sampling_message</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">rewritten_prompt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;sampling message: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">text</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="n">text</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">mcp</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">transport</span><span class="o">=</span><span class="s2">&#34;streamable-http&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">5001</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">main</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="client">Client</h3>
<p>The client is simple. It uses the <code>lmstudio</code> library for model access for convenience. This part could be easily replaced with any other model call.
All the magic is in the <code>sampling_handler</code> function, which processes the sampling request and triggers the LLM inference.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="hl"><span class="lnt">15
</span></span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="hl"><span class="lnt">28
</span></span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">logging</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">lmstudio</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastmcp.client</span> <span class="kn">import</span> <span class="n">Client</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastmcp.client.sampling</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">SamplingMessage</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">SamplingParams</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RequestContext</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">llm</span> <span class="o">=</span> <span class="n">lmstudio</span><span class="o">.</span><span class="n">llm</span><span class="p">(</span><span class="s2">&#34;ministral-3-14b-instruct-2512&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">sampling_handler</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">SamplingMessage</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">params</span><span class="p">:</span> <span class="n">SamplingParams</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">context</span><span class="p">:</span> <span class="n">RequestContext</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Accept sampling messages from the server and execute inference.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;sampling handler operation with message: </span><span class="si">{</span><span class="n">messages</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\n\n</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">m</span><span class="o">.</span><span class="n">content</span><span class="o">.</span><span class="n">text</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">messages</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">llm</span><span class="o">.</span><span class="n">respond</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;LLM response: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">content</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;http://localhost:5001/mcp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">sampling_handler</span><span class="o">=</span><span class="n">sampling_handler</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">ping</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">sampling_result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">call_tool</span><span class="p">(</span><span class="s2">&#34;generate_keywords&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;topic&#34;</span><span class="p">:</span> <span class="s2">&#34;elements&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;sampling result: &#39;</span><span class="si">{</span><span class="n">sampling_result</span><span class="o">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">text</span><span class="si">}</span><span class="s2">&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>To run the example code, we first start the server with <code>uv run server.py</code> and then run the client: <code>uv run client.py</code>.
The expected output is as follows.</p>
<p><strong>server</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">INFO:root:rewritten prompt: <span class="s2">&#34;List **five** words that represent names of chemical elements.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>...<span class="o">]</span>
</span></span><span class="line"><span class="cl">INFO:root:sampling message: Here are five words that represent names of chemical elements:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">1. Oxygen
</span></span><span class="line"><span class="cl">2. Hydrogen
</span></span><span class="line"><span class="cl">3. Carbon
</span></span><span class="line"><span class="cl">4. Gold
</span></span><span class="line"><span class="cl">5. Sodium
</span></span></code></pre></div><p><strong>client</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">INFO:root:Sampling handler operation with message: <span class="o">[</span>SamplingMessage<span class="o">(</span><span class="nv">role</span><span class="o">=</span><span class="s1">&#39;user&#39;</span>, <span class="nv">content</span><span class="o">=</span>TextContent<span class="o">(</span><span class="nv">type</span><span class="o">=</span><span class="s1">&#39;text&#39;</span>, <span class="nv">text</span><span class="o">=</span><span class="s2">&#34;Rewrite &#39;List words that represent names of elements&#39; to include a restriction for the list to only contain 5 words. Return the rewritten text only.&#34;</span>, <span class="nv">annotations</span><span class="o">=</span>None, <span class="nv">meta</span><span class="o">=</span>None<span class="o">))]</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>...<span class="o">]</span>
</span></span><span class="line"><span class="cl">INFO:root:Sampling handler operation with message: <span class="o">[</span>SamplingMessage<span class="o">(</span><span class="nv">role</span><span class="o">=</span><span class="s1">&#39;user&#39;</span>, <span class="nv">content</span><span class="o">=</span>TextContent<span class="o">(</span><span class="nv">type</span><span class="o">=</span><span class="s1">&#39;text&#39;</span>, <span class="nv">text</span><span class="o">=</span><span class="s1">&#39;&#34;List **five** words that represent names of chemical elements.&#34;&#39;</span>, <span class="nv">annotations</span><span class="o">=</span>None, <span class="nv">meta</span><span class="o">=</span>None<span class="o">))]</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>...<span class="o">]</span>
</span></span><span class="line"><span class="cl">INFO:root:Sampling result: <span class="s1">&#39;Here are five words that represent names of chemical elements:
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">1. Oxygen
</span></span></span><span class="line"><span class="cl"><span class="s1">2. Hydrogen
</span></span></span><span class="line"><span class="cl"><span class="s1">3. Carbon
</span></span></span><span class="line"><span class="cl"><span class="s1">4. Gold
</span></span></span><span class="line"><span class="cl"><span class="s1">5. Sodium&#39;</span>
</span></span></code></pre></div><h3 id="communication-flow">Communication flow</h3>
<p>Below is the communication flow depicted as a sequence diagram.</p>
<figure class="align-center ">
    <img loading="lazy" src="mcp-sampling-seq.png#center"
         alt="Sequence diagram of the client server communication, incl. LLM."/> <figcaption>
            <p>Sequence diagram showing the client server communication including the LLM calls.</p>
        </figcaption>
</figure>

<h2 id="summary">Summary</h2>
<p>The example shows that the sampling handler is the key piece to pay attention to. For unattended execution of sampling requests, guardrails are a must.
The MCP server stays in full control how often and when to execute sampling. The client can inspect the prompt and decide whether to execute the sampling request or not. This shows how dangerous a malicious server can be when disguised as a helpful tool. Imagine, like in this example, that the server will trigger two sampling calls. The first call executes legitimate logic while the second just uses &ldquo;free&rdquo; model inference for requests collected in a queue.</p>
]]></content:encoded>
    </item>
    <item>
      <title>MCP servers: what happens behind the scenes?</title>
      <link>https://ocytko.net/posts/mcp-servers-what-happens-behind-the-scenes/</link>
      <pubDate>Sun, 06 Apr 2025 20:42:36 +0000</pubDate>
      <guid>https://ocytko.net/posts/mcp-servers-what-happens-behind-the-scenes/</guid>
      <description>The codebases of MCP servers tend to be early-stage API wrappers, far away in quality from existing SDKs. Non-determinism of LLMs adds…</description>
      <content:encoded><![CDATA[<p><em>Originally published on <a href="https://medium.com/@bocytko/mcp-servers-what-happens-behind-the-scenes-8d532f8b15fb">medium</a>.</em></p>
<p>If you are following any news related to LLMs, you surely have seen <a href="https://www.anthropic.com/news/model-context-protocol">Model Context Protocol</a> (MCP) getting a lot of attention lately. It also <a href="https://www.thoughtworks.com/radar/platforms/model-context-protocol-mcp">made it to ASSESS</a> in the recently released Tech Radar Vol. 32 by Thoughtworks which will further increase its visibility. In your feeds, MCP will be typically mentioned in conjunction with IDEs via Cursor, Windsurf, or VSCode with Github Copilot Chat by users who get access to new capabilities or extend the context of LLMs using MCPs. Often showcased by non-engineers who achieve results that were difficult for them to achieve without MCPs (such as generating scenes in Blender), their excitement is adding to the hype around MCP. Yet, when one explores the actual codebases of MCP servers, one finds early-stage, incomplete API wrappers that are far away from existing SDKs or libraries available to Software Engineers. Adding the non-determinism coming from LLMs, we’re in for a treat! 🍿</p>
<p>Having tried out the <a href="https://github.com/ahujasid/blender-mcp/">Blender integration</a> via <a href="https://docs.github.com/en/copilot/customizing-copilot/extending-copilot-chat-with-mcp">VSCode and Github Copilot</a> which was far too frequently running into rate limits, I was looking for a simple setup to try out MCP servers to understand what happens under the hood. I found <a href="https://github.com/openai/openai-agents-python">openai-agents-python</a> to provide a very simple setup for an experimentation harness against a chosen set of MCP servers. Its default integration with OpenAI’s Traces for observability provides a detailed look on what happens behind the scenes (it also saves me from running any <a href="https://ocytko.net/posts/opentelemetry-meets-openai-manual-instrumentation/">OpenTelemetry stack</a> I used previously).</p>
<p>In this post, I will be trying out three MCP servers calling them through <code>openai-agents-python</code> and using <code>gpt-4o</code> as model (default). The convenience of the framework is that given a configuration of MCP servers, it automatically handles their lifecycle (incl. download) without additional actions from the user.</p>
<h2 id="mcp-andllm">MCP and LLM</h2>
<p>The interplay between the LLM and MCP is quite simple. The LLM gets the user query and passes the list of available MCP functions (aka. tools) with their description and parameter specification to the LLM. The LLM then decides which function is most appropriate to call in the scenario defined by the user and returns as output the function call incl. parameters. The workflow orchestrator (e.g. IDE or agent framework) executes the function call using the MCP protocol and afterwards passes the input and output to the LLM for it to determine the next action.</p>
<h2 id="trying-out-mcp-server-git-git-repo-access-via-local-filesystem">Trying out mcp-server-git: Git repo access via local filesystem</h2>
<p>Starting point is the <a href="https://github.com/openai/openai-agents-python/blob/064e25b01b5c82c08aea66ff898ff27adbb013d8/examples/mcp/git_example/main.py">git_example</a> from <code>openai-agents-python</code> that we will use as basis for future experiments as well. The code sets up an agent with tools from <a href="https://github.com/modelcontextprotocol/servers/tree/main/src/git">mcp-server-git</a> for accessing any chosen repository from our local file system. The agent requests the most frequent contributor and asks for the summary of the latest change in the repo. After setting the <code>OPENAPI_API_KEY</code> environment variable, we can test it out.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nb">export</span> <span class="nv">OPENAPI_API_KEY</span><span class="o">=</span>sk-...
</span></span><span class="line"><span class="cl">$ uv run python main.py
</span></span><span class="line"><span class="cl">Please enter the path to the git repository: /tmp/openai-agents-python
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">----------------------------------------
</span></span><span class="line"><span class="cl">Running: Who<span class="err">&#39;</span>s the most frequent contributor?
</span></span><span class="line"><span class="cl">The most frequent contributor to the repository is **Rohan Mehta**.
</span></span><span class="line"><span class="cl">----------------------------------------
</span></span><span class="line"><span class="cl">Running: Summarize the last change in the repository.
</span></span><span class="line"><span class="cl">The last change in the repository was made by James Hills on 2025-04-04. The commit <span class="nb">hash</span> is <span class="sb">`</span>064e25b01b5c82c08aea66ff898ff27adbb013d8<span class="sb">`</span>, and the message was: <span class="s2">&#34;add links and mcp + voice examples (#438)&#34;</span>.
</span></span></code></pre></div><p>The first answer could be more comprehensive, but at least it worked out of the box. Let’s take a look what happened under the hood:</p>
<figure class="align-center ">
    <img loading="lazy" src="1_2jQUGAl3N5ajMYBRsHYAiw.png#center"
         alt="OpenAI Trace view for the main.py program. Shows MCP Tool list, LLM completion request to generate tool call, the git_log tool call, and LLM completion request to generate the final answer."/> <figcaption>
            <p>OpenAI Trace view for the main.py program. Shows MCP Tool list, LLM completion request to generate tool call, the git_log tool call, and LLM completion request to generate the final answer.</p>
        </figcaption>
</figure>

<p>We can see the agent fetching the list of MCP tools and calling the LLM that is then converting the user query to a tool call for <code>git_log</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">git_log</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;repo_path&#34;</span><span class="p">:</span> <span class="s2">&#34;/tmp/openai-agents-python&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;max_count&#34;</span><span class="p">:</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>As described before, the MCP server performs the tool call and passes the result again to the LLM. Since the git log contains user and commit data (commit_id, author, message) we end up with 29k tokens passed to the LLM after which we get the final response shown to the user.</p>
<figure class="align-center ">
    <img loading="lazy" src="1_yY523aRltMVTVEkpxlk2Bg.png#center"
         alt="Trace with git_log function call with input and output."/> <figcaption>
            <p>Trace with git_log function call with input and output.</p>
        </figcaption>
</figure>

<p><code>mcp-server-git</code> provides also operations to work on commits which are definitely interesting to try out another time.</p>
<h2 id="trying-out-github-mcp-server-remote-github-access-viaapi">Trying out github-mcp-server: remote Github access via API</h2>
<p>Now, let’s try the official <a href="https://github.com/github/github-mcp-server">github MCP server</a>, first released on April, 4th 2025. We will use the same queries as in the prior example, but specify the repo names directly in the prompts for the LLM to know what repository we want to query:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">send</span><span class="p">(</span><span class="n">agent</span><span class="p">:</span> <span class="n">Agent</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\\</span><span class="s2">n&#34;</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Running: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">Runner</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">starting_agent</span><span class="o">=</span><span class="n">agent</span><span class="p">,</span> <span class="nb">input</span><span class="o">=</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">final_output</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">mcp_server</span><span class="p">:</span> <span class="n">MCPServer</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">name</span><span class="o">=</span><span class="s2">&#34;Assistant&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">instructions</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Answer questions about Git repositories.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">mcp_servers</span><span class="o">=</span><span class="p">[</span><span class="n">mcp_server</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">cont</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span><span class="p">(</span><span class="n">cont</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">message</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="s2">&#34;&gt; Input: &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># exit upon Exit or CTRL+C</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">message</span> <span class="o">==</span> <span class="s2">&#34;Exit&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">cont</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">send</span><span class="p">(</span><span class="n">agent</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">MCPServerStdio</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">cache_tools_list</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="n">params</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;docker&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;run&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;-i&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;--rm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;-e&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;GITHUB_PERSONAL_ACCESS_TOKEN&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;ghcr.io/github/github-mcp-server&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;env&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;GITHUB_PERSONAL_ACCESS_TOKEN&#34;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;GITHUB_PERSONAL_ACCESS_TOKEN&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="k">as</span> <span class="n">server</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">trace</span><span class="p">(</span><span class="n">workflow_name</span><span class="o">=</span><span class="s2">&#34;MCP Git (Official)&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">run</span><span class="p">(</span><span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>At time of writing, the MCP server provides 29 functions (e.g. issue, commit, pull request operations and search) which wrap the Github APIs called using a Github token defined as an environment variable <code>GITHUB_PERSONAL_ACCESS_TOKEN</code>. For our experiment, in theory, just the function <code>list_commits()</code> for listing commits would be sufficient to answer the posed questions. Let’s see how successful the agent will be with this (simple?) task.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nb">export</span> <span class="nv">GITHUB_PERSONAL_ACCESS_TOKEN</span><span class="o">=</span>...
</span></span><span class="line"><span class="cl">$ uv run python github-mcp-server.py
</span></span><span class="line"><span class="cl">GitHub MCP Server running on stdio
</span></span><span class="line"><span class="cl">----------------------------------------
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&gt; Input: Who<span class="s1">&#39;s the most frequent contributor to zalando/skipper?
</span></span></span><span class="line"><span class="cl"><span class="s1">&gt; Running: Who&#39;</span>s the most frequent contributor to zalando/skipper?
</span></span><span class="line"><span class="cl">Error invoking MCP tool search_users: failed to search users: GET &lt;https://api.github.com/search/users?order<span class="o">=</span>desc<span class="p">&amp;</span><span class="nv">page</span><span class="o">=</span>1<span class="p">&amp;</span><span class="nv">per_page</span><span class="o">=</span>1<span class="p">&amp;</span><span class="nv">q</span><span class="o">=</span>repo%3Azalando%2Fskipper<span class="p">&amp;</span><span class="nv">sort</span><span class="o">=</span>repositories:&gt; <span class="m">422</span> Validation Failed <span class="o">[{</span>Resource:Search Field:q Code:invalid Message:None of the search qualifiers apply to this search type.<span class="o">}]</span>
</span></span></code></pre></div><p>Looks like we hit a bug: the query constructed by the LLM is invalid (reported as <a href="https://github.com/github/github-mcp-server/issues/135">github/github-mcp-server#135</a>). The LLM opted for <code>search_users()</code> instead of using the git commit log like our prior experiment.</p>
<p>Let’s take a look at the second query:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ uv run python github-mcp-server.py
</span></span><span class="line"><span class="cl">GitHub MCP Server running on stdio
</span></span><span class="line"><span class="cl">----------------------------------------
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&gt; Input: Summarize the last change in the repository zalando/skipper
</span></span><span class="line"><span class="cl">&gt; Running: Summarize the last change in the repository zalando/skipper
</span></span><span class="line"><span class="cl">Error getting response: Error code: <span class="m">429</span> - <span class="o">{</span><span class="s1">&#39;error&#39;</span>: <span class="o">{</span><span class="s1">&#39;message&#39;</span>: <span class="s1">&#39;Request too large for gpt-4o in organization org-XYZ on tokens per min (TPM): Limit 30000, Requested 68487. The input or output tokens must be reduced in order to run successfully. Visit &lt;https://platform.openai.com/account/rate-limits&gt; to learn more.&#39;</span>, <span class="s1">&#39;type&#39;</span>: <span class="s1">&#39;tokens&#39;</span>, <span class="s1">&#39;param&#39;</span>: None, <span class="s1">&#39;code&#39;</span>: <span class="s1">&#39;rate_limit_exceeded&#39;</span><span class="o">}}</span>. <span class="o">(</span>request_id: req_abcabcabc...<span class="o">)</span>
</span></span></code></pre></div><figure class="align-center ">
    <img loading="lazy" src="1_9Z8X4vTdRJg7WUEL_VblAQ.png#center"
         alt="Error message from OpenAI: rate limit of 30000 tokens exceeded, requested 68487 tokens."/> <figcaption>
            <p>Error message from OpenAI: rate limit of 30000 tokens exceeded, requested 68487 tokens.</p>
        </figcaption>
</figure>

<p>Another error. This time we hit a rate limit given that the request to the LLM is too large (68487 tokens vs. 30000 being the limit). It took the agent only 19.40s to realize this… Something must have inflated the response from the MCP server. A closer look at the traces reveals that the tool call to <code>list_commits</code> with <code>perPage = 1</code> resulted in a long response (containing 30 commits which is the default setting) with a whooping size of 180KB. Bug number two — filed as <a href="https://github.com/github/github-mcp-server/issues/136">github/github-mcp-server#136</a>.</p>
<p>A closer look at the MCP server response also shows the excessive payload originating from the Github API response. A single commit object has 5–6 KB and beyond information on the commit it includes goodies such as the PGP signature of the author, hardly needed for the task at hand:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;node_id&#34;</span><span class="p">:</span> <span class="s2">&#34;C_kwDOAlA7-NoAKDdlMmNhM2JmZDI2NzNiNTFkYjRhYmNmYmQ1OWRlYTMzYTk0YzIwMzE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;sha&#34;</span><span class="p">:</span> <span class="s2">&#34;7e2ca3bfd2673b51db4abcfbd59dea33a94c2031&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;commit&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;date&#34;</span><span class="p">:</span> <span class="s2">&#34;2025-04-02T09:43:35Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;... ...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;committer&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;date&#34;</span><span class="p">:</span> <span class="s2">&#34;2025-04-02T09:43:35Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;GitHub&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;noreply@github.com&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;Add context to log entry (#3466)\\n\\nThis change enriches log entry with request context to be used by custom log formatter.\\n\\nSigned-off-by: ... ... &lt;....&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tree&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;sha&#34;</span><span class="p">:</span> <span class="s2">&#34;cff0e8e12633313bff1291df86f65f3f81acfd66&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;https://api.github.com/repos/zalando/skipper/git/commits/7e2ca3bfd2673b51db4abcfbd59dea33a94c2031&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;verification&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;verified&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;reason&#34;</span><span class="p">:</span> <span class="s2">&#34;valid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;signature&#34;</span><span class="p">:</span> <span class="s2">&#34;-----BEGIN PGP SIGNATURE-----\\n\\n[...]\\n-----END PGP SIGNATURE-----\\n&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;payload&#34;</span><span class="p">:</span> <span class="s2">&#34;tree cff0e8e12633313bff1291df86f65f3f81acfd66\\nparent 985da0b03d2fa499e4868a22d85426ed387b4a98\\nauthor ... ... &lt;...&gt; 1743587015 +0200\\ncommitter GitHub &lt;noreply@github.com&gt; 1743587015 +0200\\n\\nAdd context to log entry (#3466)\\n\\nThis change enriches log entry with request context to be used by custom log formatter.\\n\\nSigned-off-by: ... ... &lt;...&gt;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;comment_count&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;login&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="err">[...]</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;subscriptions_url&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;https://api.github.com/users/.../subscriptions&gt;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="err">[...]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>BTW. If we adjust the query for our most frequent committer to <code>Who's the most frequent contributor to zalando/skipper? Use list_commits tool to answer this question.</code> we will run of course into the same error. However, fetching 30 commits is hardly close to the right answer.</p>
<h2 id="trying-out-aws-documentation-mcp-search-for-public-awsdocs">Trying out aws-documentation-mcp: search for public AWS docs</h2>
<p>Let’s take a look at another freshly released MCP servers — this time <a href="https://awslabs.github.io/mcp/">from AWS</a>. I choose the simplest one to run: <code>aws-documentation-mcp</code> provides access to public AWS docs and does not require any AWS account access.</p>
<p>A closer look at the <a href="https://github.com/awslabs/mcp/blob/main/src/aws-documentation-mcp-server/awslabs/aws_documentation_mcp_server/server.py">server code</a> shows the magic behind this server. It’s running a search against the <a href="https://github.com/awslabs/mcp/blob/b61e292c24343fe577d6bbdbf07eb059820642c2/src/aws-documentation-mcp-server/awslabs/aws_documentation_mcp_server/server.py#L34">AWS documentation search endpoint</a> (<a href="https://proxy.search.docs.aws.amazon.com/search">https://proxy.search.docs.aws.amazon.com/search</a>) with a dedicated <a href="https://github.com/awslabs/mcp/blob/b61e292c24343fe577d6bbdbf07eb059820642c2/src/aws-documentation-mcp-server/awslabs/aws_documentation_mcp_server/server.py#L33">user agent</a> <code>[...] ModelContextProtocol/1.0 (AWS Documentation Server)</code> allowing AWS to track the MCP server use (and likely rate limit usage). The system prompt also <a href="https://github.com/awslabs/mcp/blob/b61e292c24343fe577d6bbdbf07eb059820642c2/src/aws-documentation-mcp-server/awslabs/aws_documentation_mcp_server/server.py#L50">mentions a recommendation tool</a> to use for retrieving related content. The MCP can perform a search using the user’s input and next use search results to fetch the content, parse it using <code>beautifulsoup4</code> and convert into markdown using <code>markdownify</code>.</p>
<p>So far so good. Let’s try it out with two questions about S3:</p>
<ul>
<li>“What are the file size limits for AWS S3?”</li>
<li>“How does one enable S3 transfer acceleration?”</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">mcp_server</span><span class="p">:</span> <span class="n">MCPServer</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">agent</span> <span class="o">=</span> <span class="n">Agent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">name</span><span class="o">=</span><span class="s2">&#34;Assistant&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">instructions</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Answer questions about AWS services.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">mcp_servers</span><span class="o">=</span><span class="p">[</span><span class="n">mcp_server</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">send</span><span class="p">(</span><span class="n">agent</span><span class="p">,</span> <span class="s2">&#34;What are the file size limits for AWS S3?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">send</span><span class="p">(</span><span class="n">agent</span><span class="p">,</span> <span class="s2">&#34;How does one enable S3 transfer acceleration?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">MCPServerStdio</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">cache_tools_list</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>  <span class="c1"># Cache the tools list, for demonstration</span>
</span></span><span class="line"><span class="cl">        <span class="n">params</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;uvx&#34;</span><span class="p">,</span> <span class="s2">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;awslabs.aws-documentation-mcp-server@latest&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="k">as</span> <span class="n">server</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">trace</span><span class="p">(</span><span class="n">workflow_name</span><span class="o">=</span><span class="s2">&#34;MCP AWS Documentation Server&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">run</span><span class="p">(</span><span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The task took about 24 seconds, but it looks like a success:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">$ uv run python aws-docs.py
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">----------------------------------------
</span></span><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">Running: What are the file size limits for AWS S3?
</span></span></span><span class="line"><span class="cl">Here are the file size limits for uploading objects to Amazon S3:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Console Upload**: Up to 160 GB per file using the Amazon S3 console.
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Single PUT Operation**: Up to 5 GB per file using AWS SDKs, REST API, or AWS CLI.
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Multipart Upload**: Up to 5 TB per file using AWS SDKs, REST API, or AWS CLI. This option is for larger files, allowing uploads in parts ranging from 5 MB to 5 TB.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You can use multipart uploads for efficient handling of larger objects.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">----------------------------------------
</span></span><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">Running: How does one enable S3 transfer acceleration?
</span></span></span><span class="line"><span class="cl">To enable S3 Transfer Acceleration, you have several options including using the AWS Management Console, AWS CLI, or API. Here&#39;s a concise guide:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Using the AWS Management Console
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">1.</span> <span class="gs">**Sign In**</span>: Log into the AWS Management Console and open the S3 interface.
</span></span><span class="line"><span class="cl"><span class="k">2.</span> <span class="gs">**Select Bucket**</span>: In the left navigation pane, choose <span class="gs">**General purpose buckets**</span>. Then, select the bucket you want to enable transfer acceleration for.
</span></span><span class="line"><span class="cl"><span class="k">3.</span> <span class="gs">**Access Properties**</span>: Click on <span class="gs">**Properties**</span>.
</span></span><span class="line"><span class="cl"><span class="k">4.</span> <span class="gs">**Edit Transfer Acceleration**</span>: Under <span class="gs">**Transfer acceleration**</span>, click <span class="gs">**Edit**</span>.
</span></span><span class="line"><span class="cl"><span class="k">5.</span> <span class="gs">**Enable**</span>: Choose <span class="gs">**Enable**</span>, then save changes.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Using the AWS CLI
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">To enable Transfer Acceleration using AWS CLI, run the following command:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="s">```bash
</span></span></span><span class="line"><span class="cl">aws s3api put-bucket-accelerate-configuration --bucket <span class="o">[</span>bucket-name<span class="o">]</span> --accelerate-configuration <span class="nv">Status</span><span class="o">=</span>Enabled
</span></span><span class="line"><span class="cl"><span class="s">```</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Using the Accelerated Endpoint
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Once enabled, you can use the accelerated endpoint for faster data transfers:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Find the <span class="gs">**Accelerated endpoint**</span> under the bucket&#39;s <span class="gs">**Properties**</span> tab.
</span></span><span class="line"><span class="cl"><span class="k">-</span> Use <span class="sb">`s3-accelerate.amazonaws.com`</span> to direct requests through the accelerated endpoint.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">For more detailed instructions and examples of using the AWS CLI and SDKs, visit the [<span class="nt">Amazon S3 Transfer Acceleration documentation</span>](<span class="na">https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration-examples.html</span>).
</span></span></code></pre></div><p>The traces reveal what happened behind the scenes. For each of the inputs, we get <code>search_documentation</code> followed by <code>read_documentation</code>.</p>
<figure class="align-center ">
    <img loading="lazy" src="1_M2PEJvHL7oKU6-G3WRSjdQ.png#center"
         alt="Trace for MCP AWS Documentation Server: two tool calls and two LLM calls."/> <figcaption>
            <p>Trace for MCP AWS Documentation Server: two tool calls and two LLM calls.</p>
        </figcaption>
</figure>

<p>This time, the limits are correctly respected and by default, the search gets the top5 results.</p>
<figure class="align-center ">
    <img loading="lazy" src="1_pgZrI0gM_Zg2guAuMnti2Q.png#center"
         alt="Trace for a search_documentation tool call with limit of 5 and ranked results with rank_order, url, and title."/> <figcaption>
            <p>Trace for a search_documentation tool call with limit of 5 and ranked results with rank_order, url, and title.</p>
        </figcaption>
</figure>

<p>Also, we get valid (though simplified) answers straight from the docs. The queries took 14.88s and 10.61s respectively, which is on the slow end, yet if the MCP is embedded into the IDE, likely convenient to access without resorting to using the browser.</p>
<h2 id="closing-words-to-mcp-to-notmcp">Closing words: to MCP to not MCP?</h2>
<p>What did we learn from this exploration? MCP servers are easy to set up and are a promising tool to equip agentic flows and assistants with access to additional information and capabilities. They also provide us with a way to bring local data as context for LLMs, especially in cases where indexing this data would be impractical. We also learned that the generated LLM calls are rather costly to execute due to excessive context size (if they get executed at all). Before using MCP servers, it’s important to verify the cost footprint for expected tasks as well as add monitoring and spend limits accordingly.</p>
<p>Building a good MCP server is far from easy: getting LLMs to generate the right and syntactically correct function calls is difficult and becomes more complex the more functions are available to choose from. We saw in the AWS example how detailed instructions help guide the model to generate a multi-step call flow required for the job. Without these instructions, we just can’t magically expect good results. It is easier to ask models to generate code that includes API calls instead.</p>
<p>In the git examples, to calculate the most frequent committer, one would expect an iteration over the commit list over at last a few pages. Yet currently, due to excessive size of the response, we cannot even parse 30 commits due to limits in the model’s context size (or model rate limits). This makes the approach rather impractical. A simple prompt: <code>Generate python code to interact with the Github API to determine the most frequent committer for a chosen repository.</code> results in a code snippet that does the job in a more predictable way. Similarly, <code>Generate git command to calculate the most frequent committer for a chosen repository.</code> returns <code>git shortlog -sne</code> that paired with <code>| head -n 1</code> returns the result for our local git repo. Needless to say how cheap these LLM calls are when compared to the MCP approach. Let’s see how the mentioned git MCP servers will evolve and for how long it will stay around. They’re early on their journey and require more work to be really useful and give users confidence when and how they will actually work. Field filtering and evals come to mind as first extensions that will be a major step change in quality.</p>
<p>I will certainly continue my experiments as the level of hype is too high for MCPs to disappear quickly.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
