Безопасность и аутентификация MCP
Добавьте аутентификацию на сервер MCP с помощью токенов OAuth 2.0, реализуйте проверку входных данных для предотвращения атак с внедрением и применяйте принцип минимально необходимых привилегий к разрешениям инструментов.
«Безопасность и аутентификация MCP» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
Части этого урока еще не переведены и отображаются на английском.
Why MCP Security Matters
An MCP server is a gateway into your systems. Without proper security, a compromised AI client or malicious prompt could read sensitive data, trigger destructive operations, or exfiltrate information through the tool call channel. Security for MCP servers must be defense in depth: authentication at the transport layer, authorization at the tool level, and input validation on every call.
Local vs. Remote Server Security
The stdio transport (used by Claude Desktop) has inherent security: the server runs as a local process, only accessible to the user who launched it. Network-exposed MCP servers using HTTP/SSE face the full range of web security threats: authentication bypass, injection attacks, and unauthorized access. Security requirements differ dramatically based on deployment mode.
- Local stdio: Trust the local user; focus on input validation
- Remote HTTP/SSE: Full authentication, TLS, rate limiting, input sanitization
API Key Authentication for Remote Servers
The simplest authentication for remote MCP servers is API key validation via an HTTP header. Check the Authorization: Bearer <token> header on every incoming request and reject unauthenticated requests with HTTP 401. Store valid API keys in a database with user metadata so you can revoke individual keys.
# For HTTP/SSE MCP servers using FastAPI or similar:
from fastapi import FastAPI, HTTPException, Depends, Header
from typing import Optional
import secrets
app_http = FastAPI()
# In production: store in database with user_id, created_at, last_used
VALID_KEYS = {'sk-mcp-abc123': {'user': 'alice', 'scopes': ['read']},
'sk-mcp-def456': {'user': 'bob', 'scopes': ['read', 'write']}}
async def verify_api_key(authorization: Optional[str] = Header(None)) -> dict:
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail='Missing API key')
key = authorization.removeprefix('Bearer ')
if key not in VALID_KEYS:
raise HTTPException(status_code=401, detail='Invalid API key')
return VALID_KEYS[key] # Returns user context
# Use in route handlers:
# @app_http.get('/sse')
# async def sse_endpoint(user=Depends(verify_api_key)):OAuth 2.0 for Enterprise MCP Servers
For enterprise deployments, use OAuth 2.0 so users authenticate with their corporate identity provider (Okta, Azure AD, Google Workspace). The MCP client obtains an OAuth access token, which it includes in tool call requests. Your server validates the token signature against the identity provider's public keys using python-jose or authlib.
from jose import jwt, JWTError
import httpx
AUTH_DOMAIN = 'your-tenant.auth0.com'
AUDIENCE = 'https://api.your-mcp-server.com'
async def get_jwks():
async with httpx.AsyncClient() as client:
resp = await client.get(f'https://{AUTH_DOMAIN}/.well-known/jwks.json')
return resp.json()
async def verify_oauth_token(token: str) -> dict:
jwks = await get_jwks()
try:
payload = jwt.decode(
token,
jwks,
algorithms=['RS256'],
audience=AUDIENCE,
issuer=f'https://{AUTH_DOMAIN}/'
)
return payload # Contains sub (user ID), scope, exp, etc.
except JWTError as e:
raise ValueError(f'Invalid token: {e}')Scope-Based Authorization
Not all MCP tools should be available to all users. Use OAuth scopes or role claims in the JWT token to determine which tools the authenticated user may call. Check authorization at the beginning of every tool execution, before any database or API call is made.
TOOL_REQUIRED_SCOPES = {
'list_products': ['read:products'],
'search_products': ['read:products'],
'create_order': ['write:orders'],
'delete_order': ['admin:orders']
}
def check_authorization(tool_name: str, token_payload: dict):
'''Raise ValueError if user lacks required scope for the tool.'''
required = TOOL_REQUIRED_SCOPES.get(tool_name, [])
if not required:
return # No scope required
user_scopes = set(token_payload.get('scope', '').split())
missing = [s for s in required if s not in user_scopes]
if missing:
raise ValueError(
f'Access denied. Tool "{tool_name}" requires scopes: {missing}. '
f'Your token has: {list(user_scopes)}'
)
# In call_tool handler:
# check_authorization(name, current_user_token)
# ... then execute the toolInput Validation and Injection Prevention
All tool inputs are ultimately strings generated by an LLM — treat them as untrusted. Validate every input against expected types and patterns before use. Specifically guard against: SQL injection (use parameterized queries, never string-interpolated SQL), command injection (never pass user input to shell commands), and path traversal (normalize and validate file paths).
import re
from pathlib import Path
BASE_DATA_DIR = Path('/data/mcp-files')
def safe_file_path(user_input: str) -> Path:
'''Validate and normalize a file path to prevent traversal attacks.'''
# Remove any path traversal sequences
clean = re.sub(r'\.\./', '', user_input)
clean = re.sub(r'\.\.\\\\', '', clean)
path = (BASE_DATA_DIR / clean).resolve()
# Ensure the resolved path is still within the allowed base directory
if not str(path).startswith(str(BASE_DATA_DIR)):
raise ValueError(f'Path traversal detected: {user_input}')
return path
def safe_identifier(value: str) -> str:
'''Validate a database identifier (table/column name).'''
if not re.match(r'^[a-z_][a-z0-9_]{0,63}$', value, re.IGNORECASE):
raise ValueError(f'Invalid identifier: {value}')
return valueRate Limiting Tool Calls
An LLM agent in a loop could call expensive tools hundreds of times per minute, exhausting your database connections, third-party API quotas, or compute budget. Implement per-user rate limits using a token bucket algorithm in Redis. Reject tool calls that exceed the limit with a descriptive error so the agent knows to wait.
import redis
import time
r = redis.Redis.from_url('redis://localhost:6379')
def check_rate_limit(user_id: str, tool_name: str, limit: int = 60, window: int = 60) -> None:
'''Allow at most `limit` calls per `window` seconds per user per tool.'''
key = f'rate:{user_id}:{tool_name}'
pipe = r.pipeline()
pipe.incr(key)
pipe.expire(key, window)
count, _ = pipe.execute()
if count > limit:
retry_after = r.ttl(key)
raise ValueError(
f'Rate limit exceeded for {tool_name}. '
f'Limit: {limit} calls/{window}s. '
f'Retry after {retry_after} seconds.'
)Principle of Least Privilege
Apply the principle of least privilege at every layer of your MCP server. The database user should have SELECT-only on the tables the server needs. The server process should run as a non-root OS user. Tools should request only the permissions they need. API keys should have the minimum scope for their purpose. Each permission you withhold is a potential attack that can't succeed.
-- PostgreSQL: Create a dedicated read-only database user for your MCP server
CREATE ROLE mcp_reader LOGIN PASSWORD 'strong_random_password';
-- Grant SELECT on only the tables the server needs
GRANT SELECT ON products, categories, public_content TO mcp_reader;
-- Explicitly deny access to sensitive tables
REVOKE ALL ON users, api_keys, payment_methods FROM mcp_reader;
-- Never grant: INSERT, UPDATE, DELETE, TRUNCATE, or DDL permissionsTLS and Transport Security
Remote MCP servers must use TLS to protect data in transit. Configure your server to only accept HTTPS connections. In production, use a reverse proxy (nginx, Caddy) to handle TLS termination and keep certificates up to date with automatic renewal (Let's Encrypt via Certbot or Caddy's built-in ACME support).
# Example Caddyfile for TLS-terminating MCP server at a subdomain:
#
# mcp.yourcompany.com {
# reverse_proxy localhost:8080
# encode gzip
# tls internal # Use Let's Encrypt in production
# header {
# Strict-Transport-Security 'max-age=31536000; includeSubDomains'
# X-Content-Type-Options nosniff
# X-Frame-Options DENY
# }
# }Prompt Injection via MCP Resources
A subtle attack vector: if your MCP server reads content from external sources (web pages, user-uploaded files, untrusted databases) and returns it as a tool result, an attacker could embed adversarial instructions in that content. The model might obey instructions hidden in retrieved data — this is indirect prompt injection. Sanitize retrieved content and never return raw untrusted text directly as tool output.
import re
def sanitize_for_mcp_output(text: str) -> str:
'''Remove patterns that look like instructions to the LLM.'''
# Remove common injection patterns
dangerous_patterns = [
r'ignore previous instructions',
r'ignore all prior instructions',
r'system:',
r'<\|.*?\|>', # Special tokens
r'\[INST\]',
r'<s>',
]
for pattern in dangerous_patterns:
text = re.sub(pattern, '[FILTERED]', text, flags=re.IGNORECASE)
return text[:10000] # Also cap length to prevent context stuffingSecurity Auditing and Monitoring
Log all security-relevant events: authentication successes and failures, rate limit violations, authorization denials, validation errors, and unusual access patterns. Set up alerts for: multiple failed auth attempts (brute force), a single user calling destructive tools rapidly, and any tool call with extremely large inputs. Review audit logs regularly and automate anomaly detection.
Quick Check
Test your understanding of MCP security and authentication concepts.
Lesson Recap
In this lesson you learned: remote MCP servers require OAuth 2.0 or API key authentication on every request, scope-based authorization controls which tools each authenticated user can call, and all tool inputs must be validated to prevent injection attacks and path traversal. This concludes the MCP module — next we explore advanced chunking strategies for high-precision RAG retrieval.
Часто задаваемые вопросы
Урок «Безопасность и аутентификация MCP» бесплатный?
Да — полный текст урока «Безопасность и аутентификация MCP» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.
Чему я научусь в уроке «Безопасность и аутентификация MCP»?
Добавьте аутентификацию на сервер MCP с помощью токенов OAuth 2.0, реализуйте проверку входных данных для предотвращения атак с внедрением и применяйте принцип минимально необходимых привилегий к раз… Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Engineering Academy?
Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Безопасность и аутентификация MCP»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Engineering Academy?
Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Что такое MCP и почему это важно
- Создание первого сервера MCP
- Предоставление ресурсов базы данных через MCP
- Безопасность и аутентификация MCP