MCP Series (07): Enterprise Deployment — Security, Authentication, and Version Management A developer outlines enterprise deployment strategies for MCP servers, covering API key, OAuth 2.0, and mTLS authentication methods. The post provides Docker and systemd configurations for production-grade security, including network isolation and resource limits. It also addresses version management challenges when multiple agent sessions connect to an MCP server. A local MCP Server takes one command: python server.py . Enterprise production adds three required problems: Simplest option, suitable for service-to-service calls inside a corporate network: python import os from mcp.server import Server EXPECTED API KEY = os.environ.get "MCP API KEY" if not EXPECTED API KEY: raise RuntimeError "MCP API KEY environment variable is required" server = Server "jira-tools" For HTTP transport non-stdio , validate in middleware: python from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request class ApiKeyMiddleware BaseHTTPMiddleware : async def dispatch self, request: Request, call next : key = request.headers.get "X-API-Key" or request.headers.get "Authorization", "" .removeprefix "Bearer " if key = os.environ "MCP API KEY" : return Response "Unauthorized", status code=401 return await call next request Use when different users need access to different subsets of data different Jira projects per user : The MCP spec 2025 defines OAuth integration interfaces. The Host Claude Desktop / Claude Code acquires the OAuth token during user login and passes it to the Server on each MCP connection. For finance, healthcare, or government scenarios with strict data security requirements — mutual certificate verification: python import ssl ssl context = ssl.SSLContext ssl.PROTOCOL TLS SERVER ssl context.load cert chain "/certs/server.crt", "/certs/server.key" ssl context.load verify locations "/certs/ca.crt" ssl context.verify mode = ssl.CERT REQUIRED require client cert pass ssl context to HTTP server Authentication selection: Internal service calls same network → API Key + network isolation Cross-org or user-level permission needs → OAuth 2.0 High security finance, healthcare, gov → mTLS FROM python:3.12-slim WORKDIR /app Separate dependency install cache layer COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . Don't run as root security best practice RUN useradd -r -s /bin/false mcpuser USER mcpuser HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ CMD python -c "import sys; print 'healthy' " || exit 1 CMD "python", "jira server.py" docker-compose.prod.yml version: "3.9" services: jira-mcp: build: . image: jira-mcp-server:1.3.0 restart: unless-stopped auto-restart on crash environment: - MCP API KEY=${MCP API KEY} injected from .env or Secrets - JIRA URL=${JIRA URL} - JIRA TOKEN=${JIRA TOKEN} - LOG LEVEL=INFO volumes: - ./logs:/app/logs networks: - mcp-internal internal only deploy: resources: limits: cpus: "0.5" memory: "256M" reservations: memory: "128M" logging: driver: "json-file" options: max-size: "100m" max-file: "5" networks: mcp-internal: driver: bridge internal: true no external network access Key configuration points: restart: unless-stopped : restart on crash; stay stopped only on manual stop internal: true : Docker network with no outbound connection — only containers on the same network can reach the Server /etc/systemd/system/jira-mcp.service Unit Description=Jira MCP Server After=network.target Service Type=simple User=mcpuser WorkingDirectory=/opt/jira-mcp ExecStart=/usr/bin/python3 /opt/jira-mcp/jira server.py Restart=always RestartSec=5 Environment="MCP API KEY=your-key" Environment="JIRA TOKEN=your-token" StandardOutput=journal StandardError=journal Install WantedBy=multi-user.target systemctl enable jira-mcp systemctl start jira-mcp journalctl -u jira-mcp -f live log stream Multiple Agent sessions connect to MCP Server v1.2. You need to release v1.3 adds a new tool without dropping those connections. docker-compose.prod.yml parallel versions services: jira-mcp-stable: image: jira-mcp-server:1.2.0 current stable, serves existing connections restart: unless-stopped networks: mcp-internal jira-mcp-canary: image: jira-mcp-server:1.3.0 new version, accepts new connections restart: unless-stopped networks: mcp-internal deploy: replicas: 1 Point the Host config at the canary version first. Monitor it. Then switch stable: { "mcpServers": { "jira": { "command": "docker", "args": "exec", "jira-mcp-canary", "python", "jira server.py" } } } MAJOR.MINOR.PATCH MAJOR: breaking changes → Remove a tool, rename a tool, change required inputSchema fields → Agent code that depends on this tool must update in sync MINOR: backward-compatible additions → Add a tool, add optional parameters, extend return fields → Existing Agent code continues working; new features opt-in PATCH: behavior-preserving fixes → Bug fixes, performance improvements, logging changes → Transparent upgrade Declare the version in Server code: server = Server "jira-tools", version="1.3.0" returned to Client in initialize response python @server.call tool async def call tool name: str, arguments: dict : if name == "search jira": old tool name logger.warning "Tool 'search jira' is deprecated. " "Use 'search issues' instead. Removing in v2.0.0." return await search issues arguments delegate to new implementation Keep the old tool name for 90 days, log usage, then remove it in the MAJOR version. Authentication and authorization Network isolation internal: true — Server has no direct outbound access Tool security Operations restart: unless-stopped or systemd supervision ensures availability restart: unless-stopped crash recovery + internal: true network isolation + resource limits stability Check out PrimeSkills — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works. Find more useful knowledge and interesting products on my Homepage