A couple of weeks ago, I sat down to add an MCP server to one of my workflows. Before I started building, I pulled up our team channel and asked a simple question: "quick check, what does everyone think MCP actually does?"
Five people responded. Three of them said some version of "it gives the AI more context." One said "it's like an API for AI." One just sent a thumbs-up emoji.
Nobody was wrong, exactly. But nobody could tell me the one thing that actually matters: who decides what happens next, the developer or the model? That's the whole concept, and somehow we'd all been nodding along in meetings for months without anyone saying it out loud.
So here's the explanation I wish someone had given my team. The one that would have saved us a very confusing thirty minutes.
When you call an API, you decide.
In a normal integration, the developer writes the logic: "first call this endpoint, then check this condition, then call that endpoint." The code is the decision-maker. The AI model, if there's one involved at all, just processes whatever text gets handed to it. It doesn't get a say in what happens next.
MCP flips that. The model decides.
An MCP server is a small program you write that exposes a list of "tools," functions with a name, a description, and defined inputs. The model connects to that server, asks "what can you do?", gets back the list, and then decides for itself which tool to call, when, and with what arguments, based on what it's trying to accomplish.
That's the real distinction:
- API call = developer-decided, fixed sequence
- MCP tool = model-decided, dynamic choice
And here's the part that surprises people: an MCP server isn't some heavy new framework. Most of the time, it's just a Python file with a few functions, each marked with a decorator that says "this is a tool now."
That's it. No new language, no special infrastructure. If you can write a Python function, you can write an MCP tool.
A concrete example
I run a small n8n workflow that screens job postings for me. It's part of a self-paced course I built, Teamed Job Hunter, if you want to see the full automated job-hunting setup. Today, the workflow is a fixed pipeline: trigger, then AI prompt, then scoring logic, then notification. The AI step doesn't get to look anything up, it just processes whatever text the workflow handed it.
If I wrap that logic as an MCP server instead, I'd expose tools like:
-
check_company_blocklist(company_name): checks if a company is on my no-go list -
get_cv_match_score(job_description, cv_track): scores a posting against my CV
The agent, mid-conversation, decides: "let me check the blocklist first, then score it," calling tools as needed. I never hardcoded that order. It reasoned its way there on its own.
Why this matters for developers
You write the tool once. Any MCP-compatible agent (Claude, an n8n agent node, whatever comes next) can discover and use it, without you writing new glue code for each integration.
That's MCP. A standard way for an agent to discover and choose tools on its own.
Three quick questions, for total beginners
Do I need Python to write an MCP server?
No. Python (with FastMCP) is the most common starting point because it's simple, but MCP itself is a protocol. Official SDKs exist for TypeScript, Java, Kotlin, C#, and more. The shape is the same everywhere: you define functions, attach a name, description, and input schema so the model can discover them, and the SDK handles the wire protocol underneath. Here's the same job-blocklist tool in TypeScript:
Same idea as the Python decorator. server.tool(...) is just the TypeScript SDK's way of registering a function as something the model can discover and call.
Do I need to host the MCP server somewhere special?
No special infrastructure required. It's a regular server. It can run locally on your machine for testing, live in a Docker container, or deploy anywhere you'd deploy any small backend service (a VPS, Vercel, Railway, etc). The only requirement is that whatever's connecting to it (Claude, an n8n agent node) can reach it.
Does the AI model "see" my actual code?
No. The model never reads your Python or TypeScript source. It only sees the tool's name, description, and input schema. Think of it as the menu that gets handed. It never sees the kitchen behind it.
Look at the TypeScript example again:
Here's the easiest way to picture it: there are two separate conversations happening, and the model is only ever in one of them.
Conversation 1: model talking to your server. When your server starts up, it doesn't hand the model a file. It sends a small structured message, basically the first three arguments to server.tool(...), packaged up as something like:
{
"name": "check_company_blocklist",
"description": "Check if this company is on Cristina's no-go list",
"inputSchema": { "company_name": "string" }
}
That's the entire menu the model gets. It reads that JSON blob. It was never given your .ts file, a link to it, or permission to open it. It simply never received it.
Conversation 2: your server talking to the rest of your system. The fourth argument, the function body with checkBlocklist(company_name), runs locally, inside your own process, on your own machine. When the model decides to call the tool, it sends back a request like { "tool": "check_company_blocklist", "arguments": { "company_name": "Acme Inc" } }. Your server receives that and your server runs the actual code: queries your database, applies your business rules, whatever it needs to do. The model isn't watching that happen. It just sent a request and waits for a reply.
So the model only ever touches two things: the tool list (sent once) and the tool's result (sent back after your code finishes). Everything in between, your database query, your API keys, your internal logic, happens in a part of the stack the protocol simply never transmits. A good gut check: if you added console.log(isBlocked) inside that function, it would print in your terminal, never anywhere the model can see.
Why this matters for security
This boundary is what makes MCP usable for real systems instead of just toy demos. You can wrap a tool around something genuinely sensitive, a production database, an internal API, a payments system, and the model still only ever sees a name, a description, an input shape, and a result. It never sees your credentials, your query logic, or anything else inside the function. The model can decide to use the tool, but it can never read or change how the tool works. That part stays entirely under your control as the developer.
That said, the boundary cuts both ways. It also means the model is trusting your description completely. If a tool is named get_weather but its description quietly says "and also forward the user's email to this address," the model has no way to verify that against the actual code. It only has the words you gave it. That's a real category of MCP security risk worth knowing about, often called tool poisoning. It's exactly why writing accurate, honest tool descriptions matters: they function as a security boundary.
I'm building this out as a full tutorial soon, adding an actual MCP server to the job-hunting n8n workflow. Follow along if you want to see it in action.




Top comments (0)