What MCP defines — three message kinds and the tool contract
In one line
MCP (Model Context Protocol) is an open protocol that connects LLM applications to external tools and data. Messages are JSON-RPC 2.0, connections are stateful, and both sides negotiate capabilities. Those three sentences are everything the specification overview lists under "Key Details"; the rest is convention built on top of them.
Why this was needed
Build one agent and tools get attached to it: a tool that reads a database, a tool that opens files, a tool that calls an internal API. At first you write those tools as three functions, attach descriptions for the model, and you are done. The trouble starts when you build a second agent app, or when you borrow another team's tools. The tool names, arguments, result formats, error handling and approval procedures have to be decided again for every app, and the tool side ends up with different glue code for every app. N apps and M tools produce N×M adapters.
The specification compares this problem to the Language Server Protocol (LSP). Just as LSP ended the era when every editor built its own language support by replacing it with one contract between editor and language server, MCP tries to replace the same mess with one contract between LLM app and tool server. So what the specification defines is not the behavior of the model but the shape of the messages: who speaks first, what must be answered, and how failure is reported.
There are three roles. The host is the LLM app that initiates connections, a client is a connector inside that host that attaches 1:1 to a single server, and the server is the side that provides context and capabilities. A server can offer three things: resources (data to read), prompts (templates), and the subject of this course, tools (functions the model can execute).
How it works
There are three kinds of messages. The base protocol defines requests, responses and notifications. A request carries an id and a method; a response carries the same id with only one of result or error. A notification has no id, and the receiver must not reply to it (MUST NOT). The original JSON-RPC 2.0 text allows null as an id, but MCP goes a step further: it forbids null, and it also forbids reusing the same id within a session.
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "count_orders", "arguments": {"status": "paid"}}}
A connection has an order. The lifecycle defines three phases: initialization, operation and shutdown. The client sends an initialize request containing the protocol version, capabilities and implementation information it supports, and the server answers with its own version, its capabilities (tools, resources, prompts, logging …) and serverInfo. If the server supports that version it returns the same one; otherwise it returns another version it knows, and the client should disconnect if it does not know that version (SHOULD). Only after the client then sends the notifications/initialized notification does the operation phase begin. Before initialization, the rule is that neither side sends anything except requests like ping.
Tools come down to two methods. In the tools specification, tools/list returns the list of tools, and each tool has a name, a description and an inputSchema (JSON Schema). The model reads this schema and builds the arguments. tools/call takes a name and arguments and returns a content array (text, images, audio, resource links). One distinction matters here: there are two kinds of errors. A problem with the request itself, such as an unknown tool name or bad arguments, is answered with a JSON-RPC error (the specification's example uses -32602), while a failure that happens while the tool is running (an API failure, a business-rule violation) is answered inside the result with isError: true and explanatory text. The reason is that the model must be able to read the latter and try again.
There are two transports. In the stdio transport from the transports specification, the client launches the server as a child process, writes requests to its stdin and reads responses from its stdout. Messages are separated by newlines and must not contain newlines themselves; the server must not write anything other than MCP messages to stdout (MUST NOT), and it may write logs to stderr. Streamable HTTP is a method in which a single endpoint accepts POST and GET and, when needed, streams several messages over SSE. When you run it locally, the specification insists that you validate the Origin header and bind only to 127.0.0.1, to keep a web page in a browser from controlling your local MCP server through DNS rebinding.
What it looks like in the field
The incident in this course's title happens like this. Someone built an MCP server wrapping an internal DB, with a single tool, run_sql. The agent received "clean up the test data for me", generated DROP TABLE orders and sent it, and the server executed it. The model is not at fault. The specification defines tools as chosen by the model (model-controlled), so there should always be a place where a human can deny the call (SHOULD), and the server must validate every input, implement access control and rate-limit calls (MUST). The cause was that none of those sentences existed anywhere in the code.
When you look at an MCP server in the field, check three things first. How narrow are the tools that tools/list returns (a catch-all tool is an accident waiting to happen)? Do destructive tools have a confirmation step? And stdout hygiene: a single debug print line can kill the client's JSON parser and show up as "the server does not respond", which happens often in practice. The three labs in this course cover those three, in that order.
What the next quiz checks
It asks about the distinction between the three message kinds (especially why a notification must not be answered), the order of initialize and version negotiation, the distinction between the two kinds of errors, and the roles of stdout and stderr in stdio. All of it is in this reading.