Making MCP Servers Production-Ready

11 Mins to read

MCP servers production

Introduction to the MCP Server

Building on the foundational concepts covered in the first blog, which introduced Model Context Protocol (MCP) and its role in connecting AI infrastructure and AI assistants to external tools and data, this blog is the second installment of our two-part series on MCP and focuses on a critical aspect for production deployments: authorization.

MCP is gaining momentum with major companies like AWS, Google, Databricks, and Microsoft actively developing and publishing MCP servers on GitHub. The ecosystem now includes:

  • 20+ reference servers, which are the built‑in examples demonstrating core MCP protocol features.
  • 115+ official integrations that are production‑quality servers maintained by platform vendors.
  • 300+ community servers, which are community‑contributed MCP implementations (approximate count).
  • 10+ frameworks that include SDKs and tools to help you build servers or clients.
  • 15+ additional resources, including guides, registries, and community hubs for MCP.

As MCP adoption expands, one challenge becomes clear: how do we ensure AI infrastructure and AI agents don’t get more access than they need? In our first blog, we explored how MCP servers connect AI assistants to a wide range of tools and sensitive enterprise data, making integration seamless and scalable. But with this power comes risk; if an MCP server is granted full authorization, it could expose confidential information or allow unintended actions by AI agents.

MCP server concept explained

Understanding the Problem

By design, agentic AI and AI agents can operate autonomously and interact with multiple systems. If we hand them unrestricted access, we lose the ability to control what data or actions they can perform, increasing the risk of data leaks, compliance violations, or even malicious behavior.

MCP server resolves the problem

Why Do We Need Authorization at All?

MCP servers often handle sensitive data and critical actions, making authorization a fundamental aspect of their security. OAuth 2.0 / 2.1 serves as the foundation for MCP’s authorization framework, enabling users to grant AI agents limited access without exposing passwords or API keys.

OAuth in MCP: The Challenge

The MCP specification mandates the use of OAuth 2.0 / 2.1 for authorization but leaves the implementation details to developers. This flexibility can lead to misunderstandings—some teams may assume that every MCP server must operate its own OAuth service.

In reality, OAuth defines distinct roles: the Authorization Server, which issues tokens, and the Resource Server (your MCP API), which validates them. Requiring every MCP server to also function as an authorization server is unnecessary and inefficient.

From a product and operational perspective, this distinction is critical. Forcing every MCP server to manage authorization increases complexity, slows development, and creates scalability challenges. In an ecosystem with many clients and servers, manually registering each client on every server is not scalable.

The MCP specification strongly recommends that MCP Servers (authorization servers) support Dynamic Client Registration. This approach allows new MCP clients to register automatically, reducing friction and enabling a smoother user experience. By embracing this model, teams can focus on building core MCP functionality while ensuring secure, scalable authorization.

Best Practices for MCP Authorization

To make MCP servers production-ready, it is essential to implement authorization correctly. The following best practices help ensure secure and scalable MCP deployments:

1. Separate the Authorization Server

Keep your MCP server focused on its core functions by acting solely as an OAuth 2.0 / 2.1 Resource Server (see figure below). Delegate login and token issuance to a dedicated Authorization Server. In practice, this means leveraging an external OAuth provider—such as your enterprise Identity Provider (IDP) or services like Auth0 or Google—rather than building an authentication UI into the MCP server. For example, Atlassian’s MCP integration delegates authorization to their existing cloud identity system, offloading single sign-on (SSO), multi-factor authentication (MFA), and auditing to proven systems.

Abstract MCP Server Flow

2. Enable Dynamic Client Registration and Discovery

Make your OAuth setup flexible by supporting Dynamic Client Registration (RFC 7591), which allows new MCP clients to register automatically. Implement OAuth Authorization Server Metadata (RFC 8414) so clients can auto-discover authorization endpoints.

This is essential because:

  • MCP clients won’t know all potential authorization servers in advance.
  • Manual registration would reduce usability.
  • Dynamic registration ensures seamless onboarding with new servers.
  • Authorization servers can enforce their own policies during registration.

If Dynamic Registration isn’t supported, servers must offer alternative methods to obtain client credentials:

  • Clients may need to hardcode credentials, or
  • Provide a UI for users to manually input credentials after self-registration.

3. Scope Access at the Tool/Function Level

Define granular OAuth scopes for each tool or function your MCP server exposes, for example, files.read or files.write. AI agents should request only the scopes they need, and users should see a consent screen detailing these permissions. Your MCP server must enforce these scopes to ensure agents can only invoke allowed actions. This limits potential damage if a token is misused and provides clear boundaries for the AI’s capabilities.

MCP Server Example: AUTH Implementation (Client/Server/Auth) (Python)




# --- Placeholder/Mock Classes (These would typically come from an MCP SDK or other libraries) ---
class MockOAuthToken:
"""A simplified representation of an OAuth access token."""
def __init__(self, access_token: str, token_type: str = "Bearer", expires_in: int = 3600): passclass MockTokenStorage:
"""A placeholder for a class that would handle secure token storage and retrieval."""
async def get_tokens(self) -> MockOAuthToken | None:
"""Retrieves a previously stored OAuth token."""
passasync def set_tokens(self, tokens: MockOAuthToken) -> None:
"""Stores a new OAuth token."""
pass# --- Error Handling ---
class OAuthError(Exception): pass
class TokenExpiredError(OAuthError): pass
class AuthorizationDeniedError(OAuthError): pass# --- MCP Client Core ---
class MCPClient:
"""
Represents an MCP Client application that interacts with an MCP Resource Server,
handling OAuth 2.1 authorization with an external Authorization Server.
"""

def __init__(self, mcp_server_url: str, auth_server_url: str, client_callback_port: int = 3030, oauth_scopes: list[str] = None):
"""
Initializes the MCP Client with necessary server URLs and configuration.
"""

self.mcp_server_url = mcp_server_url
self.auth_server_url = auth_server_url
self.client_callback_port = client_callback_port
self.client_callback_url = f"http://localhost:{client_callback_port}/callback"
self.token_storage = MockTokenStorage()
self.access_token = None # The active access token for authenticated requests
self.client_id = "mock_client_id_123" # A pre-registered client ID for demonstration
self._callback_server_instance = None # Internal reference to the local HTTP server
self.oauth_scopes = oauth_scopes or ["mcp:read", "mcp:write"]async def run(self):
"""
Starts the MCP client's main operation.
It attempts to obtain an access token and then uses it to interact with the MCP server.
"""

passasync def _get_access_token(self) -> str | None:
"""
Manages the process of acquiring or refreshing an OAuth 2.1 access token.
This involves checking cached tokens, initiating the Authorization Code Flow
if needed, and exchanging the authorization code for an access token.
"""

passasync def _refresh_token_if_needed(self) -> bool:
"""
Checks if the current access token is expired and attempts to refresh it.
Returns True if a valid token is available (either current or refreshed), False otherwise.
"""

if self.access_token and self._is_token_expired():
return await self._refresh_access_token()
return True
def _is_token_expired(self) -> bool:
"""
Checks if the current access token has expired.
(Placeholder for actual expiry logic).
"""

pass
async def _refresh_access_token(self) -> bool:
"""
Attempts to refresh the access token using a refresh token (if available).
(Placeholder for refresh token logic).
Returns True on successful refresh, False otherwise.
"""

pass

async def _initiate_authorization_flow(self, code_verifier: str, expected_state: str) -> tuple[str, str | None]:
"""
Constructs the authorization URL and opens it in the user's browser.
Then, it starts a local HTTP server to listen for the OAuth redirect
and waits to capture the authorization code and state.
"""

pass
async def _exchange_code_for_token(self, auth_code: str, code_verifier: str) -> str | None:
"""
Sends a POST request to the Authorization Server's /token endpoint
to exchange the authorization code for an access token.
"""

pass
async def _make_authenticated_mcp_request(self, endpoint: str) -> dict:
"""
Sends an HTTP GET request to the MCP server, including the access token
in the Authorization header. Handles potential 401 Unauthorized responses.
"""

pass
# --- Internal HTTP Callback Server for OAuth Redirect ---
class _ClientCallbackHandler: # Replaced BaseHTTPRequestHandler with a simpler class due to import removal
"""
A custom HTTP request handler for the temporary local server.
It's designed to capture the authorization code and state from the OAuth redirect.
"""

def do_GET(self):
"""Handles incoming GET requests, parsing the URL for OAuth parameters."""
pass
def log_message(self, format, *args):
"""Suppresses default HTTP server logging to keep output clean."""
pass
class _ClientCallbackServer: # Replaced BaseHTTPRequestHandler with a simpler class due to import removal
"""
Manages the lifecycle of the temporary local HTTP server.
This server is crucial for the OAuth flow as it receives the authorization code
from the Authorization Server via a browser redirect.
"""

def __init__(self, port: int, callback_data_store: dict): pass
def start(self) -> None:
"""Starts the HTTP server in a separate thread to avoid blocking."""
pass
def stop(self) -> None:
"""Shuts down the HTTP server."""
pass
def wait_for_callback(self, timeout: int = 60) -> tuple[str, str | None]:
"""Waits until the authorization code is received or a timeout occurs."""
pass
# --- Main Client Demo Function ---
async def main():
"""
The main entry point for the MCP Client demonstration.
Initializes the client and runs its core logic.
"""

client = MCPClient(mcp_server_url="http://localhost:8001", auth_server_url="http://localhost:9000")
await client.run()
if __name__ == "__main__":
# To run this code, ensure 'httpx' is installed: pip install httpx
# Note: This code is a structural outline. Running it without full implementations
# and actual HTTP server/client libraries will result in errors.
pass # asyncio.run(main()) removed as it would error without imports

Security Best Practices

  • To secure the Model Context Protocol (MCP), servers must enforce strict authorization and session management.
  • They must prevent security attacks by always reconfirming user consent for new clients, especially when acting as a proxy.
  • The practice of “token passthrough” is forbidden; servers must validate that any received security token is specifically intended for them.
  • To stop session hijacking, every request must be individually authenticated, and servers must use secure, unpredictable session IDs that are bound to specific user information rather than using the session itself for authentication.
  • A very good (recommended reading) resource for MCP security best practices is available here

Conclusion

Authorization is a crucial piece in making MCP servers production-ready. While the MCP specification mandates OAuth 2.0 / 2.1 for authorization, it leaves implementation details to developers, which can lead to inconsistent or insecure setups if not carefully addressed.

Separating the Authorization Server from the MCP Resource Server simplifies development and improves scalability. Leveraging external OAuth providers, such as enterprise identity systems or services like Auth0 and Google, offloads complex authentication tasks and enables robust features like SSO, MFA, and auditing.

Supporting Dynamic Client Registration and OAuth Authorization Server Metadata allows MCP clients to register and discover authorization endpoints automatically, eliminating manual client setup and enhancing user experience.

Finally, enforcing fine-grained scopes at the tool or function level ensures agentic AI and AI agents only access what they are explicitly permitted to, reducing security risks and maintaining clear boundaries. By following these best practices, developers can build secure, scalable MCP servers that integrate seamlessly with existing identity infrastructures, making AI integrations both powerful and safe.