# MCP Series (07): Enterprise Deployment — Security, Authentication, and Version Management

> Source: <https://dev.to/wonderlab/mcp-series-07-enterprise-deployment-security-authentication-and-version-management-2n4n>
> Published: 2026-07-16 13:46:30+00:00

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*
