<?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>PostgreSQL on Last DBA</title><link>https://lastdba.com/en/tags/postgresql/</link><description>Recent content in PostgreSQL on Last DBA</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>© 2026 liuzhilong62</copyright><lastBuildDate>Fri, 29 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://lastdba.com/en/tags/postgresql/index.xml" rel="self" type="application/rss+xml"/><item><title>UUID v4 and v7: Collision Incidents and Performance Benchmarks</title><link>https://lastdba.com/en/2026/05/29/uuid-v4-and-v7-collision-incidents-and-performance-benchmarks/</link><pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate><guid>https://lastdba.com/en/2026/05/29/uuid-v4-and-v7-collision-incidents-and-performance-benchmarks/</guid><description>&lt;blockquote&gt;&lt;p&gt;Source material: &lt;a href="https://news.ycombinator.com/item?id=48060054" target="_blank" rel="noreferrer"&gt;HN UUID v4 Collision Thread&lt;/a&gt;, &lt;a href="https://dev.to/umangsinha12/postgresql-uuid-performance-benchmarking-random-v4-and-time-based-v7-uuids-n9b" target="_blank" rel="noreferrer"&gt;dev.to UUID Benchmark&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;AI-generated ratio: 99%&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 class="relative group"&gt;TL;DR
 &lt;div id="tldr" 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="#tldr" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;UUID v4 collided — someone on HackerNews actually hit a real collision. The root cause was a software stack bug, not math. v4 and v7 have no fundamental difference in collision safety. The real difference is index performance: v7 is time-ordered, B-tree is more compact, writes are 35% faster, indexes are 22% smaller. Your UUID v4 is probably fine, but if you care about index performance, switching to v7 is a cheap win.&lt;/p&gt;</description><content:encoded>&lt;blockquote&gt;&lt;p&gt;Source material: &lt;a href="https://news.ycombinator.com/item?id=48060054" target="_blank" rel="noreferrer"&gt;HN UUID v4 Collision Thread&lt;/a&gt;, &lt;a href="https://dev.to/umangsinha12/postgresql-uuid-performance-benchmarking-random-v4-and-time-based-v7-uuids-n9b" target="_blank" rel="noreferrer"&gt;dev.to UUID Benchmark&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;AI-generated ratio: 99%&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 class="relative group"&gt;TL;DR
 &lt;div id="tldr" 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="#tldr" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;UUID v4 collided — someone on HackerNews actually hit a real collision. The root cause was a software stack bug, not math. v4 and v7 have no fundamental difference in collision safety. The real difference is index performance: v7 is time-ordered, B-tree is more compact, writes are 35% faster, indexes are 22% smaller. Your UUID v4 is probably fine, but if you care about index performance, switching to v7 is a cheap win.&lt;/p&gt;

&lt;h3 class="relative group"&gt;The UUID v4 Collision Incident
 &lt;div id="the-uuid-v4-collision-incident" 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="#the-uuid-v4-collision-incident" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;A HackerNews thread blew up — &lt;a href="https://news.ycombinator.com/item?id=48060054" target="_blank" rel="noreferrer"&gt;Ask HN: We just had an actual UUID v4 collision&amp;hellip;&lt;/a&gt;, 479 upvotes, 347 comments.&lt;/p&gt;
&lt;p&gt;The OP&amp;rsquo;s own words:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I know what you&amp;rsquo;re thinking&amp;hellip; and I still can&amp;rsquo;t believe it, but&amp;hellip; This morning, our database flagged a duplicate UUID (v4).&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;It wasn&amp;rsquo;t a double-insert bug. The code didn&amp;rsquo;t write it twice. Only ~15,000 rows in the table, using npm&amp;rsquo;s &lt;code&gt;uuid&lt;/code&gt; package &lt;code&gt;uuidv4()&lt;/code&gt;, and two rows created at different times collided on the same UUID:&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;b6133fd6-70fe-4fe3-bed6-8ca8fc9386cd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;What&amp;rsquo;s the probability of a UUID v4 collision? 122 random bits, 2^122 ≈ 5.3×10^36 possibilities. With 15,000 records, collision probability is roughly 2×10^-29. Theoretically &amp;ldquo;impossible.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;But it happened.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Cause 1: Unreliable entropy sources
 &lt;div id="cause-1-unreliable-entropy-sources" 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="#cause-1-unreliable-entropy-sources" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;p&gt;HN&amp;rsquo;s top-voted comment (jandrewrogers):&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;UUIDv4 security depends on high-quality entropy sources. Hardware defects, software bugs, and misunderstandings of &amp;ldquo;high-quality entropy&amp;rdquo; all break this assumption. Detecting entropy source failures is expensive, so nobody checks — until a collision happens.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;UUID v4 is &lt;strong&gt;explicitly banned&lt;/strong&gt; in high-reliability systems because entropy source quality cannot be verified.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Cause 2: Known npm uuid package bugs
 &lt;div id="cause-2-known-npm-uuid-package-bugs" 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="#cause-2-known-npm-uuid-package-bugs" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;p&gt;The npm uuid package README itself warns:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;This module may generate duplicate UUIDs when run in clients with deterministic random number generators, such as Googlebot crawlers.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;More seriously, its internal &lt;code&gt;rng()&lt;/code&gt; function has global mutable state. One commenter pointed out: calling &lt;code&gt;rng()&lt;/code&gt; and sending the result effectively &lt;strong&gt;overwrites someone else&amp;rsquo;s random number, and you can predict it&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Related commit: &lt;a href="https://github.com/uuidjs/uuid/commit/91805f665c38b691ac2cbd" target="_blank" rel="noreferrer"&gt;91805f665c&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Community advice: use Node.js built-in &lt;code&gt;crypto.randomUUID()&lt;/code&gt;, not the npm uuid package.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Cause 3: Linux kernel /dev/random race condition
 &lt;div id="cause-3-linux-kernel-devrandom-race-condition" 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="#cause-3-linux-kernel-devrandom-race-condition" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;p&gt;Another comment:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I encountered duplicate UUIDs during soak testing of a distributed system. After extensive debugging, I found it was a Linux kernel race condition bug — on multi-processor systems, two processes simultaneously reading /dev/random could, with extremely low probability (~one in a million), get the same bytes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 class="relative group"&gt;Cause 4: Go UUID library not checking return values
 &lt;div id="cause-4-go-uuid-library-not-checking-return-values" 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="#cause-4-go-uuid-library-not-checking-return-values" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;blockquote&gt;&lt;p&gt;Early Go UUID libraries called random functions without checking the return value length. &amp;ldquo;Request N bytes, got 3 bytes back&amp;rdquo; never happened on most hardware, so nobody checked — until production, where it generated thousands of duplicate UUIDs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 class="relative group"&gt;Cause 5: Historical AMD CPU RNG defects
 &lt;div id="cause-5-historical-amd-cpu-rng-defects" 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="#cause-5-historical-amd-cpu-rng-defects" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;p&gt;Certain AMD CPUs had built-in random number generator issues. VM environments can also &amp;ldquo;virtualize away&amp;rdquo; entropy — both time sources and entropy sources can degrade inside VMs.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;v4 and v7 have no fundamental difference in collision safety. The difference is in the first 48 bits — v4 is random, v7 is a timestamp. You&amp;rsquo;re unlikely to encounter timestamp source issues, and random source issues are equally rare. The HN thread is an interesting edge case. Knowing that a tiny number of people hit it is enough — you don&amp;rsquo;t need to distrust the UUID v4 in your own systems.&lt;/p&gt;
&lt;p&gt;When choosing v4 vs v7, what you should really look at isn&amp;rsquo;t collisions — it&amp;rsquo;s &lt;strong&gt;index performance&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 class="relative group"&gt;UUID v7 Performance Comparison in PG 16
 &lt;div id="uuid-v7-performance-comparison-in-pg-16" 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="#uuid-v7-performance-comparison-in-pg-16" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;UUID v7 has one concrete advantage over v4 in PostgreSQL: &lt;strong&gt;temporal clustering, more B-tree-friendly&lt;/strong&gt;. v4 can bloat and v7 can bloat too — the difference is simply that v7&amp;rsquo;s first 48 bits are time-ordered, so inserts concentrate on the right side of the B-tree, reducing page splits.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/umangsinha12/postgresql-uuid-performance-benchmarking-random-v4-and-time-based-v7-uuids-n9b" target="_blank" rel="noreferrer"&gt;Umang Sinha&amp;rsquo;s benchmark&lt;/a&gt; ran a rigorous comparison on a PG 16 Docker container (8 cores, 16GB, NVMe).&lt;/p&gt;

&lt;h4 class="relative group"&gt;Test Conditions
 &lt;div id="test-conditions" 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="#test-conditions" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&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; uuid_v4_test (id UUID &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;, payload TEXT);
&lt;/span&gt;&lt;/span&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; uuid_v7_test (id UUID &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;, payload TEXT);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Parameter&lt;/th&gt;
 &lt;th&gt;Value&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Data volume&lt;/td&gt;
 &lt;td&gt;10 million rows per table&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Batch size&lt;/td&gt;
 &lt;td&gt;10,000 rows per batch&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Client&lt;/td&gt;
 &lt;td&gt;Go + pq driver&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UUID generation&lt;/td&gt;
 &lt;td&gt;Pre-generated in memory, not timed&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 class="relative group"&gt;Performance Results
 &lt;div id="performance-results" 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="#performance-results" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Metric&lt;/th&gt;
 &lt;th&gt;UUID v4&lt;/th&gt;
 &lt;th&gt;UUID v7&lt;/th&gt;
 &lt;th&gt;Improvement&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Write 10M rows&lt;/td&gt;
 &lt;td&gt;5 min 35 sec&lt;/td&gt;
 &lt;td&gt;3 min 38 sec&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;35% faster&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Table + index total size&lt;/td&gt;
 &lt;td&gt;3618 MB&lt;/td&gt;
 &lt;td&gt;3443 MB&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;5% smaller&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;B-tree index size&lt;/td&gt;
 &lt;td&gt;776 MB&lt;/td&gt;
 &lt;td&gt;602 MB&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;22% smaller&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Point lookup&lt;/td&gt;
 &lt;td&gt;0.167 ms&lt;/td&gt;
 &lt;td&gt;0.038 ms&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;4.4x faster&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Range scan&lt;/td&gt;
 &lt;td&gt;8.283 ms&lt;/td&gt;
 &lt;td&gt;3.791 ms&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;2.2x faster&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 class="relative group"&gt;Why Such a Big Difference
 &lt;div id="why-such-a-big-difference" 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="#why-such-a-big-difference" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;p&gt;


&lt;img src="https://lastdba.com/img/uuid-v4-structure.png" alt="UUID v4 bit structure" /&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img src="https://lastdba.com/img/uuid-v7-structure.png" alt="UUID v7 bit structure" /&gt;&lt;/p&gt;
&lt;p&gt;UUID v4 is fully random. Newly inserted UUIDs scatter randomly across the B-tree index, causing massive page splits and severe index fragmentation. UUID v7 has a millisecond-precision timestamp in the first 48 bits, so newly generated UUIDs are naturally ordered — writes cluster on the right side of the B-tree, page splits drop dramatically, and the index is much more compact.&lt;/p&gt;
&lt;p&gt;The 22% smaller index isn&amp;rsquo;t magic — it&amp;rsquo;s &lt;strong&gt;reduced fragmentation&lt;/strong&gt;. Point lookups being 4x faster isn&amp;rsquo;t surprising either — fewer B-tree levels, higher cache hit rates.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Summary
 &lt;div id="summary" 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="#summary" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;UUID v4 and v7 are identical in collision safety — both depend on entropy source quality, one fills the first 48 bits with random numbers, the other with a timestamp. Collisions are edge cases that a tiny number of people hit in specific environments. Your environment is probably fine — that basic judgment doesn&amp;rsquo;t change.&lt;/p&gt;
&lt;p&gt;What you really should think about is &lt;strong&gt;index performance&lt;/strong&gt;. v7&amp;rsquo;s temporal property makes B-trees more compact, with measured results of 35% faster writes, 22% smaller indexes, and 2-4x faster queries. If your system writes UUIDs at high volume, switching to v7 saves meaningful storage and CPU.&lt;/p&gt;
&lt;p&gt;PG 18 will natively support &lt;code&gt;gen_uuid_v7()&lt;/code&gt;. For now, generate UUIDs at the application layer. Whichever version you use, always add a UNIQUE constraint.&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><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>