跪拜 Guibai
← Back to the summary

MCP Is Three Primitives, Not One — and Your Token Should Never Touch the LLM

I wrote an MCP Server a long time ago, but I always thought MCP was only about Tools

I wrote a GitLab MCP Server quite early on.

At the time, I found the official SDK, created a Server, and used TypeScript and Zod to register a few tools:

It was indeed used by an Agent later. I also frequently use MCPs for Notion, Figma, and others in my daily work, so I always felt that I had "used MCP."

But today, while relearning the principles of MCP, I realized: my past practice was real, but my understanding was incomplete.

I had always treated Tool as the entirety of MCP.

MCP is more than just Tool

MCP stands for Model Context Protocol.

The core problem it solves is: enabling MCP-compatible applications to discover and use external capabilities according to a unified protocol, without needing to design a separate tool integration method for each Agent.

An MCP Server can expose three types of core capabilities:

Type Function My Understanding
Tool Execute operations Query GitLab projects, create Issues, modify data
Resource Provide readable data Project descriptions, repository content, materials accessible to the current user
Prompt Provide reusable prompt templates Code review templates, project analysis templates, fixed task entry points

The GitLab MCP I wrote in the past only registered Tools. In my daily use of MCP, my focus was always on which tools the Agent could call. Therefore, in my actual understanding, MCP was almost equivalent to "a protocol for uniformly integrating tools for Agents."

It wasn't until today that I learned an MCP Server can provide not only Tools but also Resources and Prompts. I didn't conclude after research that MCP only had Tools; rather, my past practice was limited to Tools, and I hadn't realized the protocol defined two other types of capabilities.

Currently, my understanding of Resources and Prompts is still at the conceptual level. The real usage scenarios and implementation methods will be verified in future MCP practice.

Who exactly are Host, Client, and Server?

Another term that confused me during the learning process was MCP Client.

Initially, I understood it as the "client application" we usually talk about, leading to this perception:

The MCP Server runs inside the MCP Client.

This statement looks very much like a fact in certain local running scenarios, but it confuses the relationship between roles and processes.

Taking Codex calling GitLab MCP as an example, the complete chain is:

Me
  → Codex (Host)
  → MCP Client inside Codex
  → Transport
  → GitLab MCP Server
  → GitLab API

Host is a role in the official MCP architecture. Here, it can be directly understood as the application that hosts and coordinates the Agent, MCP Client, and user interaction, such as Codex.

The MCP Client is the protocol component within the Host responsible for connecting to a specific MCP Server. It handles initializing the connection, discovering capabilities, sending tools/call requests, and receiving results.

The one actually executing the GitLab query logic is the GitLab MCP Server. The Tool handler inside the Server then requests the GitLab API.

Therefore, the LLM is responsible for understanding user intent and selecting capabilities, the MCP Client is responsible for protocol communication, and the MCP Server is responsible for executing the corresponding handler. They are not on the same layer.

stdio and Streamable HTTP change the connection method

My original belief that "the Server runs on the Client" mainly came from the experience of using stdio.

In stdio mode, the application where the MCP Client resides typically starts a local MCP Server child process, then communicates via standard input/output:

Codex
  → MCP Client
  → Starts local GitLab MCP Server
  → stdin/stdout communication

Therefore, from the perspective of the process lifecycle, the local Server can indeed be started and managed by the Client side.

But in Streamable HTTP mode, the structure is different:

Codex MCP Client
  → Connects to https://mcp.company.com
  → Independently deployed GitLab MCP Server

The remote Server is started and maintained by the company's deployment system. The MCP Client inside Codex is only responsible for connecting to it, not for starting it.

The two methods change the Transport and deployment topology, but do not change the responsibilities of the Client and Server.

Tokens should not enter the LLM context

The trust boundary of MCP is not just about "whether the connection can be successfully established," but also includes which components the credentials pass through.

A Token is needed when calling the GitLab API, but the LLM does not need to know this Token.

The ideal chain should be:

The LLM sees:
- User question
- Tool description
- Tool parameter structure
- Parameters for this call

The LLM does not see:
- GitLab Token
- Authorization request header
- Server-side environment variables

In stdio scenarios, credentials can be injected through the Server's runtime environment.

In remote HTTP scenarios, access credentials should be passed via the standard Authorization: Bearer <token> request header and must not be placed in URL query parameters.

The reason is not just "it looks insecure." URLs can enter browser history, reverse proxy logs, access logs, or monitoring systems. Even if HTTPS is used, query parameters can still be recorded by these systems after reaching the proxy or server and being decrypted.

Therefore, an error scenario was specifically added to the Demo:

if (url.searchParams.has("token")) {
  throw new Error("access token must not enter the URI query string");
}

And this validation must occur before the actual call to the GitLab API. Otherwise, even if the MCP Server ultimately reports failure, downstream side effects may have already occurred.

I verified this boundary with three scenarios

Today's Demo did not connect to a real GitLab or read real credentials; instead, it used local simulation to verify three situations.

How to run:

node demo.mjs stdio
node demo.mjs http-safe
node demo.mjs http-unsafe

The results of the three scenarios are:

  1. stdio

    The MCP Client's stdio transport starts the local Server, and the GitLab API is called once.

  2. http-safe

    The Server has already been started by the company's deployment system. The Client connects using the Authorization request header, and the GitLab API is called once.

  3. http-unsafe

    The Token is deliberately placed in the URL query parameter. The Server rejects the request before the downstream API call, and the GitLab API call count is zero.

All three scenarios also jointly check: the Token did not enter the simulated LLM context.

Full Demo:

2026-08-04 MCP Principles and Trust Boundary Experiment

This Demo is a deterministic local model, not a production-grade MCP SDK example. Its purpose is not to teach me to copy framework code, but to clearly run through the Client, Server, Transport, API, and credential boundaries.

MCP, API, Tool, Skill, and Plugin are not the same thing

Taking the GitLab scenario as an example, these concepts can be distinguished as follows:

Concept Responsibility in the GitLab Scenario
API The HTTP interface provided externally by GitLab
Tool Specific capabilities that an Agent can call, such as "query by project name"
MCP The unified protocol for the Client to discover and call Server capabilities
Skill Instructions telling the Agent when to call and what process to follow
Plugin A carrier for installing, distributing, and version-managing capabilities like Skills and MCP Servers

A Skill can define a process, but it does not automatically produce GitLab data modifications by itself.

Real side effects are usually produced by the Tool handler in the MCP Server calling the GitLab API. A Plugin can deliver a Skill and an MCP Server together, but it is not equivalent to MCP.

The understanding I truly corrected today

This is not my first time using MCP, but it is the first time I have placed my past MCP practice back into the complete architecture for understanding.

In the past, my implementation only had Tools, so I thought Tool was MCP; in the past, stdio would start a local process, so I thought the Server ran inside the Client; in the past, I knew the Token should be placed in environment variables, but I hadn't truly clarified the credential boundary between the LLM, Client, Server, and API.

Today I confirmed:

The next step is not to continue memorizing concepts, but to implement and actually connect to an MCP Server once: complete capability discovery and invocation, distinguish between read-only and write permissions, and observe how each layer returns results when a call fails.

Official References: