Skip to content

Commit d0d1584

Browse files
committed
ui fixes+updated blog
1 parent 9944fe5 commit d0d1584

File tree

7 files changed

+166
-146
lines changed

7 files changed

+166
-146
lines changed

blog/2025-01-16-mcp-servers-nightmare.md

Lines changed: 0 additions & 93 deletions
This file was deleted.
File renamed without changes.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
slug: mcp-servers-nightmare
3+
title: Why MCP Servers Are a Nightmare for Engineers
4+
tags: [mcp, engineering, critique, comparison, utcp]
5+
---
6+
7+
# Why MCP Servers Are a Nightmare for Engineers
8+
9+
*(and what we can learn from the pain)*
10+
11+
> *"I just wanted my LLM to call `cat` on a file. For that, I had to build a stateful server and do so many transactions before I actually get the information. It's wild." – Razvan, early MCP adopter (interview excerpt)*
12+
13+
<!--truncate-->
14+
15+
---
16+
17+
## TL;DR: MCP is just 100x harder than you think
18+
19+
Model Context Protocol (MCP) was sold as the USB-C for AI tools: one simple, standard plug for everything. In practice, it feels more like trying to get a 1990s serial port working on a modern laptop. You end up lost in a jungle of ambiguous specs, wrapper processes, and brittle infrastructure that consumes engineering hours for breakfast.
20+
21+
We sat down with engineers who have been in the MCP trenches. Their stories reveal a protocol that is powerful in theory but punishing in reality.
22+
23+
![Drake meme showing preference for UTCP over MCP](/img/drake-meme.png)
24+
25+
---
26+
27+
## 1. The "What the f*** is STDIO?" Problem
28+
29+
The first hurdle isn't the code; it's the dictionary. The documentation is a maze of conflicting examples and invented jargon that leaves engineers guessing at the most basic concepts.
30+
31+
As Marc, another early adopter, recalls from his first attempt:
32+
33+
> “I implement this class… and then I'm like, okay, let's connect to it. Connect, doesn't work. Why? How? And then I spend a bunch of time and then only realize this only works if the process calling this *is* the process which has this client on it. Which is like, what the fuck?”
34+
35+
This single, poorly explained assumption—that the client *must spawn your server as a child process*—is the source of immense pain. But the confusion runs deeper.
36+
37+
* **Ambiguous Transports:** The spec says it connects via “STDIO.” But what does that mean? Is it a raw `stdin/stdout` pipe? Is it a custom framing protocol over that pipe? Is it a TCP socket? You only discover the truth through trial and error.
38+
* **Contradictory Examples:** The official GitHub reference servers receive breaking changes weekly, often without a clear changelog, leaving developers to reverse-engineer why their once-working implementation suddenly fails.
39+
40+
---
41+
42+
## 2. You Have to Ship an Extra App Just to Run Your App
43+
44+
The protocol's architecture forces a clunky, "side-car" model that feels alien to modern development. You can't just expose an MCP interface from your existing application. Instead, you're forced to create a separate "middle-man" process.
45+
46+
Simone shared a particularly harrowing experience trying to build an MCP server for a VS Code extension:
47+
48+
> “To have the client access the VS Code extension, I would need to ship them an extra JavaScript file. And that JavaScript file needs to be run by them to proxy the call from my extension and their client. I have to somehow give them an extra JavaScript file to connect through my extension which they already downloaded. What?”
49+
50+
This got worse in a real-world scenario:
51+
52+
1. **The Setup:** A user has two VS Code windows open. One project is configured to use OpenAI, the other to use Gemini.
53+
2. **The MCP Problem:** MCP assumes one server process. When an AI client like Cursor connects to the single JavaScript "bridge," it has no idea which VS Code window (and which configuration) it's supposed to be talking to.
54+
3. **The Nightmare Hack:** Simone's only solution was to have his extension write the network port into a temporary file so the bridge could find it. He was juggling PIDs and file I/O just to manage state between two windows—a problem created entirely by MCP's rigid process model.
55+
56+
---
57+
58+
## 3. It Forces Statefulness on Stateless Problems
59+
60+
MCP is fundamentally opinionated and designed around long-lived, stateful sessions. It wants to manage *resources*, *prompts*, and *tools* over time. This is great for a complex, multi-turn copilot, but it's massive overkill for 99% of tool use cases.
61+
62+
The result is absurd overhead for simple tasks.
63+
64+
> “I just want my LLM to call `cat` command to read a file, and for that, I have to build a stateful server and do so many transactions before I actually get to my information. It’s wild.”
65+
66+
Want to expose a simple weather API? With MCP, you can't just make a direct call. You first have to build and maintain a persistent server process just to wrap that stateless `GET` request. In contrast, agents should be able to call existing REST or gRPC endpoints *directly*, with no middlemen.
67+
68+
---
69+
70+
## 4. It Doesn't Scale for Tools or for Teams
71+
72+
MCP’s design creates scalability bottlenecks at both the technical and organizational levels.
73+
74+
### The Prompt Bloat Problem
75+
How do popular clients like Cline handle multiple tools? They don't have a sophisticated search or routing mechanism. They just jam every single tool definition into the LLM's context window.
76+
77+
> “They inject all of the MCP stuff into the context. Which is crazy... What if I add 100 tools? How do I search for which one is relevant? It's completely omitted in MCP.”
78+
79+
This leads to enormous prompt bloat, wasted tokens, and why many clients have hard caps of 40 tools or less. The protocol itself offers no solution for tool discovery or search, a fundamental requirement for a truly scalable ecosystem.
80+
81+
### The Adoption Headache
82+
For enterprises, the problem is even bigger. A company like Amazon or Microsoft has thousands of existing, battle-tested HTTP APIs.
83+
84+
> “MCP is clearly not easy to adopt because you have to literally build new infrastructure for it... you have to have people which maintain the new code which popped up now to integrate MCP into their existing public APIs.”
85+
86+
Asking every team to re-implement their services behind a stateful MCP wrapper is a non-starter. It introduces a new point of failure, new maintenance costs, and new security surfaces.
87+
88+
---
89+
90+
![MCP Engineering Experience Meme](/img/mcp-meme.png)
91+
92+
93+
## 5. Security Feels Like a Hasty Patch Job
94+
95+
When a protocol adds security as an afterthought, it shows.
96+
97+
> “MCP didn't start with any sort of security... at some point they were like, what, maybe we should add security, which already is a bit of a red flag.”
98+
99+
The current approach is a patchwork of inconsistent, "roll-your-own" solutions:
100+
* **Undefined Standards:** Authentication often relies on stuffing an OAuth token into an "extra data" field that isn't formally specified. This means every client and server can interpret it differently, breaking interoperability.
101+
* **Complex Middleware:** If your app uses API keys but the client only supports OAuth, you're forced to build an authentication bridge. You now have a middle-man server whose only job is to translate one security token into another.
102+
* **Farming Vulnerabilities:** A malicious server can abuse the "server-initiated call" feature to farm your LLM for unlimited completions, running up your token bill without your knowledge.
103+
104+
---
105+
106+
## 6. So… Is there *anything* good?
107+
108+
Yes. MCP is great for giving giving your LLM rich context, just as its name implies. For that, it has two killer features:
109+
110+
1. **Server-Initiated LLM Calls:** A server can offload a sub-task back to the client’s model. For example, a tool for generating complex code could ask the client's LLM to write the documentation for it, using the client's budget.
111+
2. **Rich Context Packaging:** For deeply integrated IDE copilots that truly need to manage a web of chained prompts, file resources, and streaming diffs, MCP's stateful structure is powerful.
112+
113+
---
114+
If your goal is simply to let an AI agent call your API, MCP is not the answer today. Sometimes you don't need a "rich context with prompts and resources" to just ask for the weather.
115+
116+
To solve this problem at scale, we've adopted a simple, but intuitive approach: a protocol that allows LLMs to interact with APIs directly, just like developers do. No extra servers, no extra infra.
117+
118+
If you're interested, you can check the RFC [here](https://www.utcp.io/about/RFC).

docusaurus.config.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,32 @@ const config: Config = {
125125
},
126126

127127
{
128-
type: 'doc',
129-
docId: 'RFC',
128+
to: '/registry',
130129
position: 'left',
131-
label: 'RFC',
132-
docsPluginId: 'about',
130+
label: 'Tool Registry',
133131
},
134132
{
135-
to: '/registry',
133+
type: 'dropdown',
134+
label: 'more',
136135
position: 'left',
137-
label: 'Tool Registry',
136+
items: [
137+
{
138+
type: 'doc',
139+
docId: 'RFC',
140+
label: 'RFC',
141+
docsPluginId: 'about',
142+
},
143+
{
144+
type: 'doc',
145+
docId: 'about-us',
146+
label: 'About Us',
147+
docsPluginId: 'about',
148+
},
149+
{
150+
label: 'Blog',
151+
to: '/blog',
152+
},
153+
],
138154
},
139155
{
140156
type: 'docsVersionDropdown',
@@ -236,8 +252,8 @@ const config: Config = {
236252
to: '/about/RFC',
237253
},
238254
{
239-
label: 'Contributing',
240-
to: '/about/contributing',
255+
label: 'Contact the Founders',
256+
href: 'mailto:juan@bevel.software',
241257
},
242258
{
243259
label: 'Impressum',

src/css/custom.css

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -249,44 +249,38 @@ article a:hover,
249249
opacity: 0.8;
250250
}
251251

252-
/* Blog page styling - make navbar and sidebars more faded */
253-
.blog-wrapper .navbar__items .navbar__item .navbar__link:not(.github-button-custom),
254-
.blog-list-page .navbar__items .navbar__item .navbar__link:not(.github-button-custom),
255-
.blog-post-page .navbar__items .navbar__item .navbar__link:not(.github-button-custom),
256-
body[class*="blog"] .navbar__items .navbar__item .navbar__link:not(.github-button-custom) {
252+
/* Blog page styling - fade entire navbar */
253+
.blog-wrapper .navbar,
254+
.blog-list-page .navbar,
255+
.blog-post-page .navbar,
256+
body[class*="blog"] .navbar {
257257
opacity: 0.6 !important;
258258
transition: opacity 0.3s ease !important;
259259
}
260260

261-
.blog-wrapper .navbar__brand,
262-
.blog-list-page .navbar__brand,
263-
.blog-post-page .navbar__brand,
264-
body[class*="blog"] .navbar__brand {
265-
opacity: 0.6 !important;
266-
transition: opacity 0.3s ease !important;
267-
}
268-
269-
.blog-wrapper .navbar__toggle,
270-
.blog-list-page .navbar__toggle,
271-
.blog-post-page .navbar__toggle,
272-
body[class*="blog"] .navbar__toggle {
273-
opacity: 0.6 !important;
274-
transition: opacity 0.3s ease !important;
261+
.blog-wrapper .navbar:hover,
262+
.blog-list-page .navbar:hover,
263+
.blog-post-page .navbar:hover,
264+
body[class*="blog"] .navbar:hover {
265+
opacity: 0.9 !important;
275266
}
276267

277-
/* Hover effects for faded elements */
278-
.blog-wrapper .navbar__items .navbar__item .navbar__link:not(.github-button-custom):hover,
279-
.blog-list-page .navbar__items .navbar__item .navbar__link:not(.github-button-custom):hover,
280-
.blog-post-page .navbar__items .navbar__item .navbar__link:not(.github-button-custom):hover,
281-
body[class*="blog"] .navbar__items .navbar__item .navbar__link:not(.github-button-custom):hover {
282-
opacity: 0.9 !important;
268+
/* Floating GitHub button on blog pages */
269+
.blog-floating-github {
270+
position: absolute !important;
271+
top: 50% !important;
272+
right: 60px !important; /* Position where GitHub button normally is */
273+
transform: translateY(-50%) !important;
274+
z-index: 1000 !important;
275+
display: none !important; /* Hide by default */
283276
}
284277

285-
.blog-wrapper .navbar__brand:hover,
286-
.blog-list-page .navbar__brand:hover,
287-
.blog-post-page .navbar__brand:hover,
288-
body[class*="blog"] .navbar__brand:hover {
289-
opacity: 0.9 !important;
278+
/* Show floating GitHub button only on blog pages */
279+
.blog-wrapper .blog-floating-github,
280+
.blog-list-page .blog-floating-github,
281+
.blog-post-page .blog-floating-github,
282+
body[class*="blog"] .blog-floating-github {
283+
display: flex !important;
290284
}
291285

292286
/* Hide Discord button completely on blog pages */
@@ -297,21 +291,6 @@ body[class*="blog"] .navbar-discord-link {
297291
display: none !important;
298292
}
299293

300-
/* Version dropdown fading on blog pages */
301-
.blog-wrapper .navbar__items .dropdown,
302-
.blog-list-page .navbar__items .dropdown,
303-
.blog-post-page .navbar__items .dropdown,
304-
body[class*="blog"] .navbar__items .dropdown {
305-
opacity: 0.6 !important;
306-
}
307-
308-
.blog-wrapper .navbar__items .dropdown:hover,
309-
.blog-list-page .navbar__items .dropdown:hover,
310-
.blog-post-page .navbar__items .dropdown:hover,
311-
body[class*="blog"] .navbar__items .dropdown:hover {
312-
opacity: 0.9 !important;
313-
}
314-
315294
/* Blog sidebar fading */
316295
.blog-wrapper .blog-sidebar,
317296
.blog-list-page .blog-sidebar,

static/img/drake-meme.png

237 KB
Loading

static/img/mcp-meme.png

50.8 KB
Loading

0 commit comments

Comments
 (0)