Building Secure MCP Servers - What I Learned After Six Months of Trial and Error
Published: September 30, 2025
•By: Miska Kaskinen
I’ve been building and testing Model Context Protocol servers since January 2025. Last week I stumbled across a research paper that explained why I’d been fighting so many authentication battles: “Enterprise-Grade Security for the Model Context Protocol (MCP): Frameworks and Mitigation Strategies” by researchers at Amazon Web Services and Adversarial AI Security reSEarch.
Turns out the problems I hit weren’t just my implementation. MCP has fundamental security gaps.
The security paper confirmed what I suspected
The researchers tested 20 AI models against tool poisoning attacks. Success rate: 72.8%.
Tool poisoning works because AI models trust tool descriptions completely. An attacker hides malicious instructions in metadata users never see. When you ask the AI to add two numbers, it also reads your SSH keys and configuration files—all while showing you a clean confirmation dialog.
The paper documented something worse. A fake “add numbers” tool can poison descriptions for OTHER trusted servers. Your AI redirects all emails to an attacker even when you explicitly specify different recipients. The AI never mentions it.
Even Claude-3.7-Sonnet—the most resistant model tested—refused these poisoned instructions less than 3% of the time.
Reading this paper felt validating and concerning. I’d been building MCP servers for months without realizing how exposed they were.
Why I started building my own boilerplate
The biggest concern for MCP servers isn’t performance. It’s security.
Third-party MCP servers are convenient. Connect them to your customer database, production systems, internal tools. But 15% of analyzed MCP servers contain potentially exploitable metadata. Supply chain attacks are trivial—packages change behavior post-approval, typosquatting impersonates official integrations.
I decided: don’t use third-party MCP servers where they touch critical systems. Build your own.
With recent advancements in streamable HTTP and OAuth 2.0, I started building a boilerplate for secure authentication. The goal: implement proper OAuth 2.0 with PKCE, deploy globally via Cloudflare Workers, connect to MySQL through Hyperdrive.
What I learned: MCP authentication is harder than it looks.
The OAuth problem nobody talks about
OAuth 2.0 with PKCE works great for human users. Users click, authorize, exchange tokens. Perfect.
Then I tried using these MCP servers in automated environments. AI agents in Clay. Messages API. Responses API. Autonomous operations.
OAuth flows require user presence for token exchange. Agents operate asynchronously. No user sitting there to approve requests.
I had two options. Force all automation through OAuth refresh tokens with constant re-authentication. Or add API keys with scoped permissions for machine-to-machine scenarios.
I chose both. OAuth 2.0 with PKCE for human-delegated access. API keys for automated agents that need defined permissions without interactive approval.
API keys get a bad reputation. But scoped correctly—with specific permissions, regular rotation, full logging of agent ID, user ID, and action—they work for this use case. OAuth handles delegation and consent. API keys handle operations that don’t need a human approving each request.
The Cloudflare Workers learning curve
Building globally-distributed MCP servers hit immediate friction.
Cloudflare Workers can’t do direct TCP connections to MySQL databases. I discovered this the hard way. Workers support outbound TCP but with hard limits—maximum six simultaneous connections per invocation.
Direct database access from hundreds of edge locations would exhaust connection pools in minutes. Establishing a MySQL connection requires seven round-trips. At 125ms per round-trip from edge to origin, that’s 625ms before the first query executes.
Hyperdrive solved this. Cloudflare’s connection pooler maintains warm connections near your database. Pre-established inter-datacenter connections eliminate repeated handshakes. Query caching identifies read operations automatically and caches responses at edge and origin.
The integration was surprisingly smooth once I figured out the connection patterns. Configure your database connection string once. Hyperdrive manages pooling, placement, and caching automatically.
Implementing dual PKCE validation
Standard OAuth 2.0 with PKCE prevents authorization code interception. The client creates a random code verifier (minimum 256 bits entropy), hashes it with SHA256, sends the hash during authorization, proves possession of the original verifier during token exchange.
Without PKCE, malicious apps intercept authorization codes and exchange them for access tokens. With PKCE, intercepted codes are useless without the verifier sent over TLS.
I implemented dual PKCE validation. The server maintains separate flows—one for the external OAuth provider (Google) and one for client validation against the MCP server itself. This prevents confused deputy problems where the server executes actions with its own privileges rather than the user’s permissions.
Cloudflare’s OAuth provider library didn’t support this pattern. I had to build a custom implementation that supported both OAuth flows and API keys. That meant implementing Dynamic Client Registration (RFC 7591) for on-the-fly agent registration, Resource Parameters (RFC 8707) for audience-bound tokens, and proper token validation at multiple layers.
More secure than basic API keys. More complex to build.
What production MCP deployment actually requires
The research paper specified three layers for enterprise MCP security.
First layer: build your own MCP servers. Implement static application security testing (SAST) and software composition analysis (SCA) in your build pipeline. Sign MCP components for integrity verification. Include MCP servers in standard vulnerability management.
Second layer: comprehensive tool validation. Cryptographic signing and verification for tool descriptions. Malicious pattern detection using RegEx and semantic analysis. Behavioral baselining for each tool.
Third layer: monitoring and audit trails. Log every MCP tool invocation with full delegation chain—user ID, agent ID, token ID, tool name, timestamp, sanitized parameters. Feed logs to your SIEM. Build correlation rules for MCP threats.
I haven’t implemented all of this yet. Tool validation and behavioral baselining are still work in progress. But the foundation is there—OAuth 2.0 with PKCE, dual PKCE validation, global distribution via Cloudflare Workers, database pooling through Hyperdrive.
The final outcome: globally distributed MCP server that scales without infrastructure thinking. Security follows OAuth 2.0 with PKCE best practices. Cloudflare’s generous free tier means I can test and iterate without cost concerns.
The work continues
This is definitely still a work in progress. I enjoy tinkering and studying this stuff as the technology evolves. Each implementation reveals new edge cases. Each integration surfaces unexpected challenges.
The authentication and security for MCP is not an easy task. I spent considerable time solving small problems related to how I wanted the infrastructure to work and support specific needs.
MCP adoption is accelerating. OpenAI integrated it in March 2025. Google DeepMind confirmed support in April. Hundreds of MCP servers proliferate across public repositories.
The convenience of universal AI integration is real. The security implications are just becoming clear.
Are you running MCP in production? What deployment patterns are you using? What security challenges have you encountered? I’m curious what database combinations people are trying beyond MySQL, and how others are handling the OAuth vs API key decision for automated agents.
Building secure MCP infrastructure isn’t impossible. It requires deliberate architecture, comprehensive validation, and continuous monitoring. The question is whether we implement these controls now, or wait for the first major breach to force the conversation.
Written by Miska Kaskinen
← Back to all posts