← Back to list

Model Context Protocol: A Complete Introduction

In my previous article, I discussed integrating enterprise applications with AI-powered LLMs, leveraging the Java language as the medium to…

Satyananda Sahu (Satya) · 2026-07-19 10:37 · 26 claps · 24.5 min read
#software-development #software-engineering #software-architecture #ai #manager
Open on Medium ↗
Wiki topics: LLM · Large Language Models RAG · RAG & Retrieval AI · AI · General 🏛️ · Architecture

Model Context Protocol: A Complete Introduction

In my previous article, I discussed integrating enterprise applications with AI-powered LLMs, leveraging the Java language as the medium to unlock their benefits. In this piece, I will highlight how LLMs interact with external systems through the Model Context Protocol (MCP). If you haven’t read my previous article yet, here’s the link to it, along with links to all my other AI-related articles.

Model Context Protocol (MCP)

A protocol is a defined set of rules that governs how a system operates in a systematic way, such as how systems interact, to ensure consistency, reliability and interoperability.

When it comes to the Model Context Protocol (MCP), it is an open standard designed to enable seamless integration between LLM applications and external data sources or tools. Whether you’re building an AI-powered IDE, enhancing a chat interface, or designing custom AI workflows, MCP provides a standardized framework that connects LLMs with the contextual information they need. The Model Context Protocol (MCP) was initially proposed and formalized by Anthropic and adopted as a standard or contract.

This specification defines the authoritative protocol requirements, based on the TypeScript schema in schema.ts.

Why is a protocol needed? A protocol is essential because it defines a common set of rules that allow multiple resources to work together under a common standard. This ensures

  • processes run systematically,
  • delivers better outcomes,
  • enables plug and play functionality and
  • helps reach goals efficiently.

Similarly, MCP is needed because

  • All resources connect through the same protocol, so upgrades happen once at the protocol level instead of across dozens of custom pipelines. It works much like an Enterprise Service Bus (ESB), which allows applications built in different languages to communicate seamlessly even without a shared native protocol.
  • MCP solved the problem of the N×M integration problem, before MCP, if you had N AI applications (ChatGPT, Claude, a custom agent, an IDE assistant) and M data sources/tools (Slack, GitHub, Postgres, Google Drive, a CRM), you needed a custom connector for every single pairing, N×M integrations, each maintained separately, each breaking independently when either side changed.

Example: Say you have 5 AI apps and 4 tools. Without a standard, that’s potentially 20 bespoke integrations. Add a 5th tool, and you’re not writing 1 new integration, you’re writing 5 (one per app). Every app vendor duplicates the same “how do I talk to Slack” logic.

MCP flattens this to N + M: each AI app implements the MCP client side once, each tool or data source implements the MCP server side once and any client can talk to any server. Add a new tool → build one server → every MCP-compatible app can use it immediately, no per-app work required.

Architecture overview: hosts, clients, servers

MCP uses a host-client-server model, per the official spec:

Host: The user‑facing AI application, such as Claude Desktop, an IDE like Cursor, or a custom agent, acts as the host process. It serves as the container and orchestrator:

  • managing the conversation,
  • enforcing security boundaries and
  • deciding what context is passed to the model.

The host coordinates multiple MCP servers while maintaining strict isolation between them. Within this setup, the host can create and manage multiple clients, with each client maintaining a one‑to‑one relationship with a specific server.

Client: Lives inside the host and manages exactly one connection to one server (1:1). If a host connects to x servers, it spins up x clients internally.

Server: An independent program that exposes tools, resources or prompts, for example, a GitHub MCP server or a Stack Overflow MCP server. Each server operates autonomously with a focused responsibility.

Example flow: When you open Claude Desktop (the host), it connects to both a GitHub server and a filesystem server. Behind the scenes, the host spins up two separate MCP clients, one dedicated to the GitHub server, the other dedicated to the filesystem server. Each connection is strictly isolated — the filesystem server never has access to your GitHub credentials and the GitHub server never sees your local filesystem data.

During initialization, capabilities are negotiated between the client and server. Servers declare what they support? such as resource subscriptions, tool functionality and prompt templates. Clients declare their own capabilities, like sampling support and notification handling. Once established, both sides are expected to honor these declarations consistently throughout the session.

MCP vs. traditional function calling / plugins

Traditional LLM function calling, such as raw OpenAI or Anthropic tool use, is tied to each individual application. As the software designer, you define the JSON schema for every function and implement the execution logic directly in your codebase. While this approach works, it isn’t portable or plug and play, if you switch applications or want the same tool available in another agent, you have to rebuild the integration from scratch.

Plugin ecosystems, such as the early ChatGPT plugins, were an important step toward standardization. However, they remained tightly bound to a single vendor’s platform and directory, limiting portability and broader adoption.

MCP differs in these key ways:

  • Protocol, not proprietary API: MCP is an open specification that any client or server can implement, rather than a closed plugin format tied to a single company.
  • Built‑in discovery: At runtime, a client can query a server for its available tools, resources and prompt templates (e.g., tools/list, resources/list, prompts/list). This eliminates the need for designers or developers to hardcode a fixed function list in advance.
  • Beyond function calls: Traditional function calling only covers actions. MCP also standardizes data access (resources such as read‑only files or database rows), reusable prompt templates and a reverse channel (sampling) where a server can request assistance from the client’s model.

Example: With plain function calling, if you want your agent to read GitHub issues, you write a get_github_issues() function, its schema and its execution logic, inside your app. With MCP, you instead point your app at an existing GitHub MCP server, discovery tells your app what’s available and no custom function code is needed in your app at all. The same server also works, unmodified, in a completely different AI app.

The “USB-C for AI” analogy

Before USB-C, every device had its own charger/cable, proprietary, not interchangeable. USB-C standardized the physical and logical interface, so any USB-C cable works with any USB-C device, regardless of manufacturer.

MCP aims to do the same for AI integrations:

  • a standardized “port” that any AI application can plug into, and
  • any tool/data source can expose itself through, without needing to know or care which specific AI app is on the other end.

Where the analogy holds well? Plug and Play Interoperability, reduced one-off wiring, “write once, connect anywhere”.

USB-C is a hardware/electrical standard with fixed connector geometry, MCP is a software/messaging protocol (JSON-RPC over stdio or HTTP), so the analogy is about interchangeability, not physical compatibility. Some writers prefer “the HTTP of AI tool-calling” for that reason, since it emphasizes it’s a communication protocol, not a plug shape.

JSON-RPC requires a communication protocol to transmit messages between client and server. Instead of relying on plain text or ad‑hoc formats, MCP establishes a standardized approach by adopting JSON-RPC 2.0 for remote procedure calls, wrapping these messages in HTTP or other supported transport protocols.

JSON-RPC itself is just a specification for remote procedure calls encoded in JSON. It doesn’t mandate how those messages are transported, that’s left to the implementation. Transport Options for JSON-RPC

  • HTTP: The most common transport. Clients send JSON-RPC requests as HTTP POSTs, servers reply with JSON responses.
  • WebSocket: Useful for bidirectional communication (e.g., live updates, streaming).
  • TCP/Raw Sockets: Some systems use plain TCP connections for efficiency.
  • Message Queues: JSON-RPC can be wrapped in systems like RabbitMQ or Kafka for distributed workflows.
  • Other protocols: In theory, any transport that can carry text messages (even serial ports) can be used.

Architects, designers, or developers can think of MCP much like SOAP for web services: a protocol that defines how messages are exchanged while adhering to WSDL and XML standards. Similarly, it can be compared to RAML in REST. The key distinction is that MCP is generic and universal as the contract is the same for everyone because it strictly follows JSON‑RPC 2.0. In contrast, WSDL and RAML are service specific contracts, meaning each consumer must adapt to the unique design of the service they want to use.

MCP And JSON-RPC 2.0

JSON‑RPC 2.0 has become the baseline that MCP providers and implementers follow. As by this time you all might have understood MCP provides a standardized way for applications to

  • Share contextual information with language models,
  • Expose tools and capabilities to AI systems and
  • Build composable integrations and workflows

MCP uses JSON-RPC 2.0 for all client↔server communication. In client‑server architecture, communication always involves a request, a response and often errors or notifications, the same principle applies here as well. There are three message shapes:

  • Request,
  • Response (result or error), and
  • Notification.

Requests must include a string or integer ID, params are optional and unlike base JSON-RPC the ID must not be null and must not have been reused in the same session. Notifications are structurally identical to requests but omit the id field, so the receiver processes them without replying.

Model Context Protocol (MCP) — JSON Structure Reference

Base Message Shapes

  • Base message shapes: These are the standard “envelopes” that wrap every MCP message.
  • JSON‑RPC foundation: MCP is built on JSON‑RPC 2.0, a widely adopted protocol for remote procedure calls using JSON.
  • Tooling compatibility: Because MCP traffic follows JSON‑RPC exactly, existing JSON‑RPC libraries, debuggers and logging tools can parse and understand MCP messages without modification.
  • Predictable structure: In practice, MCP messages always follow the same shapes, Request, Response or Notification, making them consistent and easy to work with.

Once you know these three shapes (Request, Response, Notification), you can read any MCP exchange because every feature in the protocol (tools, resources, prompts, sampling, etc.) is just a specific method name and params/result payload layered on top of this envelope. In the following section, I will share JSON messages that illustrate MCP features

Request Message: A Request Message is a request sent to an API, service, or another party to perform an operation with the expectation of a reply. It can originate from either the client or the server to initiate an operation or the interaction, e.g. “list your tools”, “read this resource”, “call this tool”.

⦿ Request Message json

{
  "jsonrpc": "2.0",
  "id": "string | number",
  "method": "string",
  "params": {
    "...": "optional, object of named parameters"
  }
}

○ "id" is required and MUST be a string or number (never "null" in MCP). 
○ "id" must not be reused within the same session.

Response Message: A Response Message is the reply sent back when a requested operation completes. A response contains either result or error, never both.

Successful Response: The reply sent back when a requested operation completes without error. It carries the data, the method requested for and the service provider promised to return (a tool list, a resource’s contents, a tool’s output, etc.) inside “result”. The “id” must echo the request’s “id” so the caller can match it back up, this matters because MCP connections are often async, with several requests in flight at once.

⦿ Successful Response Message json

{
  "jsonrpc": "2.0",
  "id": "same id as the request",
  "result": {
    "...": "any JSON object"
  }
}

Error Response: The reply sent back when a requested operation fails. Lets the caller distinguish “it worked” from “it didn’t” and why -

  • “code” gives a machine-readable category,
  • “message” a human-readable summary, and
  • “data” optional structured detail (e.g. which field was missing).

This is what lets an LLM client decide whether to retry, ask the user or fall back to something else.

⦿ Error Response Message json

{
  "jsonrpc": "2.0",
  "id": "same id as the request",
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": { "...": "optional extra info" }
  }
}

Notification: A one-way message, structurally identical to a request, but with no “id”. This is used for things where the sender doesn’t need (or want to wait for) an acknowledgment, such as progress updates, log lines, the tool list changed or cancellation signals. Notifications are lightweight, non‑blocking, and well‑suited for high‑frequency or best‑effort updates.

⦿ Notification json

{
  "jsonrpc": "2.0",
  "method": "string",
  "params": { "...": "optional" }
}

○ No "id" field at all — this is what makes it a notification rather than
a request. The receiver executes it silently and must not reply.

Standard JSON-RPC error codes: A shared vocabulary of failure ensures that every MCP client-server pair interprets errors consistently, for example, distinguishing between ‘invalid parameters’ and ‘Invalid Request’ regardless of implementation language. These error codes function much like HTTP status codes, providing standardized meanings across systems. The defined codes are as follows:

  • -32700: Parse error (invalid JSON)
  • -32600: Invalid Request
  • -32601: Method not found
  • -32602: Invalid params
  • -32603: Internal error
  • -32000 to -32099: Server/implementation/user defined errors

Lifecycle: “initialize”

Lifecycle initialize is the handshake that opens every MCP session, always the very first exchange on a new connection.

Functionality: It does all these things at once

  • negotiates a shared protocolVersion so both sides know which spec revision to speak,
  • lets each side declare its capabilities (does the server support resources? prompts? does the client support sampling?), and
  • exchanges basic identity info (“clientInfo”/”serverInfo”) for logging and debugging.

Why it matters? Nothing else in the protocol should be called before this completes, a client shouldn’t call “tools/list” on a server that hasn’t confirmed it supports tools. The exchange has three part:

  • request,
  • response and
  • confirmation notification.

Client → Server request: Request for the negotiated version, tells the server exactly which protocol/version support and identifies itself.

⦿ Client → Server request json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "tools": {},
      "resources": { "subscribe": true },
      "prompts": {},
      "logging": {}
    },
    "clientInfo": {
      "name": "ExampleClient",
      "version": "1.0.0"
    }
  }
}

Server → Client response: confirms the negotiated version, tells the client exactly which features it can rely on (so the client doesn’t waste a call asking for prompts if the server has none) and identifies itself.

⦿ Server → Client Response json

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true, "listChanged": true },
      "prompts": { "listChanged": true }
    },
    "serverInfo": {
      "name": "ExampleServer",
      "version": "1.0.0"
    }
  }
}

Client → Server notification: a one-way “we’re good to go” signal that closes out the handshake, the server can now expect normal operational requests (“tools/call”, resources/read, etc.).

⦿ Client → Server notification json

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

Tools

MCP’s mechanism for exposing actions the server can take on the model’s behalf, functions like “search the web”, “execute the query”, “send an email”. Tools are the closest MCP concept to traditional function-calling/tool-use in LLM APIs.

Functionality: Two methods cover the whole lifecycle — “tools/list” (discovery: “what can you do?”) and “tools/call” (execution: “do this specific thing with these arguments”). Each tool advertises an “inputSchema” (JSON Schema) so the client/model knows exactly what arguments are valid before calling.

Why it matters? This is the primary way an LLM takes real world action through MCP, so getting the schema and error handling right is what makes tool calls reliable instead of assumptions or guesswork.

tools/list: A discovery request asking the server to enumerate every tool it currently exposes. The Use tools/list is typically called once at session start (or again if the server sends a “notifications/tools/list_changed”), so the client/model has an up-to-date menu of callable actions.

⦿ tools/list json

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

⦿ result Json

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get-weather",
        "description": "Get current weather for a city",
        "inputSchema": {
          "type": "object",
          "properties": {
            "city": { "type": "string" },
            "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
          },
          "required": ["city"]
        }
      }
    ]
  }
}

tools/call: A request to actually execute a named tool with a specific set of arguments. The use of “name” must match one of the tools returned by “tools/list” and “arguments” must satisfy that tool’s “inputSchema”. The result comes back as a “content” array (text, images, etc.) rather than a raw value, so a single tool can return mixed media.

⦿ tools/call json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get-weather",
    "arguments": {
      "city": "Washington D.C.",
      "unit": "fahrenheit"
    }
  }
}

⦿ result json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "Current temperature in Washington D.C. 
is 80°F" }
    ],
    "isError": false
  }
}

Tool Execution Error: The call succeeded at the protocol level but the tool itself failed (e.g. city not found, API timeout). Tool execution errors are returned as a normal result object with isError: true. This allows the model to inspect the failure reason inside the content field and determine the appropriate response, whether to retry, prompt the user or attempt a different tool.

⦿ Tool Execution Error json

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "Error: city not found" }
    ],
    "isError": true
  }
}

➤ Protocol Level Error: The call itself was malformed or invalid (bad params, unknown tool name), so it never reached the tool’s own logic. This is returned as a proper JSON‑RPC error object (not a result), indicating a client‑side bug or misuse rather than a runtime failure of the tool. Typical cases include invalid parameters, unknown methods or attempts to call a non‑existent tool.

⦿ Protocol Level Error json

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": "Missing required argument: 'city'"
  }
}

Resources

MCP’s mechanism for exposing data the server has access to — files, database rows, API responses, log excerpts as read-only(ish) content the model can pull into context.

MCP provides a mechanism for exposing server‑accessible data, such as files, database rows, API responses or log excerpts, as read‑only (or near read‑only) content that the model can incorporate into its context.

Functionality:

  • resources/list is used to discover available items, returning URIs along with metadata.
  • resources/read then retrieves the actual content for a given URI.

This pattern mirrors the tools workflow, first discover, then act but applied to reading data rather than executing actions.

Why it matters? Resources let MCP share data the server already has, like files, database rows, API results or log snippets, with the model. The model can then use this information as context without having to put it directly into the prompt or run a full tool call.

resources/list: A discovery request enumerates the resources currently exposed by the server. Using resources/list, the client can obtain URIs along with metadata, enabling it to build a picker or menu of available files or data sources that the model (or user) may attach to the conversation.

⦿ resource/list json

{ 
  "jsonrpc": "2.0", 
  "id": 1, 
  "method": "resources/list", 
  "params": {} 
}

⦿ result json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resources": [
      {
        "uri": "file:///project/README.md",
        "name": "README.md",
        "mimeType": "text/markdown"
      }
    ]
  }
}

resources/read: A request to retrieve the actual content of a specific resource by its URI. The resources/read method is called once a resource has been selected (either from resources/list or a known URI), returning the content as text or binary depending on its MIME types, so it can be inserted into the model’s context.

⦿ resources/read json

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "resources/read",
  "params": { "uri": "file:///project/README.md" }
}

⦿ result json

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "contents": [
      {
        "uri": "file:///project/README.md",
        "mimeType": "text/markdown",
        "text": "# My Project\n..."
      }
    ]
  }
}

The complete list of MIME types is maintained by IANA (Internet Assigned Numbers Authority). There are thousands of registered types, organized into top‑level categories such as application, audio, font, image, message, model, multipart, text and video.

Here are the major groups:

  • Application: Formats like application/json, application/pdf, application/zip, application/msword.
  • Audio: audio/mpeg, audio/ogg, audio/aac, audio/wav.
  • Font: font/woff, font/woff2, font/ttf, font/otf.
  • Image: image/jpeg, image/png, image/gif, image/svg+xml, image/webp.
  • Message: message/rfc822 (email), message/http.
  • Model: model/obj, model/3mf, model/gltf+json.
  • Multipart: multipart/form-data, multipart/mixed, multipart/alternative.
  • Text: text/plain, text/html, text/css, text/csv, text/markdown.
  • Video: video/mp4, video/webm, video/ogg, video/x-msvideo.

Default types: If MIME types is unknown, text files default to text/plain and binary files default to application/octet-stream.

Prompts

MCP provides a way to expose reusable, parameterized prompt templates curated by the server author. Instead of the model or user writing prompts from scratch, these templates, such as “summarize this”, “review this code” or “draft a commit message”, can be invoked directly to streamline common tasks.

Functionality:

  • “prompts/list” discovers available templates and
  • “prompts/get” fills one in with specific “arguments” and returns the ready-to-use message(s).

Why it matters? This means a server can include ready‑made, well‑designed prompts along with its tools and resources. Instead of users writing their own prompts each time, they can pick from these curated ones, often through a slash‑command style interface, to get consistent, reliable results. For example, the client asks the server for available templates using prompts/list. The server responds with options like

  • /summarize → Summarize a given text.
  • /review-code → Analyze code for bugs or improvements.
  • /commit-message → Draft a commit message from a diff.

prompts/list: A discovery request lists the prompt templates a server makes available. Using prompts/list, the client can surface these templates in the UI, often as a menu or slash‑command options, so the user can easily pick one to attach to the conversation.

⦿ prompts/list json

{ 
  "jsonrpc": "2.0", 
  "id": 1, 
  "method": "prompts/list" 
}

prompts/get: A request to fetch a specific prompt template, filled in with the provided arguments. The prompts/get method returns fully formed messages that are ready to send to the model (or insert into its conversation). In other words, it’s the “fill in the blanks” step that happens after a template has been chosen.

⦿ prompts/get json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "prompts/get",
  "params": { "name": "summarize", "arguments": { "length": "short" } }
}

⦿ result json

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "description": "Summarization prompt",
    "messages": [
      {
        "role": "user",
        "content": { "type": "text", "text": "Summarize this in a short 
paragraph." }
      }
    ]
  }
}

Sampling — server asks client to run an LLM completion

The one direction where the server starts initiate the request instead of the client. The server asks the client’s LLM to generate a completion, essentially the reverse of the usual flow where the client asks the server to do something.

Functionality: Sometimes a server needs a bit of language‑model reasoning to do its job — for example, summarizing intermediate data before returning a result. Instead of embedding its own model or API key, the server asks the connected client to run the completion using sampling/createMessage. The client, which controls model access and ensures user consent and oversight, then returns the generated text.

Why it matters? This approach keeps model access and costs centralized with the client or user, instead of being duplicated across every server. It also ensures the client, with a human in the loop, stays in control of what the model generates on their behalf.

⦿sampling/create message json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sampling/createMessage",
  "params": {
    "messages": [
      { "role": "user", "content": { "type": "text", "text": "What is 
the capital of France?" } }
    ],
    "maxTokens": 100
  }
}

⦿ result json

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "role": "assistant",
    "content": { "type": "text", "text": "The capital of France is 
Paris." }
  }
}

Example: Server Asking Client’s LLM for Help

Scenario: A server is processing a large dataset and wants to return a clean summary to the user. It doesn’t have its own LLM or API key.

Step 1 — Server Request: The server sends a request to the client:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sampling/createMessage",
  "params": {
    "input": "Summarize the following intermediate results: [data here]"
  }
}

Step 2 — Client Action: The client runs this request through its connected LLM (which it controls) and generates a summary.

Step 3 — Client Response: The client returns the generated text to the server, for example:

{
  "output": "The dataset shows a steady increase in user activity over the
 past few months, with peak engagement in July."
}

Result: The server now has a concise summary it can include in its final response to the user, without needing its own model or credentials.

Notifications (no id, no response)

This refers to the full set of one way, asynchronous messages used in MCP for actions that don’t require a reply or where waiting for a response would be unnecessary or counterproductive.

Functionality: They cover four common needs

  • reporting progress on long-running work,
  • cancelling an in-flight operation,
  • streaming log/debug output and
  • announcing that a previously-listed set of items (tools, resources, prompts) has changed.

Why it matters? Because notifications don’t require a round‑trip response, they are lightweight enough to send frequently, for example, progress updates, without adding request/response overhead. They also let either side push information proactively, instead of forcing the other side to poll for it.

Progress update: A progress update shows how far a long running operation has advanced. When sent, the receiver can display a progress bar or status message to the user. The progressToken links the update back to the original request, so the client knows which task the update belongs to.

⦿ notifications/progress json

{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "operation-123",
    "progress": 75,
    "total": 100,
    "message": "Processing files..."
  }
}

Cancellation: A cancellation signal tells the system to stop a request that was previously sent. It’s used when a user cancels an action or when a timeout occurs. The requestId identifies which ongoing request should be aborted.

⦿ notifications/cancelled json

{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": {
    "requestId": "long-running-request-id",
    "reason": "User requested cancellation"
  }
}

Log message: A log message is a debug, info or warning line sent from the server to the client. It appears in developer consoles or debug panels and is meant purely for observability, not to change the model’s behavior.

⦿ notification/message json

{
  "jsonrpc": "2.0",
  "method": "notifications/message",
  "params": {
    "level": "info",
    "data": "Database connection established"
  }
}

List changed: A “list changed” signal announces that a previously fetched list, such as tools, resources or prompts is now outdated. When received, the client should call the corresponding /list method again to refresh its cache. For example, if the set of tools has changed, the client re‑invokes tools/list to get the updated list.

⦿ notification/tools/list_changed json

{ 
  "jsonrpc": "2.0", 
  "method": "notifications/tools/list_changed" 
}

Batching

A way to bundle multiple independent JSON-RPC messages into a single JSON array and send them in one network round trip.

Functionality: The server processes each request in the batch independently and returns an array of responses, order isn’t guaranteed to match, so the client must correlate results back to requests using each “id”.

Why it matters? This reduces round‑trip overhead when a client needs multiple items at once, for example, fetching both tools/list and resources/list during startup. It’s especially useful over higher‑latency connections, though not every transport or server is required to support it.

⦿ Multiple requests can be sent as a JSON array: Batching json

[

  { 
     "jsonrpc": "2.0", 
     "id": 1, 
     "method": "tools/list" 
  },
  { 
     "jsonrpc": "2.0", 
      "id": 2, 
      "method": "resources/list" 
  }
]

○ The server responds with an array of responses. Order is not guaranteed, 
match by id. Notifications in a batch produce no corresponding 
response entry.

Notes:

  • Message format: All MCP messages are UTF‑8 encoded JSON and must follow the JSON‑RPC 2.0 specification.
  • Parameters convention: In MCP, params must be a named object. While positional arrays are technically valid JSON‑RPC, they are not the convention here.
  • Source of truth: The canonical schema is defined in TypeScript within the official spec repository (modelcontextprotocol/modelcontextprotocol/schema/draft/schema.ts) and is also mirrored as JSON Schema.
  • Versioning: The protocol version referenced above (2025‑11‑25) reflects the spec current at the time of writing. Always check the official documentation for the latest version string.

Use Cases

Here’s a use-cases section with concrete examples for each.

Coding agents pulling in repo context

The problem: A coding agent that only sees the file you have open is blind to the rest of your codebase, conventions, related modules, open PRs, past issues, CI status.

How MCP fixes it: An MCP server for GitHub, GitLab, Sentry, or even a local filesystem makes repositories accessible in a standardized way, repo data is exposed as resources and repo actions are exposed as tools. This lets an agent fetch file contents, open issues, view recent commits or pull in PR review comments as context before writing code. It can also call tools to run tests, check CI status or open a PR, all without requiring the coding tool’s vendor to build custom integrations for each service.

Example: Nowadays, popular MCP servers include GitHub for code access and review, along with utility servers like Sequential Thinking and Context7 that enhance agent reasoning during development. A common workflow looks like this -

  • the agent receives a task,
  • discovers available tools through MCP,
  • executes in an isolated environment and iterates against real CI/CD systems — generating, testing and deploying code directly into the pipeline instead of relying on guesses.

Customer support bots querying CRM data

The problem: A support agent (human or AI) needs to flip between the CRM, the ticketing system and the knowledge base to answer one customer question, that context-switching is slow and error-prone.

How MCP fixes it: MCP allows a support bot to query CRM, ticketing and knowledge base platforms through a single, consistent interface. This gives the bot complete customer context, such as purchase history, account status and prior interactions, without needing separate, custom integrations for each system.

Example workflow: A customer can submit a request through any channel. The AI then pulls in the customer’s profile, purchase history and account status. A classifier sets the priority based on sentiment and SLA. The AI drafts a personalized response, while complex cases are routed to a human agent along with the full context gathered. In practice, this looks like:

  • a ticket arrives,
  • the agent connects to MCP servers for the ticketing platform, knowledge base and CRM,
  • queries account status, retrieves relevant documentation, drafts a reply and updates the ticket — all through the same protocol, regardless of which tools are behind it.

Research agents combining web search + internal docs

The problem: Good research answers need both current external information (news, papers, competitor data) and internal, private knowledge (internal wikis, past reports, proprietary databases), two very different kinds of sources that don’t normally share an interface.

How MCP fixes it: A research agent can maintain multiple MCP connections at once, for example, one to a web search server, another to an internal Notion, Confluence or SharePoint server and another to a database server. Instead of hardcoding separate pipelines for each source, the agent treats them all as resources it can draw into a single line of reasoning, combining search results, documentation and data seamlessly through the same protocol.

Example: This is exactly the kind of multi‑server composition MCP was built for. Research and analysis teams often connect several MCP servers at once, search or SEO tools, knowledge bases like Notion and databases such as Supabase or Snowflake. One important caveat to note

● every connected server adds its tool definitions into the model’s context window.

With 5–7 servers active, a significant portion of context can be consumed just by tool definitions. To manage this, some teams use a gateway that dynamically loads only the servers relevant to the current task, instead of keeping all of them attached simultaneously.

Knowledge Enhancer

USB-B was designed as a “universal” connector for peripherals like printers and scanners, but it never achieved the same ubiquity as USB-C. Here’s why only USB-C is considered truly universal today,

Why USB-C Is Called Universal?

  • One connector for everything: Works across laptops, phones, tablets, monitors, docking stations, and even cars.
  • Reversible design: No “upside down” problem, unlike USB-A or B.
  • Multi-functionality: Supports power delivery (up to 240W), data transfer (up to 80 Gbps with USB4), and video output (HDMI/DisplayPort over USB-C) — all through the same port.
  • Global adoption: It’s now mandated in regions like the EU for smartphones and laptops, making it a true standard. — Future-proof: Just like USB-C absorbed Thunderbolt and USB4, it keeps evolving without changing the physical connector.

Why USB-B Isn’t Universal?

  • Device scope: USB-B was mainly used for large peripherals (printers, scanners, external hard drives). Phones, laptops, and smaller electronics rarely adopted it.
  • Form factor: The connector is bulky and not suited for slim devices.
  • Variants: Multiple shapes existed (standard B, mini-B, micro-B), which fragmented compatibility.

USB-B was universal for a niche (peripherals), while USB-C is universal across the entire ecosystem of modern electronics.

Sequential Thinking and Context7

Sequential Thinking and Context7 are two specialized MCP servers designed to improve how AI agents reason and code. Sequential Thinking provides a structured scratchpad for step‑by‑step reasoning, while Context7 injects up‑to‑date, version‑specific documentation into the agent’s context so it writes code aligned with the actual libraries you use.

Sequential Thinking MCP Server: Gives AI models a transparent, structured way to think step by step.

How it works?

  • Exposes a tool called sequentialthinking.
  • Forces the model to break problems into numbered steps with explicit parameters (current thought, step number, total steps, revisions).
  • Each step is logged, inspectable, and auditable.

Why it matters?

  • Prevents compounding errors in multi‑step reasoning.
  • Provides auditability and self‑correction.
  • More reliable than “chain‑of‑thought” prompting hidden inside a system prompt.

Use cases: Complex coding tasks, debugging, mathematical reasoning, workflows where transparency and correctness are critical.

Context7 MCP Server: Supplies AI agents with real‑time, version‑specific documentation for libraries and frameworks.

How it works?

  • Maintained by Upstash.
  • Connects directly to official sources (GitHub repos, docs sites).
  • Resolves the exact library and version before retrieving docs.

Why it matters?

  • Prevents outdated or hallucinated code suggestions.
  • Ensures the agent writes code for React 19 if your project uses React 19, not React 13 or 18.
  • Eliminates “generic answers” based on stale training data.

Use cases: Modern web frameworks (Next.js, React, Pydantic, etc.), projects pinned to specific versions, enterprise coding environments.

Sequential Thinking is “Think step by step, visibly” whereas Context7 is “Use the right docs, at the right version”. Together, they make AI agents more reliable in reasoning and more accurate in coding. Here are the information of different MCP servers.

JSON RPC

JSON‑RPC (JavaScript Object Notation — Remote Procedure Call) is a lightweight, stateless protocol that allows applications to communicate by sending requests and receiving responses encoded in JSON. It’s widely used because JSON is simple, human‑readable, and language‑agnostic, making integration across different systems straightforward.

Core Concepts of JSON‑RPC

  • Remote procedure calls: A client invokes a method on a server as if it were a local function.
  • Message format: Every request and response is a JSON object with predictable fields.
  • Transport agnostic: JSON‑RPC doesn’t mandate HTTP or WebSocket, it can run over any transport layer.
  • Stateless: Each request is independent; the server doesn’t need to remember prior calls.

Example Request and Response

Request: { “jsonrpc”: “2.0”, “method”: “addition”, “params”: [46, 43], “id”: 1 }

Response: { “jsonrpc”: “2.0”, “result”: 89, “id”: 1 }

Why It Matters?

  • Interoperability: Works across languages and platforms.
  • Simplicity: Easy to parse and debug with existing JSON tools.
  • Extensibility: Supports notifications (no response expected) and batch requests (multiple calls in one message).

JSON‑RPC 2.0 is the modern, standardized version of the protocol, offering clearer message formats, structured error handling, and support for batch requests and notifications, all improvements over the simpler but less flexible JSON‑RPC 1.0.

JSON‑RPC 1.0 Request: { “method”: “addition”, “params”: [46, 43], “id”: 1 }

JSON‑RPC 2.0 Request: { “jsonrpc”: “2.0”, “method”: “addition”, “params”: {“augend”: 46, “addend”: 43}, “id”: 1}

Why 2.0 Is Preferred?

  • Standardization: The “jsonrpc”: “2.0” field makes version detection explicit.
  • Flexibility: Named parameters allow optional arguments and clearer semantics.
  • Efficiency: Batch requests reduce overhead by grouping multiple calls.
  • Robustness: Structured error objects improve debugging and interoperability.
  • Compatibility: Existing JSON‑RPC tooling (libraries, debuggers, loggers) can parse MCP traffic directly since MCP strictly follows JSON‑RPC 2.0.

Note: If you are interested in discussing this topic or learning more, you are welcome to connect with me anytime.

In this article, some content reflects my personal viewpoints while few are drawn from their respective sources. The Knowledge Enhancer section of this article includes information from internet sources, though not all of its content is derived from them. If you would like to learn more, you can visit the websites mentioned either alongside the content.

Mr. Satyananda Sahu ( Satya )

Originally published at https://www.linkedin.com. by Mr. Satyananda Sahu (Satya)


메타데이터
post_id
2626e8d7ae60
slug
model-context-protocol-a-complete-introduction-2626e8d7ae60
url
https://medium.com/@satyaa.sahu/model-context-protocol-a-complete-introduction-2626e8d7ae60
canonical_url
https://medium.com/@satyaa.sahu/model-context-protocol-a-complete-introduction-2626e8d7ae60
author_url
https://medium.com/@satyaa.sahu
status
ok
fetched_at
2026-07-29 01:50:13