How to build a secure MCP server with OAuth 2.1: lessons from a CRM connector
To let an AI assistant work with real business data over the Model Context Protocol (MCP) , the server needs two things done properly: authentication (OAuth 2.1 with PKCE, so the AI client never sees a password) and authorization (so the assistant can see exactly what the signed-in person can see, and nothing more). For the Nitspot CRM connector I built, the most useful decision was to make the…
Building a secure Model Context Protocol (MCP) server for use with AI assistants requires implementing two key security measures: authentication and authorization. For the Nitspot CRM connector, using OAuth 2.1 with PKCE proved to be the most effective approach.
To establish an MCP connection, the server must first perform a discovery step. If a client initiates a request without a valid access token, the server responds with a 401 status code and a WWW-Authenticate header directing the client to the /.well-known/oauth-protected-resource endpoint. This endpoint provides metadata about the authorization server, including authorization server endpoints and capabilities.
For the Nitspot CRM connector, it was beneficial to treat the OAuth access token as a real user session. This allowed the server's row-level security (RLS) to handle authorization automatically. To accomplish this, the AI client registers itself along with its redirect URIs through dynamic client registration (RFC 7591). Since this is a public client, there is no client secret involved.
The authorization process utilizes PKCE S256, which involves the client sending a code_challenge during the authorization flow. After successful user authentication and consent, the server exchanges the code and code_verifier for an access token and a refresh token.
Several crucial security measures were implemented in this flow. The code is single-use and must be claimed only once. The token endpoint marks the code as used through a single conditional UPDATE statement. The access token is bound to the client that received it and is rotated with every use. The refresh token is also bound to the client and follows similar rotation rules. Additionally, an unregistered redirect target will never be redirected to, even for error reporting.
In the context of row-level security, the access token serves as the user's session token. Each MCP tool operates within the bounds of the caller's token, allowing the database's RLS to enforce appropriate permissions. This approach eliminates the need for additional permission systems and ensures that AI assistants can only access data authorized for the signed-in user.
For the Nitspot CRM connector, nine read-only tools were implemented, each with a readOnlyHint annotation. The server only implements the initialize, ping, tools/list, and tools/call methods, with any other methods returning a "method not found" error.
To further enhance the usability of these tools for AI models, two additional features were incorporated. First, admin-only tools are hidden from non-admins in the tools/list response. When an unauthorized model attempts to call an admin-only tool, the error response provides clear, plain-language feedback explaining why the action is prohibited.
Second, if a tool's result is too large, the server returns a clear error message, encouraging the model to narrow its query with a smaller limit or more filters. This ensures that the output remains useful and manageable for the AI model.
Finally, security considerations for browser-based MCP endpoints were addressed. The server rejects requests whose Origin header does not match the app's own origin, thereby preventing malicious web pages from accessing the MCP server via the user's browser. Native MCP clients are unaffected by this restriction, as they do not include an Origin header.
In summary, by leveraging OAuth 2.1 with PKCE, exact redirect matching, single-use, short-lived, hashed codes, and utilizing the user's session as the access token, a secure MCP server can be built for AI assistant integration. Implementing read-only tools, hiding inaccessible features, and providing clear error messages for AI models ensures a robust and secure integration experience.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.