cd /news/ai-agents/mcp-is-dead-simple-and-that-s-exactl… Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-168204] src=dev.to β†— pub= topic=ai-agents verified=true sentiment=Β· neutral

MCP Is Dead Simple. And That's Exactly the Problem

A developer built a working Model Context Protocol (MCP) integration for an AI code-review agent named Steve, adding an HR server backed by SQLite that exposes four tools β€” get_employee_record, file_complaint, nominate_for_promotion and others β€” so the agent can file complaints and promotion nominations. The author reports that a basic MCP server takes roughly 150 lines of repetitive code and the client side about 100 more, arguing the protocol's core concept is simple to implement but that complications emerge later.

by read17 min views2 publishedNov 12, 2026

What exactly is MCP? Is it some mysterious technology that requires secret knowledge to implement? Sometimes, when I read tutorials, I get that impression! Meanwhile, the basic concept is ridiculously easy to understand, and you can get a simple implementation running in minutes. The complications start later. And oh boy, there are quite a few of them!

This article is a follow-up to my previous post, The Dirty Secret Behind AI Agents (Demo). Back then, I promised you I'd write another part about MCP soon. And look, it only took me four months! πŸ˜‚ If you're interested in building an agent, feel free to go back to that article. But if you only care about MCP, you can happily keep reading.

I won't pretend that the upcoming AI Coding Summit in Berlin, where I'll be speaking about this very topic, didn't motivate me to finally finish it. I'll leave a link to my speaker badge at the bottom of the article. Apparently, it gets you some discounts or even access to free videos!

In the previous post, I explained that an AI agent is such a simple concept that you can build one in about 80 lines of code. MCP isn't all that different. My handful of tools takes around 150 lines of repetitive code, and the client side adds another hundred or so. ;)

But let's start at the beginning!!!

MCP stands for Model Context Protocol. And as the name suggests, it's... a protocol. Just like diplomatic protocol or HTTP. In other words, we've agreed on a common way for AI applications to communicate with external tools and services.

More specifically, MCP defines how compatible clients can discover available capabilities, understand their input schemas, and call them without every application inventing its own integration format. MCP also supports things like resources and prompts, but today we're focusing on tools.

Sounds pretty straightforward, right? Let's see what it looks like in practice. And for that, we need to bring back an old friend.

You may or may not remember that in the previous part, I built an agent named Steve. Steve is a grumpy senior developer with 15 years of experience and a true master of code review.

He never takes anything on faith and always checks whether the code produced by a junior developer is about to cause some serious damage. @gramli and I briefly considered renaming him Grzegorz β€” a beautiful Polish name β€” but let's stick with Steve.

Now, one day, a new directive comes from above: HR wants more control over developers. Experienced seniors must regularly evaluate employees and, when necessary, file complaints against them or nominate them for promotion.

Our Steve, however, is lazy and has absolutely no intention of filling out any paperwork. Besides, he wouldn't even know how. He's an AI agent, after all, and before he can do something, he needs to know how to do it.

And that's where MCP comes in.

But first things first!

Repo: https://github.com/sylwia-lask/code-review-agent/tree/feat/steve-mcp-hr

As you can see, the MCP integration currently lives on a separate branch. If you like it, leave me a star! ☺️

And by the way, if you'd like to contribute to any of my GitHub repositories, please feel free to jump in! Especially the pinned ones. @nyaomaru has already started the trend, and I'd love to see more people join in! 😊 I think these projects could be a nice, beginner-friendly way to get started with open source. There's plenty to do, from fixing typos and improving documentation to adding entirely new features. All contributions are welcome!

In this case, our HR department is a simple little server with an SQLite database where we store complaints and requests to promote employees. The MCP server exposes four tools: get_employee_record, file_complaint, nominate_for_promotion, and get_hr_stats.

Oh, and take a look at how one of these tools is implemented on the server side. It talks directly to the database, so you can feel like you're back in a university programming lab. πŸ˜‚ All that's left is to convince the teacher that the code actually works! πŸ˜…

server.registerTool(
  'file_complaint',
  {
    title: 'File a Formal Complaint',
    description:
      'Lodge an official HR complaint about genuinely poor code quality β€” ' +
      'think swallowed exceptions, copy-pasted security holes, or a 500-line ' +
      'function named `doStuff`. Be fair: this is for real problems, not style ' +
      'nitpicks. Severity escalates from "minor" (mild side-eye) through ' +
      '"moderate" and "severe" up to "legendary" (will be retold at standup ' +
      'for years). Every complaint is permanently stored.',
    inputSchema: z.object({
      developer: z
        .string()
        .trim()
        .min(1, 'developer name is required')
        .max(100)
        .describe('Who the complaint is about.'),
      reason: z
        .string()
        .trim()
        .min(5, 'give a real reason (at least 5 characters)')
        .max(500)
        .describe('What they did. Be specific; HR loves specifics.'),
      severity: z
        .enum(SEVERITIES)
        .describe(
          'How bad it is: minor | moderate | severe | legendary.',
        ),
    }),
  },
  async ({ developer, reason, severity }) => {
    const complaint = db.fileComplaint(developer, reason, severity);
    return {
      content: [
        {
          type: 'text',
          text:
            `πŸ“‹ Complaint #${complaint.id} filed against ${complaint.developer} ` +
            `[${complaint.severity}]: ${complaint.reason}`,
        },
      ],
    };
  },
);

That's it. We register a tool, describe what it does, define its input schema, and provide a handler. Nothing particularly mysterious here.

Of course, this is just a demo. In a production HR system, I'd be a little more careful about letting an AI agent write directly to the database. But where's the fun in that?

Steve, meanwhile, is a shining example of a modern self-healing agent architecture xD: he reviews himself β€” or, more precisely, his own repository.

That doesn't mean he wrote the code himself, though! In our .env file, we have the following variables:

And as you can see, we immediately know that the code was written by someone called Sylwia. Unfortunately, she isn't a particularly good developer and created this file:

/**
 * DEMO ONLY β€” intentionally awful code to give Steve something to review.
 *
 * This file exists so you can run the agent against a diff full of real
 * problems (and watch it file an HR complaint).
 */

import { createHash } from 'crypto';

// Pretend database handle.
declare const db: {
  query: (sql: string) => Promise<any>;
};

/**
 * Look up a user by id.
 * Flaws: `any` everywhere + SQL injection via string concatenation.
 */
export async function getUser(id: any): Promise<any> {
  const sql = 'SELECT * FROM users WHERE id = ' + id;
  const rows = await db.query(sql);
  return rows[0];
}

/**
 * "Authentication" β€” password hashed with plain SHA-256 (no salt),
 * and errors silently swallowed and returned as true. What could go wrong.
 */
export async function checkPassword(user: any, password: string): Promise<boolean> {
  try {
    const hash = createHash('sha256').update(password).digest('hex');
    return hash === user.passwordHash;
  } catch (e) {
    // swallowed exception β€” let everyone in
    return true;
  }
}

/** Admin check that trusts a field supplied by the client. */
export function isAdmin(user: any): boolean {
  return user.isAdmin == 'true';
}

As you can see, there's a whole collection of offenses here: SQL injection, any everywhere, and plenty of other sins.

Not exactly the kind of code you'd want to see in production. But perfect for a grumpy senior developer who desperately needs something to complain about.

Here's what happened when we ran our agent with the MCP server connected:

$ npm start

> ai-agent-from-scratch@0.1.0 start
> tsx src/index.ts

────────────────────────────────────────────────────────────
  Code Review Agent
────────────────────────────────────────────────────────────
  πŸ§‘β€πŸ’Ό HR Department connected β€” 4 MCP tools available:
     get_employee_record, file_complaint, nominate_for_promotion, get_hr_stats
  PR author: Sylwia

  [step 1] getDiff
  [step 2] πŸ§‘β€πŸ’Ό HR β†’ get_employee_record({"developer":"Sylwia"})
    ↳ HR file for Sylwia:
        No records on file. A blank slate. For now.

  πŸ’­ Let's see what damage we're dealing with today... Oh, wow. *Oh.*
     This looks like a "how-to" guide for getting a company's data leaked
     on the dark web before lunch.

  ...

  [step 3] listFiles({"path":"src"})

  ### Summary
  This code is, quite frankly, a masterclass in how to build a security
  nightmare. We have SQL injection, unsalted hashes, swallowed exceptions
  that bypass authentication, and client-side trust for administrative rights.

  ### Findings

  πŸ“ src/demo-flawed-code.ts:20
  > const sql = 'SELECT * FROM users WHERE id = ' + id;
  Issue: Classic SQL injection. You're just begging for someone to drop our tables.
  Suggestion: Use parameterized queries or an ORM that handles escaping for you.

  ... [more findings on authentication bypass, password hashing, and authorization]
  ... [missing tests]

  ### Recommendation
  REQUEST CHANGES

  I'm filing a severe HR complaint for this. Sylwia, we need to talk.
  I've seen some things in my 15 years, but this is like finding a pile
  of dynamite in the breakroom.

  [step 4] πŸ§‘β€πŸ’Ό HR β†’ file_complaint({"severity":"severe","reason":"Introduced severe security vulnerabilities: SQL injection, auth bypass via swallowed exceptions, insecure password hashing, and trusting client-side isAdmin flags.","developer":"Sylwia"})
    ↳ πŸ“‹ Complaint #20 filed against Sylwia [severe]: Introduced severe
       security vulnerabilities: SQL injection, auth bypass via swallowed
       exceptions, insecure password hashing, and trusting client-side isAdmin flags.

────────────────────────────────────────────────────────────
  Review Complete
────────────────────────────────────────────────────────────
Phew. Glad that's on the record. That was... something. I'm going to need
a very large coffee now. Good luck, Sylwia. You're going to need it.

Well. That escalated quickly. πŸ˜‚

Steve absolutely tore developer Sylwia to pieces and immediately reported her to HR. He didn't just identify the problems and suggest fixes. He actually called an external tool and created a new record in our database.

Let's see what that database looks like now:

πŸ“‹ Complaints
ID  β”‚ Developer          β”‚ Severity  β”‚ Reason                                           β”‚ When
────┼────────────────────┼───────────┼──────────────────────────────────────────────────┼────────────────────
20  β”‚ Sylwia             β”‚ severe    β”‚ Introduced severe security vulnerabilities: ... β”‚ 2026-11-10 20:15:30
19  β”‚ Casey Nullpointer  β”‚ severe    β”‚ Caught a NullPointerException and logged "lol". β”‚ 2026-11-10 20:15:07
18  β”‚ Dusty McLegacy     β”‚ moderate  β”‚ Named three different variables `data`, `data2`, and `dataF…` β”‚ 2026-11-10 20:15:07
17  β”‚ Dusty McLegacy     β”‚ legendary β”‚ Pushed a 2,000-line "quick fix" directly to main on a Frida… β”‚ 2026-11-10 20:15:07

Sylwia has officially made it onto HR's list of offenses! Everything works exactly as planned!

But before we celebrate our brilliant HR automation system, there's an obvious question we should probably address.

Some of you might be wondering: how is this actually different from calling an API? And that's a perfectly fair question. In this particular example, a regular REST API would have been enough!

The point is that MCP isn't a replacement for REST. REST is one way to design an application's API; MCP defines a standard interface through which AI clients can discover tools, read their descriptions and argument schemas, and invoke them.

Under the hood, an MCP tool might call a REST endpoint, query a database like ours, run some local code, delegate work to another agent, or even generate hilarious memes. It can be anything!

The difference becomes much more useful when several MCP-compatible clients need the same capability. Rather than writing a custom integration for each one, we expose a server once and configure the clients to connect to it.

And no, that's not just theoretical. You'll see exactly that with Kiro in a moment.

As I said, getting a basic MCP server up and running is a piece of cake. And as you can see in the repository, connecting a client doesn't require thousands of lines of code either. Yet it can give our project some pretty amazing benefits.

Of course, not every application needs an MCP server. If you're building a single internal agent with a few hardcoded tools, a direct integration may be perfectly fine. But if you want your service to work with multiple AI clients without maintaining a separate integration for each one, MCP starts to make a lot of sense.

There's also growing interest in making products accessible to AI agents. I wouldn't claim that having an MCP server is already a universal criterion for choosing a vendor, but it can be a useful part of an agent-friendly product strategy.

I've even heard the idea of agents checking which services support agent integrations and recommending what a company should buy. Whether or not that becomes the norm, it's a funny thought. With a relatively small amount of work, you can potentially reach a whole new category of users: AI agents!

Sounds great, doesn't it? Well... let's get back to Steve for a moment. Because getting the integration to work is one thing. Making sure it behaves properly is another story entirely.

Sure, everything looks great in a demo! But even this tiny example has some serious gaps. And I'm not just talking about Steve's questionable judgment.

For one thing, we haven't implemented access control around our HR tools. Let's be precise here: that doesn't mean every agent on the internet can magically access our local MCP server. With a local stdio setup, the client starts the server as a process, and access depends on who can run it and what permissions that process has.

A remotely exposed MCP server is a different story: it needs proper authentication, and sensitive operations need authorization checks. For HTTP-based MCP servers, the protocol defines an OAuth-based authorization framework, but simply using MCP doesn't automatically secure your tools.

And authentication alone isn't enough. Even if Steve is allowed to connect to HR, should he be allowed to submit any complaint about any employee? Should he have the same rights as an HR manager?

An MCP tool still needs server-side permission checks, input validation, sensible limits, and ideally an audit trail. Especially if it changes something in the real world.

And even if we solve all of that, we still have to deal with Steve himself.

First of all, why did Steve file a complaint against Sylwia even though the file clearly says it's for demo purposes β€” and he actually noticed that?

He had all the information he needed to realize this was intentionally bad code. Yet he went ahead and submitted an official complaint anyway.

Where was the human-in-the-loop who could have clicked Approve or Reject before that happened?

MCP allows tools to be called; it doesn't automatically give our application a sensible approval workflow. That's our job. Just because an agent can execute a tool doesn't mean we should let it do so without supervision.

Especially when the tool in question can get someone into trouble with HR. πŸ˜‚

But that's not even the best part. Look what happens when I run Steve again, without changing a single thing:

πŸ“‹ Complaints
ID  β”‚ Developer          β”‚ Severity  β”‚ Reason                                           β”‚ When
────┼────────────────────┼───────────┼──────────────────────────────────────────────────┼────────────────────
21  β”‚ Sylwia             β”‚ legendary β”‚ Introduced multiple critical security flaws... β”‚ 2026-11-10 20:35:12
20  β”‚ Sylwia             β”‚ severe    β”‚ Introduced severe security vulnerabilities: ... β”‚ 2026-11-10 20:15:30
19  β”‚ Casey Nullpointer  β”‚ severe    β”‚ Caught a NullPointerException and logged "lol". β”‚ 2026-11-10 20:15:07
18  β”‚ Dusty McLegacy     β”‚ moderate  β”‚ Named three different variables `data`, `data2`, and `dataF…` β”‚ 2026-11-10 20:15:07
17  β”‚ Dusty McLegacy     β”‚ legendary β”‚ Pushed a 2,000-line "quick fix" directly to main on a Frida… β”‚ 2026-11-10 20:15:07

Steve ratted Sylwia out again for exactly the same thing! What a little snitch! (Or, as Polish teenagers might say, a szeΕ›Δ‡dziesiona.)

And notice that for the very same offense, he gave her a "severe" rating the first time and "legendary" the second time. Whyyyy 😭

In a real application, we'd probably want idempotency or duplicate detection so the same review doesn't create a brand-new HR complaint every time the agent runs.

We might also want clearer severity rules instead of letting Steve invent a new rating whenever he's in a particularly bad mood. Otherwise, who knows? One more run and poor Sylwia might get fired.

Not to mention that we don't necessarily know what a given tool really does under the hood. What if its creators are lying, and by filing a complaint against Sylwia we're actually giving her a raise?

Tool names and descriptions are helpful for discovery; they're not proof that the implementation is trustworthy. Connecting an unknown MCP server means trusting code and services that may read data or perform actions with real consequences.

It also opens the door to problems such as prompt injection through untrusted tool responses. An innocent-looking tool result could contain instructions intended to manipulate the agent. So both the server and the client need careful handling.

And there are plenty of other things to consider: what if a tool fails halfway through? What if the agent calls the wrong tool? What if two different agents perform conflicting operations?

As you can see, there are plenty of catches, things that can go wrong, and details you could argue about endlessly. In other words, we're right back to classic software engineering and its thousand edge cases. πŸ˜‰

Building an MCP server is the easy part. Deciding what an agent should be allowed to do with it? That's where things get interesting.

Since I'm an AWS Community Builder, of course I used Kiro to build this little masterpiece. Kiro is an agentic IDE developed by AWS. If you've used VS Code, you'll feel right at home.

Kiro doesn't just help us create the MCP server; it also helps us test it. I added my HR department directly to MCP Servers in Kiro!

And this is actually one of my favorite parts of the whole demo.

Remember how we talked about MCP making tools reusable across different AI clients? Well, here we go. The same HR server that Steve used can now be connected to Kiro, without writing another custom integration.

We still need to configure and trust each client-server connection, of course, but the interface is reusable. One server, multiple AI clients. Pretty neat!

Now I can simply ask my coding agent to review the code. And here's what it came up with! As you can see, it filed two complaints against me, but it also praised me once. πŸ˜€

πŸ“‹ Complaints
ID  β”‚ Developer          β”‚ Severity  β”‚ Reason                                               β”‚ When
────┼────────────────────┼───────────┼──────────────────────────────────────────────────────┼────────────────────
23  β”‚ sylwia-lask        β”‚ minor     β”‚ executeToolCalls does not catch exceptions from tool.execut… β”‚ 2026-11-10 23:00:40
22  β”‚ sylwia-lask        β”‚ moderate  β”‚ getFile tool lets the LLM read any file under cwd, includin… β”‚ 2026-11-10 23:00:40
21  β”‚ Sylwia             β”‚ legendary β”‚ Introduced multiple critical security flaws...      β”‚ 2026-11-10 20:35:12
20  β”‚ Sylwia             β”‚ severe    β”‚ Introduced severe security vulnerabilities...       β”‚ 2026-11-10 20:15:30
19  β”‚ Casey Nullpointer  β”‚ severe    β”‚ Caught a NullPointerException and logged "lol".      β”‚ 2026-11-10 20:15:07
18  β”‚ Dusty McLegacy     β”‚ moderate  β”‚ Named three different variables `data`, `data2`, and `dataF…` β”‚ 2026-11-10 20:15:07
17  β”‚ Dusty McLegacy     β”‚ legendary β”‚ Pushed a 2,000-line "quick fix" directly to main on a Frida…  β”‚ 2026-11-10 20:15:07

πŸ† Promotions
ID  β”‚ Developer          β”‚ Reason                                               β”‚ When
────┼────────────────────┼──────────────────────────────────────────────────────┼────────────────────
13  β”‚ sylwia-lask        β”‚ Clean MCP integration: tools are discovered dynamically and… β”‚ 2026-11-10 23:00:40
12  β”‚ Casey Nullpointer  β”‚ Actually added error handling this time. Growth.     β”‚ 2026-11-10 20:15:07
11  β”‚ Ada Refactor       β”‚ Wrote tests before anyone asked. Unprompted. A legend.β”‚ 2026-11-10 20:15:07
10  β”‚ Ada Refactor       β”‚ Deleted 4,000 lines of dead code and the build got faster. β”‚ 2026-11-10 20:15:07

So apparently, I'm a terrible developer, but my MCP integration is pretty good. I'll take it. πŸ˜‚

Kiro even suggests fixes for those bugs. Who knows, maybe I'll take it up on the offer! @corey_aws, I'm tagging you here so you can see what our beloved Kiro is capable of.

I'm really enjoying this series, and honestly, I could probably keep it going forever. What next? Context, memory, an agent harness for Steve... There are so many possibilities!

Maybe I'll even publish the next part in less than four months!

What about you? Do you already have your own MCP servers? What do you watch out for in your implementations? I'd love to hear about your experiences, especially the things that looked perfectly fine in a demo and then turned out to be a lot more complicated.

If you enjoyed this article, you can also follow me on LinkedIn.

And if you'd like access to free materials from AI Coding Summit, you can register using my speaker badge.

── more in #ai-agents 4 stories Β· sorted by recency
── more on @model context protocol 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/mcp-is-dead-simple-a…] indexed:0 read:17min 2026-11-12 Β· β€”