Model Context Protocol (MCP) · 18 min read

Introduction to MCP

Understand the protocol primitives that let AI applications discover and use external capabilities.

MCP host, client, and server components connected through the protocol. Source: Wikimedia Commons · MCP developers · MIT License

The protocol problem

An AI application often needs calendars, files, databases, search, or internal services. Without a shared interface, each host must implement a separate integration, discover its capabilities differently, and translate errors by hand. MCP defines a common conversation between a host, a client connection, and a server. The benefit is portability: a capability can be exposed once and connected to several compatible hosts. The protocol does not make an integration safe automatically. Your application still owns identity, authorization, validation, approval, observability, and the business rules that decide whether an action is appropriate.

Host, client, and server

The host is the user-facing AI application and remains responsible for the overall interaction. It creates an MCP client for each server connection, while the server owns the tools and resources it exposes. A client is therefore a protocol boundary, not usually the business system itself. Keeping these roles distinct makes security reviews clearer: the host decides which servers are trusted, the client negotiates and transports messages, and the server validates and executes requests. Never assume that because a model suggested an action, the host or server has approved it.

Minimal MCP tool call flowpython
import json

request = {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "get_invoice_status",
        "arguments": {"invoice_id": "INV-1042"}
    }
}

print(json.dumps(request))

Tools, resources, and prompts

Tools represent callable operations such as searching orders or creating a ticket. Resources represent data that a client or model can read, such as a document, schema, or status view. Prompts are reusable interaction templates that can guide a task without being executable actions. Design each capability around its user outcome, not around a large internal API. A narrow tool like get_invoice_status is easier to authorize and explain than a generic run_query tool. Return structured results so the host can validate, display, cite, or transform them safely.

Discovery and capability negotiation

A client normally begins by establishing a connection and learning which protocol capabilities the server supports. It can then list available tools, resources, and prompts, inspect their names and input schemas, and decide what to make visible to the model. Treat every description and schema as untrusted metadata. A server should not be able to rewrite host policy through a persuasive description. The host should allowlist servers and capabilities, check versions, enforce size limits, and handle the case where a capability is missing or changed.

Request and result flow

A typical tool call starts with a user goal, model selection of an approved tool, client-side argument validation, server-side authorization, execution, and a structured result. The result may contain text, structured content, or references to resources. Keep the chain explicit in logs and user interfaces so people can see what was requested and what actually happened. Tool output is data, not automatically a new instruction. Delimit it in the model context and prevent returned content from silently changing permissions or triggering another privileged operation.

Errors, cancellation, and limits

Reliable MCP integrations expect ordinary failure. A server may be offline, a request may exceed a deadline, a tool may reject an argument, or a user may cancel while work is running. Return stable error categories and safe messages rather than stack traces or secrets. Set timeouts, maximum result sizes, concurrency limits, and budgets for expensive operations. Retry only operations that are demonstrably safe and idempotent. Cancellation should stop downstream work where possible, because abandoned requests can otherwise continue changing external state.

A practical first integration

Start with a read-only server for one small domain, such as looking up public documentation. Write a capability inventory, threat model, input and output schemas, approval policy, and evaluation cases before adding writes. Test discovery, valid calls, malformed arguments, unavailable servers, oversized results, prompt injection in returned data, and permission failures. Observe latency and logs with realistic requests. Once the boundary is stable, add one narrowly scoped mutation behind explicit confirmation. MCP becomes useful when its flexibility is paired with deliberately boring controls.