<?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>Agent on Last DBA</title><link>https://lastdba.com/en/tags/agent/</link><description>Recent content in Agent on Last DBA</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>© 2026 liuzhilong62</copyright><lastBuildDate>Wed, 27 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://lastdba.com/en/tags/agent/index.xml" rel="self" type="application/rss+xml"/><item><title>When PostgreSQL Becomes AI's Hands — Bruce Momjian's MCP Server in Practice</title><link>https://lastdba.com/en/2026/05/27/when-postgresql-becomes-ais-hands-bruce-momjians-mcp-server-in-practice/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://lastdba.com/en/2026/05/27/when-postgresql-becomes-ais-hands-bruce-momjians-mcp-server-in-practice/</guid><description>&lt;blockquote&gt;&lt;p&gt;Original: &lt;a href="https://momjian.us/main/writings/pgsql/mcp.pdf" target="_blank" rel="noreferrer"&gt;Building an MCP Server Using Postgres&lt;/a&gt;, Bruce Momjian, PGDay Armenia 2026, CC BY 4.0.&lt;/p&gt;
&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;AI-generated ratio: 80%&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Bruce Momjian (PG core team, the one who has written release notes for 20+ years) recently gave a talk at PGDay Armenia 2026: &lt;a href="https://momjian.us/main/writings/pgsql/mcp.pdf" target="_blank" rel="noreferrer"&gt;Building an MCP Server Using Postgres&lt;/a&gt;. 70 slides, extremely dense. Theory and practice — a solid reference.&lt;/p&gt;
&lt;p&gt;Reading it directly is hard work. Even having AI interpret it probably won&amp;rsquo;t make sense at first glance. I had to read for a while and ask several questions before it clicked.&lt;/p&gt;</description><content:encoded>&lt;blockquote&gt;&lt;p&gt;Original: &lt;a href="https://momjian.us/main/writings/pgsql/mcp.pdf" target="_blank" rel="noreferrer"&gt;Building an MCP Server Using Postgres&lt;/a&gt;, Bruce Momjian, PGDay Armenia 2026, CC BY 4.0.&lt;/p&gt;
&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;AI-generated ratio: 80%&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Bruce Momjian (PG core team, the one who has written release notes for 20+ years) recently gave a talk at PGDay Armenia 2026: &lt;a href="https://momjian.us/main/writings/pgsql/mcp.pdf" target="_blank" rel="noreferrer"&gt;Building an MCP Server Using Postgres&lt;/a&gt;. 70 slides, extremely dense. Theory and practice — a solid reference.&lt;/p&gt;
&lt;p&gt;Reading it directly is hard work. Even having AI interpret it probably won&amp;rsquo;t make sense at first glance. I had to read for a while and ask several questions before it clicked.&lt;/p&gt;
&lt;p&gt;These 70 slides can be cleanly split into two layers — the first half is theory, the second half is a hands-on demo. The two layers don&amp;rsquo;t have much to do with each other.&lt;/p&gt;
&lt;hr&gt;

&lt;h1 class="relative group"&gt;Theory Layer: Explaining the RAG → MCP Evolution Through Transformers (Slides 1-33)
 &lt;div id="theory-layer-explaining-the-rag--mcp-evolution-through-transformers-slides-1-33" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#theory-layer-explaining-the-rag--mcp-evolution-through-transformers-slides-1-33" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h1&gt;
&lt;p&gt;The theory layer takes up nearly half the content, from LLM fundamentals to how MCP works. The outline is clear:&lt;/p&gt;
&lt;p&gt;


&lt;img src="https://lastdba.com/img/mcp/outline.png" alt="Talk outline: Generative AI → LLM limitations → RAG → MCP → MCP Server in practice" /&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;RAG vs MCP: In One Sentence
 &lt;div id="rag-vs-mcp-in-one-sentence" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#rag-vs-mcp-in-one-sentence" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Everyone knows the RAG workflow: the programmer decides what data to query → retrieval results are appended to the system prompt → the LLM reads and generates a response. &lt;strong&gt;Pre-orchestrated&lt;/strong&gt; — what the LLM can see is decided before the user even asks.&lt;/p&gt;
&lt;p&gt;MCP is different. Tool descriptions are registered with the LLM, and the LLM &lt;strong&gt;decides for itself&lt;/strong&gt; during generation whether to call a tool and which one. &lt;strong&gt;Dynamic decision-making&lt;/strong&gt; — the programmer only exposes tools, the LLM handles orchestration.&lt;/p&gt;
&lt;p&gt;Bruce sums it up in one sentence:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;RAG can only do what the programmer pre-planned. MCP can dynamically adjust based on output quality, can iteratively call multiple tools, and can trigger external tasks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;&amp;ldquo;Word or MCP&amp;rdquo; — That Set of Vector Embedding Diagrams
 &lt;div id="word-or-mcp--that-set-of-vector-embedding-diagrams" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#word-or-mcp--that-set-of-vector-embedding-diagrams" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Slides 18-33 are the core of the theory layer. Bruce draws a detailed internal Transformer flow diagram:&lt;/p&gt;
&lt;p&gt;


&lt;img src="https://lastdba.com/img/mcp/mcp-servers.png" alt="MCP Server registered as Tool Embedding Vectors in the vector space" /&gt;&lt;/p&gt;
&lt;p&gt;His logic: take each MCP tool&amp;rsquo;s description text (e.g., &amp;ldquo;Return the radiation level (CPM) at 13 Roberts Road&amp;hellip;&amp;rdquo;), embed it into a vector using a text embedding model, and inject it into the attention layer&amp;rsquo;s vector space. Then at each inference step, the output vector matches against the nearest vector —&lt;/p&gt;
&lt;p&gt;


&lt;img src="https://lastdba.com/img/mcp/word-or-mcp.png" alt="The closest vector might be a text token, or an MCP tool" /&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;The closest vector might be a word or an MCP.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;Is This Model Correct?
 &lt;div id="is-this-model-correct" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#is-this-model-correct" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This is what puzzled me the most. Here are my thoughts.&lt;/p&gt;
&lt;p&gt;Bruce&amp;rsquo;s 15 slides are beautifully drawn, but if you try to understand them as engineering implementation, there are problems:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① MCP tools don&amp;rsquo;t need &amp;ldquo;embedding.&amp;rdquo;&lt;/strong&gt; In actual engineering, tool definitions are written directly into the system prompt as text. The LLM reads &amp;ldquo;You have these tools: geiger(), get_pretzel_inventory()…&amp;rdquo; and uses semantic understanding to decide when to call them. There&amp;rsquo;s no need to compute tool descriptions as vectors, no need to do cosine distance comparisons against word vectors. The essence of Bruce&amp;rsquo;s teaching model is explaining &amp;ldquo;LLM decision-making&amp;rdquo; as &amp;ldquo;nearest vector matching&amp;rdquo; — this is closer to the retrieval paradigm than the generation paradigm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② Attention doesn&amp;rsquo;t produce a &amp;ldquo;find nearest&amp;rdquo; operation.&lt;/strong&gt; &lt;code&gt;output = Σ(softmax(Q·K) × V)&lt;/code&gt; yields a weighted-mixed context vector. There&amp;rsquo;s no step of &amp;ldquo;binary choice between the word embedding table and the tool embedding table.&amp;rdquo; The actual mechanism for LLM tool selection is: attention produces hidden states → LM head → softmax over vocabulary → output tool call JSON. There&amp;rsquo;s never a &amp;ldquo;word vs tool&amp;rdquo; choice, only a softmax over the entire vocabulary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ System prompt and user prompt have no boundary in attention.&lt;/strong&gt; A token sequence is just a token sequence — attention blocks do Q·K dot products on all tokens equally. There is no &amp;ldquo;system zone&amp;rdquo; or &amp;ldquo;user zone.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So these 33 theory slides can be seen as a simplified teaching model Bruce built for DBAs without an AI background — visually appealing and easy to understand, but don&amp;rsquo;t use it as an architecture diagram. MCP&amp;rsquo;s truly revolutionary aspect is &lt;strong&gt;protocol standardization&lt;/strong&gt; (unified tool registration/discovery/calling spec), not any vectorization trick.&lt;/p&gt;
&lt;hr&gt;

&lt;h1 class="relative group"&gt;Practice Layer: Two Working Demos (Slides 34-69)
 &lt;div id="practice-layer-two-working-demos-slides-34-69" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#practice-layer-two-working-demos-slides-34-69" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h1&gt;
&lt;p&gt;Starting from Slide 34, the style abruptly shifts — all code, terminal output, hardware photos. That entire Transformer vector model from the theory layer completely disappears, replaced by &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;psql&lt;/code&gt;, and Perl scripts.&lt;/p&gt;
&lt;p&gt;The only thread connecting the two layers is that &amp;ldquo;they&amp;rsquo;re both talking about MCP.&amp;rdquo; But the vector matching mechanism painted in the theory layer and the actual implementation in the practice layer are nearly two different logic systems. This may be exactly the tension Bruce intended — the theory layer helps you understand why MCP is stronger than RAG, and the practice layer tells you how to actually implement it today.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Demo 1: Letting ChatGPT Read a Real-World Geiger Counter
 &lt;div id="demo-1-letting-chatgpt-read-a-real-world-geiger-counter" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#demo-1-letting-chatgpt-read-a-real-world-geiger-counter" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Bruce set up a GQ GMC-800 Geiger counter (radiation detector) in his backyard, connected via USB to a Raspberry Pi, taking environmental radiation readings every 15 minutes. First, see ChatGPT using MCP to call real data:&lt;/p&gt;
&lt;p&gt;


&lt;img src="https://lastdba.com/img/mcp/chatgpt-weather.png" alt="ChatGPT querying weather via MCP" /&gt;&lt;/p&gt;
&lt;p&gt;MCP can call external tools to get real-time data — something RAG cannot do.&lt;/p&gt;
&lt;p&gt;Connected to hardware:&lt;/p&gt;
&lt;p&gt;


&lt;img src="https://lastdba.com/img/mcp/geiger-counter.png" alt="GQ GMC-800 Geiger counter" /&gt;&lt;/p&gt;
&lt;p&gt;Wrote a Python wrapper using &lt;strong&gt;fastmcp&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; fastmcp &lt;span style="color:#f92672"&gt;import&lt;/span&gt; FastMCP
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mcp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; FastMCP(&lt;span style="color:#e6db74"&gt;&amp;#34;Geiger counter MCP server&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@mcp.tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;geiger&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; int:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Return the radiation level (CPM) at 13 Roberts Road, Newtown Square, PA, USA&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;check_output(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/var/lib/postgresql/tmp/geiger&amp;#34;&lt;/span&gt;, shell&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The underlying layer is a Perl script that sends &lt;code&gt;&amp;lt;GETCPM&amp;gt;&amp;gt;&lt;/code&gt; over serial, reads back a 4-byte CPM value. Apache reverse-proxies port 443 (OpenAI only talks to 443). After registering with ChatGPT:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: What&amp;#39;s the radiation level at 13 Roberts Road?
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GPT: I don&amp;#39;t have public data for that location...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: Use my custom app
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GPT: [calls geiger tool] → 14 CPM. Normal background radiation (5-25 CPM).
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: Take five readings and give me the average
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GPT: [calls ×5] 15 16 13 15 15 → average 14.8 CPM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Two key behaviors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The LLM can iteratively call tools and compute&lt;/strong&gt; — RAG is a one-shot data dump, MCP is &amp;ldquo;call → get result → decide → call again → compute&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The user must explicitly authorize&lt;/strong&gt; — the first time, ChatGPT didn&amp;rsquo;t say &amp;ldquo;I have your Geiger counter data.&amp;rdquo; Only when the user said &amp;ldquo;use my custom app&amp;rdquo; did the tool call trigger. The security model is conservative&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;Demo 2: Using PG as a Pretzel Shop Inventory System
 &lt;div id="demo-2-using-pg-as-a-pretzel-shop-inventory-system" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#demo-2-using-pg-as-a-pretzel-shop-inventory-system" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;From hardware back to software. Building a pretzel inventory database:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; pretzel (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; quantity INTEGER &lt;span style="color:#66d9ef"&gt;CHECK&lt;/span&gt; (quantity &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INTO&lt;/span&gt; pretzel &lt;span style="color:#66d9ef"&gt;VALUES&lt;/span&gt; (&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;); &lt;span style="color:#75715e"&gt;-- initial inventory 0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;MCP tools use &lt;code&gt;psql&lt;/code&gt; to operate on PG directly:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@mcp.tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_pretzel_inventory&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; int:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Return the number of unsold pretzels&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;check_output(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;psql --tuples-only -c &amp;#39;SELECT quantity FROM pretzel;&amp;#39; -d mcp&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; shell&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@mcp.tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sold_one_pretzel&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Call this when a pretzel is sold; reduces inventory by one&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;check_output(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;psql --tuples-only -c &amp;#39;UPDATE pretzel SET quantity = quantity - 1;&amp;#39; -d mcp&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; shell&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@mcp.tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;baked_6_pretzels&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Call this when a tray of 6 pretzels is baked; increases inventory&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;check_output(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;psql --tuples-only -c &amp;#39;UPDATE pretzel SET quantity = quantity + 6;&amp;#39; -d mcp&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; shell&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Interaction flow:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: How many pretzels available?
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GPT: 0 pretzels.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: I just baked a tray → 6 pretzels
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: I sold two → 4 remaining
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: I sold four → 0 remaining
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User: I sold one pretzel → ERROR! CHECK constraint prevented negative quantity&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The LLM doesn&amp;rsquo;t write SQL directly — it calls your predefined, controlled interfaces. PG&amp;rsquo;s CHECK constraints naturally form a safety net — even if the LLM is tricked into calling the wrong function, the database-level constraint provides a second line of defense.&lt;/p&gt;
&lt;p&gt;But this also exposes a problem: the LLM faithfully executed &lt;code&gt;sold_one_pretzel&lt;/code&gt;, but didn&amp;rsquo;t anticipate that &amp;ldquo;inventory is 0, calling it will error.&amp;rdquo; &lt;strong&gt;MCP is the execution layer, not the reasoning layer.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;

&lt;h1 class="relative group"&gt;How Far from Production
 &lt;div id="how-far-from-production" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-far-from-production" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h1&gt;
&lt;p&gt;On the final slide, Bruce frankly admits the current implementation&amp;rsquo;s limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No authentication&lt;/strong&gt; — anyone can call your MCP Server&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No parameterization&lt;/strong&gt; — all three tools are parameterless functions; real-world tools need to accept parameters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No security restrictions on dynamic SQL&lt;/strong&gt; — tool descriptions declare semantics, but the LLM could be injected with malicious content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection pooling, transaction management, rate limiting&lt;/strong&gt; — none addressed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two recommended practical reads:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pgedge.com/blog/lessons-learned-writing-an-mcp-server-for-postgresql" target="_blank" rel="noreferrer"&gt;pgedge.com: Lessons Learned Writing an MCP Server for PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cardinalops.com/blog/mcp-defaults-hidden-dangers-of-remote-deployment/" target="_blank" rel="noreferrer"&gt;CardinalOps: MCP Defaults — Hidden Dangers of Remote Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;

&lt;h1 class="relative group"&gt;Between the Two Layers
 &lt;div id="between-the-two-layers" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#between-the-two-layers" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h1&gt;
&lt;p&gt;Looking back at these 70 slides, the most interesting part isn&amp;rsquo;t any single demo — it&amp;rsquo;s how the theoretical thinking and hands-on work together explain what MCP can do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The theory layer uses Transformer vector spaces to explain &amp;ldquo;how the LLM chooses between words and tools&amp;rdquo; — this is a teaching model&lt;/li&gt;
&lt;li&gt;The practice layer uses &lt;code&gt;psql&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, and Perl scripts to actually implement things — this is engineering&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The real MCP mechanism — tool definitions inserted as text into the system prompt, the LLM using semantic understanding to decide which tool to call, outputting tool call JSON — needs none of the vector embedding model from the theory layer. Between the two layers, Bruce didn&amp;rsquo;t draw the connecting line. This might not be a bug — it might be a feature.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This article was originally published in Chinese on &lt;a href="https://lastdba.com" target="_blank" rel="noreferrer"&gt;lastdba.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded></item></channel></rss>