Real-World Security Considerations for MCP

Why yet another protocol 

Every new protocol introduces complexities—onboarding new tools and SDKs, adapting to new processes, evaluating security and performance implications, ensuring compatibility with existing systems, and overcoming the learning curve. So when a new protocol shows up, the first question to ask is: is it really necessary? Let’s ask that about MCP—what problem is it trying to solve?

The current wave of agentic applications, which started with tools like ChatGPT, is powered by large language models (LLMs). As of now, ChatGPT and GitHub Copilot use OpenAI’s latest model, GPT-4o, trained on a vast amount of public and licensed data, with a training cutoff in mid-2024. Google Search and Workspace use Gemini 2.5, trained on diverse multimodal data with a knowledge cutoff in early 2025. These models enable capabilities like search and information retrieval, summarization, content creation, image processing, code generation, and translation. But LLMs have fundamental limitations.

  • They don’t have access to real-time or recent information beyond their training data. For instance, they can’t tell you about an election or earthquake that happened last week, or current weather warnings.
  • They’re trained on generic public data, so they can’t access private or sensitive information—like your health records or PayPal transactions.
  • And they don’t execute tools. ChatGPT can help you write the code for a simple React app, but it can’t scaffold the project and run the dev server. It can assist you to find the cheapest flight from Auckland to Colombo based on some past public records, but it can’t book it for you.

MCP is designed to solve these limitations. It bridges agentic applications powered by LLMs with real-world data and tools by defining a universal connectivity protocol. Well, the above limitations need to be addressed, but why do we need a new protocol for that? We have been developing secure and high-performing APIs for more than a decade, so why can't agentic-apps reuse them? We already have proven APIs for weather data, election results, airline reservations, and PayPal, so that is the next question we should find the answers to.

To start with giving you a short but correct answer, it’s absolutely possible to connect existing real-world data and tools with agentic applications to be used with LLMs using existing APIs—you really don’t need anything new. However, as an exercise, if you go through the APIs mentioned in the previous paragraphs, you would realize that they are based on various technologies such as REST, GraphQL, and gRPC, and use different transport mechanisms such as HTTP, WebSockets, Pub/Sub, and Message Queues. They also have different security requirements such as API keys, JWT, and OAuth2 tokens. They may all sound like they use JSON, but looking closely reveals that although they use JSON as the format, the structure or schemas are completely different. This means each and every agentic application has to develop an integration for each and every business API that is needed to connect at some point based on a user’s prompt.

As mentioned, this is absolutely possible—and we have been doing that so far using integration tools. We can call this static integration, which usually requires a developer to look at the available API documentation (which can be an OpenAPI spec or a wiki page), develop integration flows using a framework or integration tool, and finally plumb the application with the business API. Obviously, this takes time and cost—and most importantly—doesn’t fit with the pace of agentic-app development, which requires dynamic connectivity based on prompted tasks.


MCP solves this problem by defining a client-server role, a standard protocol format for client-server communication, and a connection lifecycle. You can think of MCP as a bridging intermediary between your agentic applications and real-world data and tools. Not so long ago, each mobile phone and device manufacturer used to come up with their own USB and other connectors—and finally, we are moving to accept USB-C as the universal connector. So you can think of MCP as the USB-C in the API world.


Just to sum up, as an application developer or architect, this is what MCP provides to you:

  • If you are developing agentic applications, you just need to worry about implementing MCP client-side capabilities without worrying about connecting to various real-time data and tool APIs that you need to connect to. Most of the time, this is a matter of adding an MCP client SDK into your applications.

  • If you are exposing a business API, you just need to worry about exposing your APIs as an MCP server—without worrying about how client applications would use these capabilities. You also don’t need to build the MCP server capabilities from scratch. Instead, you can build an MCP server that acts as a proxy to connect to the existing APIs. Or, if you are using an API gateway, this will soon become another API management feature available at the gateway.

How MCP works 

As we have looked at exactly the problem solved by MCP, in this section let’s focus on how MCP solves these problems.

First, MCP basically defines three distinct roles:

  • Hosts: Agent applications that create and manage client instances and their lifecycle. They’re responsible for handling client connections and applying security policies. Taking familiar examples, Claude Desktop, IDEs such as Cursor and Windsurf.

  • Clients: Connectors within the host application. Each client connects to a specific MCP server and maintains a 1:1 stateful session with that server. Clients are responsible for routing protocol messages in both directions — between the host and the server. A client is a tiny layer within the host.

  • Servers: Provide the context and capabilities via the MCP protocol by connecting real-world data and tools. Although we looked at remote use cases, these capabilities can well reside in the local environment (like accessing your file system or a local database), or they can offer remote access, similar to how the PayPal MCP Server communicates with PayPal APIs remotely.

In addition to these core capabilities, MCP also defines support for configuration, progress tracking, processing cancellation, error reporting, and logging—all to be used in client-server communication.

The following diagram illustrates how each of these roles interact with each other at runtime.


As per the MCP standard, each role provides a set of features:

MCP servers offer the following features to clients:

  • Resources: Context or data exposed by the MCP server that can be consumed by the agentic application (via the client) to make decisions, generate responses, or assist the user. For example, a calendar MCP server can expose the user's calendar events like meetings, reminders, or availability as resources.

  • Prompts: Templated messages or workflows designed by the MCP server to guide user interactions. These help structure how users ask for something or trigger a flow, reducing ambiguity and improving LLM output quality. For instance, a helpdesk MCP server, a prompt might look like- "Ask the user for the product name, issue type, and priority before creating a support ticket."

  • Tools: Functions exposed by the MCP server that the AI model can invoke programmatically to perform real-world actions. These are executable operations, not just data fetches. The tools exposed by GitHub MCP Server such as createPullRequest and mergePR are examples for the tools. 

MCP clients offer the following feature for the benefit of the MCP server:

  • Sampling: Allows the MCP server to initiate agentic behavior, often by interacting with the LLM recursively or running simulations. It's a server-initiated mechanism to request deeper reasoning or multiple response options.

Here is a screenshot of resources, prompts and tools exposed by GitHub MCP server. 


MCP is a stateful protocol, which means it needs to define a connection management process consisting of the following stages:

  • Initialization: Capability negotiation and protocol version agreement and establishment of the session. 
  • Operation: Normal protocol communication where client and server communicate between each other. 
  • Shutdown: Graceful termination of the connection, one party can terminate the connection and no special message for the termination. 


For client-server communication, MCP uses JSON-RPC, a specific implementation of the RPC concept that uses JSON as its data format for encoding requests and responses. It is a lightweight protocol designed for simplicity and ease of use.

The following diagram illustrates an MCP request message using the PayPal MCP Server as an example. Here is the response message received for the above request, again from the PayPal MCP Server.


MCP defines two standard transport mechanisms for client-server communication:

  • stdio: Communication over standard in and standard out
  • Streamable HTTP: This includes Server-Sent Events (SSE)

Among them, whenever possible, clients should use stdio transport.

MCP security

Now that we’ve looked at how MCP addresses the limitations of LLMs and defines a protocol that bridges LLMs with real-world data and tools, the next challenge to consider is security. Real-world resources—such as your health records or PayPal account—cannot be exposed to any application without proper security measures, regardless of whether it’s agentic or not. MCP defines an authorization specification that applies only to the HTTP transport and not to STDIO transport. However, the scope of the MCP auth spec is limited to the client-server interaction layer. In this section, we’ll go a bit further and explore how to secure end-to-end communication, beyond just client and server. For clarity, we’ll discuss the security scenarios for each transport separately, since the security threats and requirements differ significantly between HTTP and STDIO transports. Let’s start with STDIO.

Security when STDIO transport in use 

To connect to an MCP server via STDIO, when MCP configuration parameters are provided to the agentic application, it starts the underlying MCP server as a subprocess. This allows the application to write to the STDIN stream of the MCP server and receive messages from its STDOUT stream. Therefore, when STDIO is used, the MCP server always coexists on the same machine as the client application. The following diagram illustrates three scenarios in which an agentic application connects to an MCP server running locally via STDIO transport:

  • An agentic application connects to a Filesystem MCP server to perform file manipulations on the local file system.
  • An agentic application connects to a database MCP server such as PostgreSQL, using STDIO to interact with the local database.
  • An agentic application connects to a GitHub MCP server using STDIO.

What is common across these scenarios is that the MCP client uses STDIO to connect with the MCP servers. Because the communication is confined to the same local machine, the MCP auth specification does not need to be applied to secure communication between the client and server. As previously noted, this is possible because the MCP server is launched as a subprocess of the application, making it inherently secure. MCP STDIO transport is considered secure because communication occurs exclusively on the local system via standard I/O streams. Since data never leaves the local environment, it is protected from typical network threats like eavesdropping, interception, or man-in-the-middle attacks. The only way an attacker could compromise STDIO communication is by already having control of the local machine—meaning if the system is secure, the STDIO channel is secure by default.

Now, consider the first scenario where the MCP client connects to a Filesystem MCP server. In this case, the server can only access configured directories in the local file system—similar to how a text editor creates files—so the scope of communication remains entirely within the machine. As such, no additional security measures are necessary.


In the second scenario, the MCP client connects to a PostgreSQL MCP server, which in turn connects to a PostgreSQL instance running on a local network (but outside the host machine). From the perspective of MCP auth, communication between the client and server does not require additional security.


 However, the connection between the MCP server and the PostgreSQL instance does. As is typical, the database instance requires authentication. To accommodate this, the necessary credentials—like a username and password—can be provided via environment variables during the MCP server configuration. For instance, you can pass database credentials as environment variables, but the agentic application must handle them securely, avoiding plain-text exposure in configuration files. Generally, the following approaches can be used when connecting local network resources securely to an MCP server:

  • Username and password: Applications can connect to resources like MySQL using environment variables for authentication.
  • Service account credentials: Use dedicated accounts with restricted permissions, securely stored in a vault or secret manager.
  • Secure vault integration: Credentials are retrieved at runtime from secure services like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault and injected as environment variables.
  • Privileged Access Management (PAM): PAM tools dynamically generate credentials, handle rotation, and enforce just-in-time access, reducing the need for manual credential handling.

In the third scenario, where the MCP client connects to a locally running GitHub MCP server, the communication with the MCP server still occurs over STDIO, so no special security is required there. However, since the GitHub MCP server communicates with GitHub APIs over the Internet, external security must be considered. You’ll need to configure a GitHub personal access token (PAT)—a long-lived token that can be valid for days or months—and provide it as an environment variable during MCP server setup in environments like VS Code or Claude Desktop. In general, when connecting a local MCP server to cloud APIs via HTTP, you have to use one of the following credential types:

  • Long-lived tokens: The API keys and GitHub PAT can be considered as long-lived tokens as they are valid for several days or event months. These credentials need to be obtained though out of band channels such as cloud console and provided with MCP server confirmation.
  • Short-lived tokens: These tokens have very short lifetime usually less than 1 hour which means it’s not practical to obtain from out of band channels and provide them as confirmation settings. Instead the MCP server should be able to request and obtain the credentials dynamically at the runtime with the approval of the end user. OAuth2 access tokens or JWT tokens issued through custom login API can be considered as examples for this type of tokens. Given we are primarily focused on MCP auth spec in this post, we will not discuss further details on this. 

Security when HTTP transport in use 

Consider the following scenario, where we connect to the PayPal remote MCP server through Claude Desktop, which is the agentic application running on our local machine and provides MCP host and client capabilities. In this case, the communication between the MCP client and server occurs over HTTP via the internet, and this is where we must comply with the MCP auth specification.


To connect to the PayPal MCP server, you need to provide the agentic application—in this case, Claude Desktop—with a single parameter: the remote MCP server endpoint. This takes the following format:


According to the MCP auth spec, this endpoint must be secured and must not be accessible without an OAuth 2.1 access token. Let's now look at how an agentic application can obtain a valid access token to connect to the MCP server endpoint. Without this token, the MCP server will return an HTTP 401 Unauthorized error.The MCP auth specification mandates the use of the OAuth 2.1 draft standard for both MCP clients and servers. 

As we discussed earlier, traditional static integration models don’t suit agentic use cases well—they require dynamic integration at runtime. To address this, the MCP auth spec defines a clear process for obtaining a valid access token, starting from the MCP server endpoint as the only known input. The following diagram illustrates the various phases defined in the MCP auth spec, in addition to the standard protocol phase discussed earlier. We will explore each of these phases in the following section.


Server Metadata Discovery

As shown in the above diagram, the first phase is the server metadata discovery, and our objective is to discover exact metadata details about the server. Given the MCP server is secured using OAuth 2.1, we need to discover details such as OAuth2 authorize, token, and registration endpoints, supported grant types, and supported client authentication mechanisms, which are necessary when obtaining a valid access token. This metadata information can be retrieved from an endpoint known as the server metadata endpoint.

Before diving further into metadata discovery, it's important to highlight a significant change between the latest specification ( 2025-06-18 revision) and the previous (2025-03-16 revision) specification of the MCP authorization. In the 2025-03-16 revision, the OAuth2 resource server role—responsible for implementing MCP tools, prompts, and resources—was tightly coupled with the OAuth2 authorization server role, which handles issuing and managing access tokens. These two roles were merged into a single MCP server role. This design was widely seen as a drawback, especially within the identity community, for several key reasons:

  • Increased complexity for implementers: MCP server developers were required to implement and support several OAuth2 specifications in addition to the MCP protocol itself, adding both time and development overhead.

  • Limited interoperability: While OAuth2 authorization servers already exist as mature services and products, the tightly coupled design limited their effective use. Many small to mid-sized organizations would prefer to integrate existing OAuth2-compliant identity solutions rather than rebuild authentication and authorization capabilities from scratch.

  • Real-world identity requirements: Though MCP is based on the OAuth 2.1 draft, actual systems require far more than just access token issuance. OAuth 2.1 assumes the user is already authenticated. In enterprise settings, security demands extend to features such as multi-factor authentication (MFA), adaptive authentication, single sign-on (SSO), and more—all of which introduce significant complexity.

To address these concerns, the latest 2025-06-18 revision of the MCP authorization specification clearly decouples the OAuth2 resource server role (played by the MCP server) from the OAuth2 authorization server role. This separation provides much-needed flexibility:

  • Enterprises can still combine both roles into a single system if desired—common in large, commercial implementations.

  • Alternatively, implementers can keep them separate, enabling the use of existing OAuth2 infrastructure and purpose-built identity solutions as the authorization server. This approach is likely to be more appealing to the majority of commercial organizations.

Ultimately, this separation helps maintain a clean architecture by keeping identity, authentication, and authorization as distinct, independent layers—supporting scalability, modularity, and easier integration with modern security ecosystems.

The separation of roles in the latest MCP authorization specification (2025-06-18 revision) significantly impacts the metadata discovery phase. Unlike the previous version—which relied on a single-step, proprietary discovery mechanism—the updated specification adopts a two-step discovery process based on standardized OAuth 2.0 protocols.

Step 1: Resource Server Metadata Discovery

The protocol flow begins when the MCP client initiates a connection request to the remote MCP server URL, which is provided as a configuration parameter. At this stage, the client does not include any credentials in the request.

Since no credentials are provided, the MCP server—acting as a resource server in OAuth 2.0 terms—responds with an HTTP 401 Unauthorized status. Crucially, this response includes a WWW-Authenticate header with a resource_metadata field that points to the Resource Metadata URL within the MCP server.

Upon receiving this response, the client sends a request to the provided metadata URL. The MCP server then responds with a JSON-formatted metadata document known as the Protected Resource Metadata (PRM), which complies with the OAuth 2.0 Protected Resource Metadata specification—now a formal standard.

According to the latest MCP specification:

  • MCP servers must include their resource_metadata URL in the initial 401 Unauthorized response and must serve the PRM document at that URL.
  • MCP clients must be capable of reading the metadata URL, sending a request, and processing the returned PRM document.

At this stage, the first phase of discovery—resource server metadata retrieval—is complete.

For example, the retrieved resource metadata document can include:

  • The authorization server’s issuer identifier
  • The required scopes
  • A declaration that the client must use a bearer token in the HTTP Authorization header

Also, to avoid any confusion due to repeated use of the term metadata, it’s important to distinguish between:

  • Resource Metadata: Retrieved from the MCP server. It describes the MCP resource and the authorization server responsible for protecting it.
  • Authorization Server Metadata: Constructed using the issuer identifier found in the resource metadata. It describes how to interact with the authorization server to obtain access tokens.

Step 2: Authorization Server Metadata Discovery

Using the issuer identifier from the resource metadata, the MCP client constructs the Authorization Server Metadata URL by appending the path /.well-known/oauth-authorization-server to the issuer value.

The client then calls this metadata URL, and the authorization server responds with a JSON-formatted metadata document that includes:

  • The registration endpoint
  • The authorization and token endpoints
  • Supported grant types
  • Supported scopes
  • Supported client authentication methods

This second discovery phase completes the metadata discovery flow and equips the client with everything needed to proceed with registration and authorization.
It’s also important to note that returning the Protected Resource Metadata is the responsibility of the OAuth 2.0 Resource Server (in this case, the MCP server), while returning the Authorization Server Metadata falls under the responsibility of the OAuth 2.0 Authorization Server.

Client Registration 

At this stage, based on the authorization server metadata document retrieved earlier, the client application sends a registration request to the server’s registration endpoint. In response, the agentic application receives a client identifier (client_id) and, in the case of a confidential client, a client_secret. These credentials will be used in the authorization and token flows in the next phase.

According to the MCP auth specification, both the MCP server and client should support the OAuth2 Dynamic Client Registration (DCR) protocol to facilitate client registration. However, DCR support is not mandatory. MCP servers that do not support DCR must provide alternative means to obtain a client_id and client_secret.

Although Dynamic Client Registration (DCR) is widely adopted, there are differing views on the use of open registration. In fact, the specification itself allows the registration endpoint to be protected using OAuth 2.0. However, to enable clients to operate with a single configuration parameter, the MCP auth specification expects MCP servers to support open registration. It will be interesting to observe how this approach evolves in the future.

While DCR is widely adopted across the industry, there are differing opinions regarding the use of open registration which refers to allowing client applications to register themselves with the authorization server without requiring prior authentication. While this approach simplifies the developer experience—particularly for self-service scenarios—it can introduce security concerns if not implemented carefully. Recognizing these trade-offs, the OAuth 2.0 specification permits the registration endpoint to be protected using OAuth 2.0 itself. This allows authorization servers to require an initial layer of access control before allowing client registration.

However, the MCP authorization specification recommends supporting open registration to enable clients to operate with minimal configuration—ideally, using a single configuration parameter (i.e., the MCP server URL). This design choice prioritizes ease of use and developer onboarding, especially for public clients and automated integrations. That said, it remains to be seen how this recommendation will evolve in real-world deployments. 

 Authorization Flow 

The original OAuth2 specification, introduced over a decade ago, defined four different methods for obtaining an access token—known as grant types—to support various use cases and address technical limitations of that time. One of these was the implicit grant type, introduced because browsers’ default same-origin policy prevented applications running in one domain from accessing resources in another, making it difficult for browser-based applications to use the authorization code grant type. With the introduction of CORS, however, it's now possible to relax same-origin restrictions securely, allowing applications to use the authorization code flow. Due to this—and several associated security flaws—the implicit grant is no longer recommended.

Another grant type was resource owner password credentials, introduced to ease the transition from basic authentication to OAuth2. The issue with this grant is that it exposes the end user’s credentials to the client application, undermining one of the very core objectives of OAuth2. A decade later, the need for this transition has passed, making this grant type largely obsolete. Both the implicit grant and resource owner password credentials were deprecated in the OAuth2 Best Current Practices document for some time and removed from the OAuth 2.1 draft to avoid further confusion.

The MCP auth specification mandates the use of OAuth 2.1, and you can choose from the following grant types depending on your use case—specifically, whether an end user is involved or not. 

Authorization Code Grant Type

When a client acts on behalf of a human end user—for example, when a user accesses tools on a PayPal MCP server through a desktop client to review transactions—it is recommended to use the Authorization Code Grant type.

Unlike the OAuth 2.0 core specification, the OAuth 2.1 draft mandates the use of Proof Key for Code Exchange (PKCE) for both public and confidential clients. PKCE was originally introduced as an extension to protect public clients from authorization code interception attacks, PKCE is now required across all client types to enhance overall security. As a result, when working with MCP servers, PKCE must be used with the Authorization Code flow, regardless of the client type.

The diagram below illustrates the interaction flow between the MCP client and the server in a setup where the resource server and the authorization server are implemented as separate components.


The following diagram illustrates the interaction between the MCP client and server when the authorization server and resource server roles are combined into a single component.


The following sequence diagram below shows the authorization code grant type flow in detail.


At the end of this phase, the MCP client now possesses a valid access token. In the next stage, the MCP client can make a connection request using the MCP server endpoint URL—provided as the only input—along with the access token to initiate the connection. We will look at that in the next section.

Client Credentials Grant Type

When the client is another application—not a human user—the recommended approach is to use the client credentials grant type. For example, consider an agent application that queries a secure MCP tool to check store inventory. In such cases, the application acts autonomously, without any user involvement. The client credentials flow is specifically designed for these scenarios, allowing the application to authenticate itself using credentials such as a client_id and client_secret, and then obtain an access token directly.

This flow eliminates the need for human interaction and is well-suited for machine-to-machine communication. The diagram below illustrates the interaction flow between the MCP client and the server in a setup where the resource server and the authorization server are implemented as separate components.


The following diagram illustrates the interaction between the MCP client and server when the authorization server and resource server roles are combined into a single component.


At this point, the MCP client within our agentic application possesses the necessary client credentials and is aware of the server’s authorization and token endpoints. Therefore, it can now create an authorization request to initiate the flow.

Access Token Usage

As per the MCP auth specification, authorization—meaning the access token—must be included in every request from the MCP client to the MCP server, starting from the initial connection request. This is done by passing the access token through the HTTP Authorization header. It’s important to note that the access token should not be sent as a URI query string.

Once an access token is received by the MCP server (acting as a resource server at this stage), it should validate the token before proceeding. If validation fails, the MCP server must return an HTTP 401 Unauthorized error. Depending on the type of access token, one of the following validation approaches should be used:

  • Self-contained access token: Token formats like JWT include the necessary information for validation, such as the issuer (who issued the token), audience (to ensure the token is intended for this server), expiration time, and a signature. The MCP server should validate the token signature using the key set obtained via the JWKS URI, which is provided in the authorization server metadata during the discovery phase. Optionally, the MCP server may also call the introspection endpoint to verify if the token has been revoked.

  • Reference token: Also known as an opaque token, this type does not contain usable information directly. The MCP server must call the introspection endpoint of the authorization server to validate the token.

If a refresh token is provided alongside the access token, the MCP client can use it to obtain a new access token after expiration, without requiring end-user interaction. However, issuing a refresh token is subject to the authorization server's policy.

We have covered the security requirements and flows when an MCP client communicates with an MCP server using HTTP transport. Due to the length of this post, we are not discussing the security of communication between the MCP server and its backend—in this case, the PayPal MCP server and the PayPal backend. That said, some of the techniques described in the "Security when STDIO transport is in use" section are applicable here.

All HTTP communication must use TLS, and general OAuth 2 best practices and standard web application security practices should also be followed as well. 

Conclusion

MCP isn't just another protocol—it’s a necessary shift in how we bridge the gap between modern agentic applications and the diverse, fragmented ecosystem of real-world APIs and tools. While traditional APIs and integration methods technically suffice, they are too rigid, inconsistent, and labor-intensive to keep up with the dynamic, prompt-driven workflows that define agentic systems.

By introducing a universal protocol layer—complete with standardized roles, communication flows, and a well-defined security model—MCP simplifies and accelerates the way LLM-powered applications access and act on real-world data. Its support for dynamic discovery, tool execution, and secure runtime integration makes it not only relevant but essential in the evolving landscape of AI-native applications.

In short, MCP provides the foundation for a scalable, secure, and developer-friendly ecosystem where AI agents can reliably interact with external systems—without reinventing the wheel every time.