DEV Community

Cover image for What I didn't understand about MCP until I tried to use it
Nidhi
Nidhi

Posted on

What I didn't understand about MCP until I tried to use it

I'd read about Model Context Protocol the way most people probably have. A few blog posts, a diagram with boxes and arrows, a vague sense that it's "how you give an LLM access to external tools." I felt like I got it.

Then I tried to wire a Notion connection into a Slack bot and realized I didn't understand it at all. Not the concept, the concept's fine. What I didn't understand was what it actually feels like to use one, versus read about one. This is that gap closing, badly at first, while building TeamTrail for the Slack Agent Builder Challenge.

What I thought MCP was

My mental model going in was something like: MCP server equals an API your LLM can call directly, kind of like function calling but standardized. Hand Groq a server URL, register the tools, done.

Wrong. MCP doesn't talk to your LLM. It talks to your code. You write an MCP client, your client calls the MCP server, the server gives back results, and you decide what to do with those results, including whether they go into a prompt at all. The protocol has nothing to do with Groq, OpenAI, or Claude specifically. It's just a standard shape for your app to talk to an external service.

Once that clicked, a bunch of confusing documentation stopped being confusing. Most guides blur "how MCP works" with "how a specific chat client uses MCP" like they're the same layer. They're not.

There are two kinds of MCP server and they assume different things about you

I wanted Notion content in my briefings. Notion ships an official MCP server, so I figured this would be quick.

Notion's MCP server comes in two forms though. The one most docs lead with, the hosted version where you click connect and do OAuth, assumes a human is sitting at a chat client clicking "Authorize" when the connection happens. My bot is a background process. There's no human there, no browser tab to redirect to. OAuth needs somebody to click something, and nobody's clicking anything on a server.

I didn't know this distinction existed until I'd half set up the wrong one and hit a wall. The fix was simple once I knew to look for it, Notion also ships a local server you run yourself with a static integration token instead of an interactive login. But I want to flag this specifically because I think it's the most common trap for anyone using MCP from a backend service instead of a chat app: most MCP onboarding content is written for the chat-app case, and those assumptions don't automatically carry over.

Running the server taught me MCP has its own auth layer, separate from the one you're thinking about

Got the local server running. Tried to connect. Got back:

"Unauthorized: Missing bearer token"
Enter fullscreen mode Exit fullscreen mode

My first thought was that my Notion token was wrong. It wasn't. There are two different credentials doing two different jobs here:

  • The Notion integration token authenticates the server to Notion's API. This is the one I'd been thinking about.
  • The MCP server also generates its own random bearer token to protect its own local HTTP endpoint. I hadn't considered this existed at all, because nothing in how I'd pictured "client talks to server" included "and the server also has its own front door lock."

Once I saw it framed that way it made sense. Of course a server listening on a port wants its own access control, separate from whatever it does with that access once granted. I just hadn't built that into my model yet. For a local, loopback-only server in a dev sandbox, disabling that extra lock is the right move since nothing outside the machine can reach it anyway, but I had to understand why the lock existed before I trusted myself to turn it off.

Calling a tool and getting back something I didn't expect

Finally connected. Called the search tool. Got a result. Tried to read it as text I could drop into a prompt.

Right token

It wasn't text. It was a JSON string, Notion's actual API response, serialized, sitting inside the MCP response's text field. I'd assumed the name "model context protocol" meant the content would arrive pre-formatted for an LLM to read directly. It doesn't. MCP standardizes the call itself, tool names, arguments, a content array, but what's inside that content is entirely up to the server. In Notion's case that's its raw API output, completely unprocessed.

I also assumed search would return something I could summarize. It returned page titles and URLs. Nothing else. To get actual content I needed a second, separate tool call: fetch the page by ID, as markdown. Search finds things. It doesn't read them for you.

None of this is bad design on Notion's part looking back. It's just not what I'd pictured, and the only way I found out was writing a tiny standalone script that connected, listed the available tools, and printed whatever came back, before writing a single line of code that assumed anything about the shape of the response.

That habit, verify the real shape before writing logic against an assumed one, is the actual lesson here, more than any Notion-specific detail. I understand MCP now the way I understand most protocols I've actually had to build against: less a clean abstraction, more a real thing built by real people with specific decisions I have to go look at directly instead of guessing about.

Where that leaves me

TeamTrail grounds its onboarding briefings in both Slack, via Real-Time Search, and Notion, via that local MCP connection, with citations back to both. But the thing I actually want someone to take from this isn't the feature. "I read about MCP" and "I've used MCP" are two different states, and the gap between them was made of small, specific things I only found by trying and being wrong first.

So if you're about to wire up your first MCP server, don't start with the integration. Start with a script that does nothing but connect, list the available tools, call one, and print exactly what comes back. Read that output before you write a single line that assumes anything about its shape. It won't look like you think it will, and finding that out on purpose costs you ten minutes instead of an afternoon.

Honest addendum, in hindsight.

All three detours had the same root cause. Same fix too. I knew it before I started.
"Connect, list tools, call one, print the raw output." Not just advice for whoever's reading this. It's the move I make on every system I touch, write code for it or not. Don't trust the mental model. Go look at the real thing.
That's the TPM instinct showing up here more than the engineering one. I didn't need to write an MCP server from scratch to get this working. I needed to understand the protocol well enough to know where my assumptions were probably wrong, then go check them one at a time. Auth assumptions, response shape assumptions, all of it. The fix was never a deep coding skill. It was the same pattern-spotting I'd apply to any system I'm trying to make sense of fast.
I had the real server sitting right there the whole time. Querying it directly would've cost ten minutes instead of time spent debugging code written against a guess I never checked.

Code's on GitHub: github.com/Hereforlolz/slack-ai-playground
Demo:

Built for the Slack Agent Builder Challenge 2026.

Top comments (0)