The Model Context Protocol (MCP) gives AI applications a standard way to discover and use external capabilities. Instead of building a custom integration for every model, tool, data source, and prompt library, an application can connect to an MCP server and negotiate what that server provides.

That short definition hides several layers that are easy to mix up. MCP is not a transport, although it defines standard transports. It uses JSON-RPC 2.0, but it does not expose every optional JSON-RPC behavior in every protocol revision. A tool is not the same thing as a resource, and a reusable MCP prompt is not a server-controlled system prompt.

This guide builds a practical mental model of MCP, compares the major protocol revisions, explains stdio and Streamable HTTP, and shows how tools, resources, and prompts fit together. The Go examples are based on a real CMS MCP server that exposes editorial operations and Google Search Console data.

Version note: this article uses the 2025-11-25 specification, the latest final MCP revision at the time of writing. Draft and release-candidate revisions are intentionally excluded from the baseline behavior described here.

What problem does MCP solve?

An AI application often needs more than a language model. It may need to read files, query a database, inspect an issue tracker, publish a CMS draft, or load application-specific instructions.

Without a shared protocol, each integration has to invent its own answers to questions such as:

  • How does a client discover available operations?
  • How are operation inputs described and validated?
  • How can the application read contextual data without treating every read as an action?
  • How are reusable prompt templates exposed?
  • How do client and server agree on supported features?
  • How are requests, responses, notifications, errors, and long-running operations represented?
  • How does the same integration work locally and remotely?

MCP standardizes this boundary. It focuses on exchanging context and capabilities between an AI application and external servers. It does not prescribe which model the host uses, how an agent plans, or how retrieved context is ultimately presented to the model.

The official MCP architecture overview divides the protocol into a data layer and a transport layer. Keeping those layers separate is the simplest way to understand the rest of MCP.

Hosts, clients, and servers

MCP follows a client-server architecture, but it uses three participant names:

  • MCP host: the AI application that coordinates models, user interaction, permissions, and connections.
  • MCP client: a component inside the host that maintains a connection to one MCP server.
  • MCP server: a program that exposes capabilities and context through MCP.

A host normally creates a separate MCP client for each server:

AI application / MCP host
  鈹溾攢鈹€ MCP client A 鈹€鈹€ MCP server A
  鈹溾攢鈹€ MCP client B 鈹€鈹€ MCP server B
  鈹斺攢鈹€ MCP client C 鈹€鈹€ MCP server C

The word "server" does not mean that the process must run on another machine. A local command launched by the host over stdio is still an MCP server. A remotely deployed service reached over Streamable HTTP is also an MCP server.

The two layers of MCP

An MCP connection can be pictured as two layers:

鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?    鈹?MCP data layer                      鈹?    鈹?JSON-RPC, lifecycle, capabilities,  鈹?    鈹?tools, resources, and prompts       鈹?    鈹溾攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?    鈹?Transport layer                     鈹?    鈹?stdio or Streamable HTTP            鈹?    鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?

The data layer defines message semantics. It includes initialization, version and capability negotiation, server features, client features, and notifications.

The transport layer moves those messages between processes. The current standard transports are stdio and Streamable HTTP. Both carry MCP messages encoded with JSON-RPC 2.0.

This distinction prevents a common mistake: stdio and HTTP are not two different versions of MCP. They are two ways of carrying the same negotiated protocol.

JSON-RPC 2.0: the message envelope

MCP uses JSON-RPC 2.0 as its underlying message format. The most important message types are requests, responses, error responses, and notifications.

Requests

A request has an id, a method, and optional parameters. The receiver must eventually return a response associated with the same id.

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

The id correlates the response with the request. It is not a tool name, a session identifier, or an HTTP request ID.

Successful responses

A successful response contains the matching id and a result:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": []
  }
}

Error responses

When the RPC request itself cannot be processed, a JSON-RPC error response contains an error code and message:

{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32601,
    "message": "Method not found"
  }
}

MCP also distinguishes protocol errors from tool execution errors. If tools/call is valid but the underlying operation fails鈥攂ecause an article ID does not exist, for example鈥攖he server can return a tool result with isError: true. This gives the model an actionable result and a chance to correct its input. It should not be misrepresented as a malformed JSON-RPC request.

Notifications

A notification has no id and receives no response:

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

Notifications are useful for lifecycle events, progress, cancellation, logging, and change announcements.

MCP constrains how JSON-RPC is used. For example, JSON-RPC batching was introduced in the 2025-03-26 MCP revision and removed in 2025-06-18. Implementers must follow the negotiated MCP revision instead of assuming that every JSON-RPC option is available.

Connection lifecycle and version negotiation

MCP is stateful. According to the official lifecycle specification, a connection moves through initialization, normal operation, and shutdown.

The client begins with an initialize request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {},
    "clientInfo": {
      "name": "example-client",
      "version": "1.0.0"
    }
  }
}

The server responds with:

  • the protocol version it wants to use;
  • its supported capabilities;
  • server implementation metadata;
  • optional instructions for the client.

If the server supports the requested version, it returns that version. Otherwise, it returns another version it supports. A client that cannot support the server's selected version should disconnect.

After initialization succeeds, the client sends notifications/initialized. Only then does normal operation begin.

Capabilities matter as much as the version number. A server may advertise tools, resources, prompts, logging, completions, or experimental features. A client may advertise roots, sampling, elicitation, and other supported features. Both sides must use only capabilities that were negotiated.

For Streamable HTTP, subsequent requests must include the negotiated version in the MCP-Protocol-Version header.

Protocol version versus implementation version

These are different values. A Go server may identify itself like this:

server := mcp.NewServer(
    &mcp.Implementation{
        Name:    "cms-operator",
        Version: "0.2.0",
    },
    options,
)

0.2.0 is the version of the cms-operator application. It is not the MCP protocol revision. The negotiated protocol revision is a date such as 2025-11-25 and is exchanged during initialization.

How the major MCP versions differ

MCP revisions are date-based. The following table focuses on changes that affect the concepts covered in this guide.

Revision Important changes
2024-11-05 Established an early stable baseline. Remote communication used the older HTTP+SSE transport.
2025-03-26 Replaced HTTP+SSE with Streamable HTTP, added an OAuth 2.1 authorization framework, introduced tool annotations, added audio content and argument completion capabilities, and temporarily added JSON-RPC batching.
2025-06-18 Removed JSON-RPC batching, added structured tool output and resource links in tool results, introduced elicitation, strengthened authorization discovery and resource indicators, and required the protocol-version header on subsequent HTTP requests.
2025-11-25 Added icons for primitives, improved authorization discovery and incremental consent, expanded elicitation, allowed tool use during sampling, clarified tool naming and execution errors, and introduced experimental Tasks for durable operations.

The changes are not purely additive. Batching is the clearest example: code written against 2025-03-26 cannot assume the same behavior in 2025-06-18.

For precise migration details, consult the official changelogs for 2025-03-26, 2025-06-18, and 2025-11-25.

stdio transport

With stdio, the MCP client launches the server as a child process. The client writes JSON-RPC messages to the server's standard input, and the server writes responses and notifications to standard output.

Important rules include:

  • each message is separated by a newline;
  • protocol messages are UTF-8 encoded;
  • stdout must contain only valid MCP messages;
  • informational and diagnostic logs belong on stderr;
  • closing the process streams participates in connection shutdown.

A Go MCP server can be launched with an SDK transport:

if err := server.Run(ctx, &mcp.StdioTransport{}); err != nil {
    // Handle termination without writing protocol-breaking output to stdout.
}

The CMS server used in this article logs through stderr:

logger := slog.New(
    slog.NewTextHandler(os.Stderr, nil),
)

This detail is operationally important. A harmless-looking fmt.Println("server started") writes to stdout and can corrupt the JSON-RPC stream.

stdio is a strong fit for:

  • local developer tools;
  • desktop applications;
  • commands that need access to local files or processes;
  • simple deployments where one host controls one server process.

It is not automatically insecure or obsolete. Its trust boundary and lifecycle are simply different from a remote service.

Streamable HTTP transport

The current remote transport is called Streamable HTTP. It replaced the older HTTP+SSE transport beginning with the 2025-03-26 revision.

A Streamable HTTP server exposes a single MCP endpoint that supports HTTP POST and GET:

  • clients use POST to send JSON-RPC messages;
  • a POST response may contain one JSON response or an SSE stream;
  • GET can establish an SSE stream for server-to-client messages;
  • sessions may be identified with MCP-Session-Id;
  • SSE event IDs can support resumption and redelivery.

SSE is therefore an optional streaming mechanism within Streamable HTTP, not a separate data protocol and not something every response must use.

Remote deployment creates additional security requirements. The transport specification requires servers to validate the Origin header when present and reject invalid origins with HTTP 403. Local HTTP servers should bind to localhost rather than all interfaces, and remote services need appropriate authentication and authorization.

stdio versus Streamable HTTP

Dimension stdio Streamable HTTP
Server lifecycle Usually launched by the client Independently deployed service
Typical location Same machine Local or remote
Typical clients One client per process Multiple clients
Message carrier stdin and stdout HTTP POST/GET, optionally SSE
Logging stderr Application logging or MCP logging
Authentication Often relies on the local boundary Commonly OAuth or bearer credentials
Deployment complexity Lower Higher
Common use Local tools and commands Shared services and SaaS integrations

Neither transport changes what a tool or resource means. The same MCP methods can be carried over either transport.

Tools: model-controlled actions

Tools are executable capabilities exposed by a server. An AI application discovers them through tools/list and invokes one through tools/call.

A tool definition typically includes:

  • a programmatic name;
  • a description that tells the model when and how to use it;
  • an input JSON Schema;
  • optionally an output schema;
  • optional behavioral annotations.

The MCP tools specification describes tools as model-controlled. That does not mean the model receives unlimited authority. The host remains responsible for permissions, user confirmation, policy, and presentation.

The CMS MCP server registers read-only and write tools:

mcp.AddTool(server, &mcp.Tool{
    Name:        "cms.article.list",
    Description: "List CMS articles for one locale.",
    Annotations: readOnlyAnnotations,
}, listArticles)

mcp.AddTool(server, &mcp.Tool{
    Name:        "cms.article.create_draft",
    Description: "Create one CMS article as a draft.",
    Annotations: writeAnnotations,
}, createDraft)

Annotations provide hints such as whether a tool is read-only, destructive, or idempotent. They improve the host's ability to present risk and choose approval behavior, but they are not a substitute for server-side authorization and validation.

The example server also gives write operations stable idempotency identifiers when possible. That protects the downstream CMS from accidental duplicate writes if an operation is retried.

Resources: application-controlled context

Resources expose contextual data identified by URIs. Clients can discover resources with resources/list and read one with resources/read.

A resource has metadata such as:

  • URI;
  • human-readable name;
  • description;
  • MIME type;
  • optional annotations.

Resources are application-controlled: the host decides when to read them and how to use their contents. Exposing a resource does not guarantee that its full content is automatically inserted into every model request.

The CMS server exposes fixed resources:

cms://site/health
cms://locales
cms://taxonomy

It also exposes resource templates:

cms://taxonomy/categories/{locale}
cms://articles/{article_id}/translations/{locale}

Templates allow the client to construct parameterized URIs without registering every possible article or locale as a separate static resource.

This design also demonstrates why clients should discover more than tools. Listing tools alone would reveal locale create and update actions but would not reveal cms://locales, the read-only resource that returns configured locales.

Resources should still be treated as untrusted data. An article body, database record, URL, or search query might contain text that resembles instructions. The server and host should preserve the distinction between data returned by a resource and trusted control instructions.

Prompts: user-controlled interaction templates

Prompts are reusable templates exposed by an MCP server. Clients discover them through prompts/list and retrieve a selected prompt with prompts/get.

A prompt may define arguments and return one or more messages containing text, images, audio, or embedded resources. Prompts are generally user-controlled: an application may present them as commands, workflow choices, or menu items.

The CMS server exposes prompts such as:

cms.draft_from_brief
cms.pre_publish_review
cms.weekly_content_review

For example, cms.pre_publish_review returns instructions that tell the host to:

  1. read a specific article translation resource;
  2. call the preview tool;
  3. report blocking checks and warnings;
  4. show the exact article and locale;
  5. wait for explicit user confirmation before publishing.

That prompt coordinates resources and tools, but it does not publish anything by itself.

An MCP prompt is also not necessarily a system prompt. It is a server-provided message template that the host can retrieve and incorporate according to its user experience and security model. See the official prompt specification.

A real Go CMS MCP architecture

The example application has two different communication boundaries:

AI application / MCP host
          鈹?              鈹?stdio carrying JSON-RPC MCP messages
          鈻?        cms-operator v0.2.0
      鈹溾攢鈹€ tools
      鈹溾攢鈹€ resources
      鈹斺攢鈹€ prompts
          鈹?              鈹?HTTPS with a service token
          鈻?          CMS backend
          鈹溾攢鈹€ database and Redis
          鈹斺攢鈹€ Google Search Console integration

The HTTPS connection between cms-operator and the CMS backend is an ordinary application API call. It is not MCP Streamable HTTP. The MCP transport between the host and cms-operator is stdio.

The server constructor keeps the MCP adapter separate from business dependencies:

type Dependencies struct {
    Site          SiteReader
    Locales       LocaleService
    Articles      ArticleService
    Categories    CategoryService
    Tags          TagService
    SearchConsole SearchConsoleService
}

func New(deps Dependencies, logger *slog.Logger) *mcp.Server {
    server := mcp.NewServer(
        &mcp.Implementation{
            Name:    "cms-operator",
            Version: "0.2.0",
        },
        &mcp.ServerOptions{
            Logger: logger,
        },
    )

    addResources(server, deps)
    addPrompts(server)
    registerArticleTools(server, deps.Articles)
    registerCategoryTools(server, deps.Categories)
    return server
}

This architecture has several useful properties:

  • MCP transport and protocol concerns stay in the adapter;
  • CMS business operations remain behind interfaces;
  • tools can be tested using in-memory transports;
  • read-only context can be modeled as resources;
  • repeatable editorial workflows can be modeled as prompts;
  • downstream authentication is separate from MCP capability discovery.

Choosing between a tool, resource, and prompt

Use a tool when the server must perform an operation or dynamic computation:

  • publish a CMS article;
  • create an issue;
  • execute a search;
  • update a database record.

Use a resource when the server exposes identifiable context:

  • an article translation;
  • a repository file;
  • a database schema;
  • a locale list;
  • application health information.

Use a prompt when the server provides a reusable way to structure an interaction:

  • draft an article from an editorial brief;
  • conduct a pre-publication review;
  • investigate an incident using a known sequence;
  • summarize a weekly content inventory.

Some workflows legitimately use all three. A pre-publication workflow may load an article resource, retrieve a review prompt, call a validation tool, and only later call a publishing tool after user approval.

Common MCP misconceptions

"MCP is just tool calling"

Tool calling is one part of MCP. MCP also defines discovery, lifecycle, version negotiation, resources, prompts, notifications, and client capabilities.

"Resources are automatically sent to the model"

A resource is available to the client. The host decides whether to read it and how much of it should enter model context.

"Prompts are mandatory system instructions"

Prompts are reusable server-provided templates. The host and user control whether they are selected and how they are incorporated.

"HTTP and stdio use different MCP messages"

They use the same MCP data-layer semantics. Their framing, connection management, authentication, and deployment models differ.

"Streamable HTTP is the same as the old HTTP+SSE transport"

Streamable HTTP replaced HTTP+SSE. It uses a single MCP endpoint, HTTP POST and GET, and optional SSE streaming. Backward compatibility is possible, but the transports should not be treated as identical.

"A JSON-RPC feature is automatically an MCP feature"

MCP defines how JSON-RPC is used. Always follow the negotiated MCP revision and capabilities.

Security and production considerations

A useful MCP server needs more than correct message schemas.

  • Validate all tool inputs on the server.
  • Enforce authorization independently of tool descriptions and annotations.
  • Treat resource content, tool output, URLs, and external API data as untrusted.
  • Require confirmation for consequential writes.
  • Add timeouts and cancellation support.
  • Design writes for safe retries and idempotency.
  • Keep stdio logs on stderr.
  • Validate Origin for Streamable HTTP.
  • Bind local HTTP servers to localhost unless remote access is intentional.
  • Do not forward a client's token to unrelated downstream services.
  • Return useful tool execution errors without leaking secrets.

For debugging, the MCP Inspector can connect to stdio or Streamable HTTP servers, list primitives, invoke tools and prompts, read resources, and observe notifications.

Final mental model

MCP becomes much easier to reason about when each responsibility stays in its own layer:

  1. JSON-RPC 2.0 provides requests, responses, errors, notifications, and correlation IDs.
  2. MCP lifecycle and capabilities define initialization, version agreement, and which features may be used.
  3. Tools, resources, and prompts define actions, contextual data, and reusable interaction templates.
  4. stdio or Streamable HTTP carries the messages between client and server.
  5. The host's security and product layer decides permissions, user confirmation, context use, and model behavior.

The CMS example follows that separation: the MCP host talks to a local Go server over stdio; the server exposes editorial tools, CMS resources, and review prompts; and the adapter calls the existing CMS backend over authenticated HTTPS.

Once these boundaries are clear, MCP is no longer a collection of AI-specific terms. It is a structured protocol for connecting AI applications to capabilities and context without coupling every integration to one model or one host.