A practical security guide for MCP servers in production. Credential handling, authentication, GDPR, input validation, and the mistakes we see most often.
By Finn Hartley
MCP is moving from experimental to production fast. AI assistants connected to live APIs, databases, and third-party services are no longer a proof-of-concept. They're running in companies of all sizes, handling real data, right now.
The security model for MCP is still being worked out. Anthropic's specification defines the protocol but deliberately leaves security implementation to the server operator. That's the right architectural choice. It's also why developers building MCP servers need to think carefully about security from the start.
This guide covers 10 security considerations that matter most in production. Each one includes what the risk is, who should care about it, and what to do.
Defence in Depth
Five security layers between an MCP client and your upstream API credentials
Tool Schema Validation
Constrained inputs, type checking, pattern matching
Rate Limiting
Per-session, per-license, per-upstream endpoint
Input Sanitisation
SQL parameterisation, path traversal prevention, injection blocking
Authentication Layer
Bearer tokens, OAuth 2.0, session management
Server-Side Credentials
Encrypted at rest, never transmitted to clients
Upstream APIs -- Google, Meta, Amazon, YouTube -- credentials never leave the server
Who cares: Everyone. This is the foundation.
The most common MCP security mistake is putting upstream API credentials in a configuration file that lives on the user's machine.
This pattern comes from the early stdio era of MCP, when most servers ran locally. Developer-focused clients like Cursor, VS Code, Cline, and Continue still support local stdio servers and still accept configs shaped like this.
{
"mcpServers": {
"my-tool": {
"command": "node",
"args": ["server.js"],
"env": {
"GOOGLE_API_KEY"
Product Lead at Ooty. Writes about MCP architecture, security, and developer tooling.
When you connect ChatGPT, Gemini, or Claude to your Google Analytics account through Ooty, your Google password never touches our systems. Neither does your Meta login, your Amazon credentials, or any other platform password. We use OAuth, the same authenticat
The difference between the Model Context Protocol and a traditional API comes down to one question: who orchestrates the work? With an API, your code controls every step. With MCP, the AI decides what to call and when. That single distinction determines which
Charts have always lived outside AI conversations. You run an analysis, get a table of numbers, and then open a separate tool to visualize it. Ploti changes that. It is a free, open-source MCP server that renders 43 chart types as interactive widgets directly
Every process on that machine can read those credentials. If the machine is shared, compromised, or the config file is accidentally committed to a repo, those credentials are exposed.
The modern path avoids this entirely. Remote MCP servers, accessed via the Streamable HTTP transport, live on the operator's infrastructure. Clients like ChatGPT, Claude (web, desktop, mobile), Gemini, Cursor, Windsurf, and VS Code connect by pasting a URL into a Connectors UI or adding a url entry in their MCP config. Upstream credentials never touch the end user's machine. For any multi-tenant or production deployment, remote is the only defensible architecture. The old claude_desktop_config.json stdio bridge does not even accept remote servers anymore.
According to GitGuardian's 2024 report, over 12.8 million new secrets were detected in public GitHub commits in 2023 alone (GitGuardian, 2024). API keys in config files are one of the most common vectors.
The fix: Credentials live on your server, not the client. The client authenticates to your MCP server using a token (a license key, a session token, an OAuth access token). Your server holds the upstream API keys and proxies requests on the client's behalf. This is the server-side proxy pattern. It's the only pattern that keeps upstream credentials safe in a multi-tenant deployment. Ooty's architecture uses this pattern for the same reason.
Who cares: Developers building MCP servers.
MCP servers that accept HTTP connections need authentication on every route. "I'll add auth later" is how security holes ship to production.
Two primary options:
Bearer token authentication. The client includes Authorization: Bearer {token} in every request. Your server validates the token before processing any tool calls. Appropriate for license-key or API-key based authentication.
OAuth 2.0. For user-delegated access (the user authorises your MCP server to act on their behalf), OAuth is the correct standard. The Streamable HTTP transport supports OAuth token passing natively. Appropriate when users are authorising access to their own accounts on platforms like Google or Meta.
For development environments, no-auth is fine. For anything that touches real data, implement auth before you write the first tool handler.
Who cares: Both CTOs and end users.
When implementing OAuth flows, request only the scopes your tool needs.
Bad practice:
Google OAuth scopes: analytics.readonly
mail.google.com
calendar
Requesting Gmail and Calendar access for an analytics tool is unnecessary and alarming to users. OAuth consent screens showing excessive permissions reduce conversion and create legitimate security concerns.
This matters for GDPR compliance too. GDPR's data minimisation principle (Article 5(1)(c)) requires that personal data is "adequate, relevant and limited to what is necessary." Requesting unnecessary scopes creates GDPR exposure.
The rule: If your MCP server reads Google Ads data, you need the Google Ads readonly scope. That's it. Nothing else.
MCP Threat Model
Six security risks in MCP deployments, sorted by severity
| Severity | Threat | Vector | Mitigation |
|---|---|---|---|
| critical | Credential exposure | API keys in client-side config files | Server-side proxy pattern |
| high | Token theft | Session tokens accessible to other processes | Short-lived tokens, machine binding, revocation |
| high | Injection attacks | Malicious tool parameters (SQL, path traversal) | Parameterised queries, input validation, schema constraints |
| high | GDPR violations | Over-logging, excessive data retention, missing DPAs | Log metadata only, set retention limits, right to erasure |
| medium | Runaway agent costs | Agent stuck in loop exhausts API quotas | Three-tier rate limiting (session, license, upstream) |
| medium | Supply chain compromise | Vulnerable transitive npm/pip dependencies | Lock files, CI auditing, SCA tools |
Credential exposure
Vector: API keys in client-side config files
Fix: Server-side proxy pattern
Token theft
Vector: Session tokens accessible to other processes
Fix: Short-lived tokens, machine binding, revocation
Injection attacks
Vector: Malicious tool parameters (SQL, path traversal)
Fix: Parameterised queries, input validation, schema constraints
GDPR violations
Vector: Over-logging, excessive data retention, missing DPAs
Fix: Log metadata only, set retention limits, right to erasure
Runaway agent costs
Vector: Agent stuck in loop exhausts API quotas
Fix: Three-tier rate limiting (session, license, upstream)
Supply chain compromise
Vector: Vulnerable transitive npm/pip dependencies
Fix: Lock files, CI auditing, SCA tools
Who cares: Developers and security teams.
If your MCP server issues session tokens, the design matters more than you might think.
Checklist:
On JWT specifically. JWTs are stateless, which means they can't be revoked without a blocklist. For MCP session tokens that need to be revocable (think: license cancellation, security incident), store sessions server-side and validate against the database.
Who cares: Developers.
MCP tool parameters are user-controlled inputs. Full stop. Treat them like you would treat form submissions on a public website.
The attack surface depends on what your tools do with the parameters:
../../etc/passwd) is trivially constructed with string concatenation.The MCP specification doesn't validate tool parameters. That's your responsibility.
Who cares: Anyone paying API bills.
Your MCP server sits between the AI assistant and your upstream APIs. If you don't rate limit at the MCP layer, a runaway agent can exhaust your upstream API quotas in minutes.
This is both a security concern and a cost concern. Many upstream APIs (Google Ads, Meta, Amazon) have both rate limits and cost-per-call billing. An agent stuck in a loop can generate significant unexpected costs.
Three levels of rate limiting:
Return the right error. When rate limited, return HTTP 429 with Retry-After header. MCP clients that implement proper error handling will back off automatically. Clients that don't will at least get a clear error rather than a cryptic failure.
Who cares: Any company with European users.
If your MCP server handles data about EU residents, and that includes most marketing data (analytics, ad performance, social media metrics), GDPR applies. The common areas that catch people:
Logging. If you log tool requests for debugging, be careful about what you log. Analytics data and search queries may constitute personal data if they're linkable to individuals. Log request metadata (timing, error codes, tool names) rather than request payloads.
Data processing agreements. If you proxy requests to upstream APIs on behalf of users, you're a data processor. Your terms of service and privacy policy need to reflect this.
Right to erasure. If you store any user data server-side (session tokens, cached API responses, usage logs), you need a deletion mechanism. Build the deletion path before you need it.
Cross-border transfer. US servers processing EU user data need a valid legal basis for the transfer. The EU-US Data Privacy Framework covers many US-based services, but you need to be enrolled.
Retention limits. Don't store upstream API responses longer than necessary. If you're caching for performance, set cache TTLs in hours, not months, and implement automated cleanup.
Who cares: Security teams, on-call engineers, compliance officers.
Good security posture requires knowing what happened when something goes wrong. MCP server logs should capture enough to reconstruct events without storing sensitive data.
Log:
Don't log:
Use structured JSON logs with consistent fields. This makes it practical to search for specific patterns when investigating incidents.
Keep access logs for three months. That's typically sufficient for incident investigation. Longer retention increases your GDPR exposure.
Who cares: Developers and DevOps teams.
MCP servers are typically built on npm or pip ecosystems. These ecosystems have a supply chain problem: transitive dependencies can introduce vulnerabilities that are invisible in your direct dependency list.
Minimum viable dependency security:
npm audit or pip-audit in your CI/CD pipelineSonatype's 2024 report found that open-source supply chain attacks increased 200% year-over-year (Sonatype, 2024).
The specific MCP risk. MCP servers using the stdio transport run as local processes on the user's machine with the user's permissions. A compromised dependency executes with full user-level access. This is still a meaningful threat for stdio servers distributed as npm packages, which is most of what developer clients like Cursor, Cline, and Continue run locally.
For server-side remote MCP deployments (Streamable HTTP transport), which is what ChatGPT, Claude, and Gemini now use for anything production-grade, the risk profile is similar to any other web service. Standard practices apply.
Who cares: Everyone building MCP tools.
The MCP tool schema (the JSON Schema definition of what parameters each tool accepts) is a security control, not just documentation.
A poorly defined schema is an invitation to misuse:
// Dangerous: any string accepted
{
"name": "search_data",
"parameters": {
"query": { "type": "string" }
}
}// Better: constrained, validated inputs
{
"name": "search_data",
"parameters": {
"query": {
"type": "string",
"maxLength": 200,
"pattern": "^[a-zA-Z0-9 _-]+$"
},
"date_range": {
"type": "string",
"enum": ["7d", "30d", "90d", "6m", "12m"]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 10
}
}
}The schema does three things: tells the AI client what's expected (reducing hallucinated parameters), provides a first line of validation before your code runs, and limits the attack surface for adversarial inputs.
Validate against the schema in your server code too. Don't trust that the client has enforced schema constraints.
If you're building MCP infrastructure for enterprise customers, SOC 2 Type II is the most relevant compliance framework. It evaluates security controls across five trust service criteria: Security, Availability, Processing Integrity, Confidentiality, and Privacy.
Every control described in this guide (access controls, audit logging, encryption, rate limiting, vulnerability management) maps directly to SOC 2 Security criteria. The audit is primarily about demonstrating that controls are in place and operating consistently over time. If SOC 2 is on your roadmap, implement these controls systematically and document them from the start.
The 10-Point Security Checklist
The baseline for responsible MCP server operation
Server-side credentials
No upstream API keys on client machines
Auth on every endpoint
Bearer token or OAuth before shipping
Minimum OAuth scopes
Request only what the tool needs
Proper token design
Random, expiring, revocable, stored securely
Input validation
All tool parameters treated as untrusted
Three-tier rate limiting
Session, license, and upstream API
GDPR-compliant handling
Minimise storage, know your legal basis
Safe audit logging
Log metadata not payloads, set retention
Dependency hygiene
Audit regularly, lock files, keep updated
Schema as security control
Constrain inputs, validate server-side
These 10 points represent the baseline for responsible MCP server operation. The floor, not the ceiling.
The MCP ecosystem is growing fast, and the companies that get security right early will have a real advantage over those that retrofit it later. Every week we see another headline about a data breach that started with exposed credentials or missing authentication. MCP doesn't have to repeat those mistakes.
For a concrete example of these principles in production, see how Ooty's architecture implements each of these security layers.