I am in Cursor. The host is the IDE. It opens one MCP client per server: Atlassian, a job search catalog, Chrome DevTools. Same chat. Three dedicated connections. The model never talks JSON-RPC. The client does.
I am in Cursor. I ask it to pull a Jira issue, search a public job catalog, and open a local file. One chat. Three backends. If I call that "using an MCP client," I am already wrong. Cursor is the host . It opens one client per server . The model never speaks JSON-RPC. The client does. That split is the whole protocol. Miss it and you ship a REST wrapper with extra ceremony, or you treat a server as if it were a chatbot. I have done both. Not a great look. Three mix-ups in one Cursor chat Same session. Same user. Three different roles. Anyone who has wired more than one MCP server already knows the difference, even if the names are still fuzzy. Host is not the client. A server is not a chatbot. A catalog is not your entire OpenAPI. What MCP actually is The official architecture is blunt about scope. MCP is a protocol for exchanging context between an AI application and programs that can supply tools or data. It does not tell you how to prompt. It does not tell you how to store memory. It does not replace your agent loop. USB-C slogans are cute. The useful sentence is smaller: JSON-RPC 2.0, two layers, three participants. The host is the AI application the human sees. Cursor, Claude Desktop, a custom Spring Boot agent if you write one. The host creates an MCP client for each server it connects to. Each client keeps a dedicated connection. The MCP server is the program that serves context. Local or remote. STDIO or HTTP. "Server" here means the process that answers, not "the box in the data center." One host. One client per server. The user never talks to the server directly. Two layers sit under that picture. The data layer is the JSON-RPC contract: discovery, tools, resources, prompts, notifications. The transport layer is how the bytes move: STDIO on the same machine, or Streamable HTTP when the server lives elsewhere. Same messages either way. I am writing against protocol version 2026-07-28 . That date matters. The old initialize handshake and Mcp-Session-Id are gone. Every request carries version and capabilities in _meta . Servers must implement server/discover . If your mental model is still "open a session, then chat," update it. The server: tools, resources, prompts If I am writing a server, I am not writing a conversation. I am advertising primitives. Three of them, per the spec. Tools are functions the host may invoke. tools/list returns names, descriptions, and a JSON Schema for arguments. tools/call runs one. This is where side effects live: search a catalog, fetch a Jira issue, write a file. Descriptions are the product. A vague "searches stuff" tool will get called for the wrong job, then you will blame the model. Resources are readable context. Schema dumps, a README, an OpenAPI document. resources/list and resources/read . No action. If you only needed to stuff a file into the prompt, this is the primitive. A tool that "returns the schema" is usually a resource wearing a costume. Prompts are reusable templates the host can fetch and drop into the model. Review checklists. Few-shot examples. prompts/list and prompts/get . Optional. I use them when the server knows a workflow better than the host does. List first. Then call, read, or get. Dynamic catalogs are allowed. A bookstore-shaped example, because I keep using bookstores. A catalog server might expose search_titles as a tool, schema://inventory as a resource, and a shelf_talk prompt with two good answers and one refusal. The host decides when to pull which. The server does not narrate. Do not wrap an entire public API. Pick the two or three capabilities an agent would actually reason over. Auth, tenant scope, and PII redaction belong in front of the tool, not inside the model. I have seen teams expose "the microservice" as forty tools and then wonder why the agent loops. That is a catalog problem, not an MCP problem. The client, and the host that owns it The client docs are short on purpose. The client is not the product. The host is. The client is the protocol adapter: one connection, one server, request/response, optional listen stream. What the client offers back is the part people skip. Servers can ask the human for more data. That feature is elicitation . In 2026-07-28 it rides Multi Round-Trip Requests. The server does not push a new RPC over a session. It returns an InputRequiredResult with elicitation/create inside. The client shows a form, or opens a URL, then retries the original tools/call with inputResponses . Form mode is for structured fields the client can render. URL mode is for secrets and OAuth. Passwords do not go through the LLM context. If your server is asking for an API key in form mode, stop. Two older client primitives are deprecated in this spec version: roots (filesystem boundaries that were advisory anyway) and sampling (server asking the client's model to complete a prompt). New servers should pass paths as tool arguments and call an LLM themselves if they need one. I would not start a 2026 server on sampling. The server pauses. The client gathers. Then the same call runs again. No session id required. How a call runs after 2026-07-28 Walk the happy path. Host wants to know what a job-catalog server can do. Client sends server/discover . Every request, including this one, carries protocol version and client capabilities in _meta . The server answers with supported versions, its own capabilities, and identity. Discovery is optional in the sense that you may skip it and eat a version error. I still call it once and cache the result. The spec lets list responses carry ttlMs and cacheScope for that reason. { "jsonrpc": "2.0", "id": 1, "method": "server/discover", "params": { "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": { "name": "cursor-host", "version": "1.0.0" }, "io.modelcontextprotocol/clientCapabilities": { "elicitation": {} } } } } Then tools/list . The interesting fields are name , description , and inputSchema . The host federates lists from every client and shows the model a combined toolbox. Progressive discovery exists for hosts that connect to many servers. I would use it before dumping eighty tools into one prompt. Then the model proposes a call. The host's client turns that into JSON-RPC. Not the model. The model never sees the wire format unless you are debugging. { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "search_jobs", "arguments": { "query": "Java Spring Boot Costa Rica", "limit": 5 }, "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": { "elicitation": {} } } } } The model proposes arguments. The client ships JSON-RPC. The server returns data, not prose. On Streamable HTTP, the same call also wants Mcp-Method and Mcp-Name headers so a gateway can route without parsing the body. That is a 2026-07-28 change. If you are writing a Java gateway, read those headers first. Notifications are opt-in. The client opens subscriptions/listen and names what it cares about, for example tool-list changes. Progress for a long call rides the response stream of that call, not the listen stream. I would not build a custom WebSocket "MCP-ish" channel. The spec already has a pipe. STDIO vs Streamable HTTP STDIO is the laptop path. The host spawns a process and talks on stdin/stdout. Filesystem servers, git helpers, a small Java CLI that wraps one read API. Fast. No TLS story because there is no network. Typical cardinality is one client. Streamable HTTP is the service path. POST for requests. Optional SSE when you need a stream. Bearer tokens or OAuth, as MCP recommends. Many clients can hit one deployment. Because the 2026 core is stateless, a request can land on any instance behind a load balancer if you stopped storing session ids. I...