Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,25 @@ MCP_VERY_VERBOSE=true # Enables DEBUG level logging (equivalent to 'mcp-atlass
#SOCKS_PROXY=socks5://proxy.example.com:1080 # Requires 'requests[socks]' to be installed
#NO_PROXY=localhost,127.0.0.1,.internal.example.com # Comma-separated list of hosts/domains to bypass proxy

# PAC/WPAD proxy auto-configuration is opt-in and only used when no explicit proxy is configured.
#ATLASSIAN_PROXY_WPAD_ENABLE=false
#ATLASSIAN_PROXY_WPAD_URL=http://wpad/wpad.dat

# Jira-specific proxy settings (these override global proxy settings for Jira requests).
#JIRA_HTTP_PROXY=http://jira-proxy.example.com:8080
#JIRA_HTTPS_PROXY=https://jira-proxy.example.com:8443
#JIRA_SOCKS_PROXY=socks5://jira-proxy.example.com:1080
#JIRA_NO_PROXY=localhost,127.0.0.1,.internal.jira.com
#JIRA_PROXY_WPAD_ENABLE=false
#JIRA_PROXY_WPAD_URL=http://jira-wpad.example.com/wpad.dat

# Confluence-specific proxy settings (these override global proxy settings for Confluence requests).
#CONFLUENCE_HTTP_PROXY=http://confluence-proxy.example.com:8080
#CONFLUENCE_HTTPS_PROXY=https://confluence-proxy.example.com:8443
#CONFLUENCE_SOCKS_PROXY=socks5://confluence-proxy.example.com:1080
#CONFLUENCE_NO_PROXY=localhost,127.0.0.1,.internal.confluence.com
#CONFLUENCE_PROXY_WPAD_ENABLE=false
#CONFLUENCE_PROXY_WPAD_URL=http://confluence-wpad.example.com/wpad.dat

# --- SSRF Protection (Advanced) ---
# Optional: Comma-separated list of allowed URL domains for header-based authentication.
Expand Down
24 changes: 15 additions & 9 deletions docs/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,24 @@ See [.env.example](https://github.com/sooperset/mcp-atlassian/blob/main/.env.exa

## Proxy Configuration

MCP Atlassian supports routing API requests through HTTP/HTTPS/SOCKS proxies.
MCP Atlassian supports routing API requests through HTTP/HTTPS/SOCKS proxies,
with optional PAC/WPAD auto-configuration.

| Variable | Description |
|----------|-------------|
| `HTTP_PROXY` | HTTP proxy URL |
| `HTTPS_PROXY` | HTTPS proxy URL |
| `SOCKS_PROXY` | SOCKS proxy URL |
| `NO_PROXY` | Hosts to bypass proxy |
| `JIRA_HTTPS_PROXY` | Jira-specific HTTPS proxy |
| `CONFLUENCE_HTTPS_PROXY` | Confluence-specific HTTPS proxy |

Service-specific variables override global ones.
| `HTTP_PROXY` | Global HTTP proxy URL |
| `HTTPS_PROXY` | Global HTTPS proxy URL |
| `SOCKS_PROXY` | Global SOCKS proxy URL |
| `NO_PROXY` | Global hosts to bypass proxy |
| `ATLASSIAN_PROXY_WPAD_ENABLE` | Enable global PAC/WPAD evaluation (`true`/`false`) |
| `ATLASSIAN_PROXY_WPAD_URL` | Global PAC URL (defaults to `http://wpad/wpad.dat` when enabled) |
| `JIRA_HTTP_PROXY`, `JIRA_HTTPS_PROXY`, `JIRA_SOCKS_PROXY`, `JIRA_NO_PROXY` | Jira-specific explicit proxy overrides |
| `JIRA_PROXY_WPAD_ENABLE`, `JIRA_PROXY_WPAD_URL` | Jira-specific PAC/WPAD overrides |
| `CONFLUENCE_HTTP_PROXY`, `CONFLUENCE_HTTPS_PROXY`, `CONFLUENCE_SOCKS_PROXY`, `CONFLUENCE_NO_PROXY` | Confluence-specific explicit proxy overrides |
| `CONFLUENCE_PROXY_WPAD_ENABLE`, `CONFLUENCE_PROXY_WPAD_URL` | Confluence-specific PAC/WPAD overrides |

Service-specific variables override global ones. PAC/WPAD is opt-in and is
only evaluated when no explicit proxy is configured for that service.

## Custom HTTP Headers

Expand Down
5 changes: 5 additions & 0 deletions docs/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ Header values containing sensitive information are automatically masked in logs.
```bash
HTTPS_PROXY=https://proxy.example.com:8443
```
- If your network uses PAC/WPAD, enable it explicitly:
```bash
ATLASSIAN_PROXY_WPAD_ENABLE=true
ATLASSIAN_PROXY_WPAD_URL=http://wpad/wpad.dat
```
</Accordion>
</AccordionGroup>

Expand Down
30 changes: 30 additions & 0 deletions helm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,36 @@ See the `values.yaml` file for all configuration options.
- **oauthProxy.enabled**: Expose MCP OAuth discovery + DCR routes (opt-in)
- **oauthClientStorage.mode**: `default` (FastMCP storage) or `factory` (custom)

### Proxy + PAC/WPAD

Proxy values can also enable optional PAC/WPAD auto-configuration.

```yaml
proxy:
enabled: true
https: "https://proxy.example.com:8443"
noProxy: "localhost,127.0.0.1,.internal.example.com"
wpad:
enabled: true
url: "http://wpad/wpad.dat"
jira:
wpad:
enabled: false
confluence:
wpad:
enabled: true
url: "http://confluence-wpad.example.com/wpad.dat"
```

This configures the related proxy environment variables when set, including:

- `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, `SOCKS_PROXY`
- `ATLASSIAN_PROXY_WPAD_ENABLE`, `ATLASSIAN_PROXY_WPAD_URL`
- `JIRA_PROXY_WPAD_ENABLE`, `JIRA_PROXY_WPAD_URL`
- `CONFLUENCE_PROXY_WPAD_ENABLE`, `CONFLUENCE_PROXY_WPAD_URL`

PAC/WPAD remains opt-in and is only used when no explicit proxy is configured.

### OAuth Proxy + DCR (opt-in)

Enable OAuth proxy/DCR endpoints:
Expand Down
24 changes: 24 additions & 0 deletions helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@ spec:
- name: SOCKS_PROXY
value: {{ .Values.proxy.socks }}
{{- end }}
{{- if .Values.proxy.wpad.enabled }}
- name: ATLASSIAN_PROXY_WPAD_ENABLE
value: "true"
{{- end }}
{{- if .Values.proxy.wpad.url }}
- name: ATLASSIAN_PROXY_WPAD_URL
value: {{ .Values.proxy.wpad.url | quote }}
{{- end }}
{{- if .Values.proxy.confluence.http }}
- name: CONFLUENCE_HTTP_PROXY
value: {{ .Values.proxy.confluence.http }}
Expand All @@ -251,6 +259,14 @@ spec:
- name: CONFLUENCE_NO_PROXY
value: {{ .Values.proxy.confluence.noProxy }}
{{- end }}
{{- if ne .Values.proxy.confluence.wpad.enabled nil }}
- name: CONFLUENCE_PROXY_WPAD_ENABLE
value: {{ ternary "true" "false" .Values.proxy.confluence.wpad.enabled | quote }}
{{- end }}
{{- if .Values.proxy.confluence.wpad.url }}
- name: CONFLUENCE_PROXY_WPAD_URL
value: {{ .Values.proxy.confluence.wpad.url | quote }}
{{- end }}
{{- if .Values.proxy.jira.http }}
- name: JIRA_HTTP_PROXY
value: {{ .Values.proxy.jira.http }}
Expand All @@ -263,6 +279,14 @@ spec:
- name: JIRA_NO_PROXY
value: {{ .Values.proxy.jira.noProxy }}
{{- end }}
{{- if ne .Values.proxy.jira.wpad.enabled nil }}
- name: JIRA_PROXY_WPAD_ENABLE
value: {{ ternary "true" "false" .Values.proxy.jira.wpad.enabled | quote }}
{{- end }}
{{- if .Values.proxy.jira.wpad.url }}
- name: JIRA_PROXY_WPAD_URL
value: {{ .Values.proxy.jira.wpad.url | quote }}
{{- end }}
{{- end }}

# Server Configuration
Expand Down
9 changes: 9 additions & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -204,16 +204,25 @@ proxy:
https: ""
noProxy: "localhost,127.0.0.1"
socks: ""
wpad:
enabled: false
url: ""

# Service-specific proxy overrides
confluence:
http: ""
https: ""
noProxy: ""
wpad:
enabled: null # Set true/false to override the global WPAD toggle for Confluence
url: ""
jira:
http: ""
https: ""
noProxy: ""
wpad:
enabled: null # Set true/false to override the global WPAD toggle for Jira
url: ""

# Server Configuration
config:
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ requires-python = ">=3.10"
dependencies = [
"atlassian-python-api>=4.0.0",
"requests[socks]>=2.31.0",
"pypac>=0.17.2",
"beautifulsoup4>=4.12.3",
"httpx>=0.28.0",
"mcp>=1.8.0,<2.0.0",
Expand Down Expand Up @@ -150,6 +151,10 @@ ignore_missing_imports = true
module = "src.mcp_atlassian.*"
disallow_untyped_defs = false

[[tool.mypy.overrides]]
module = "pypac.*"
ignore_missing_imports = true

[tool.hatch.version]
source = "uv-dynamic-versioning"

Expand Down
31 changes: 12 additions & 19 deletions src/mcp_atlassian/confluence/client.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"""Base client module for Confluence API interactions."""

import logging
import os

from atlassian import Confluence
from requests import Session
from requests.exceptions import ConnectionError as RequestsConnectionError

from ..exceptions import MCPAtlassianAuthenticationError
from ..utils.logging import get_masked_session_headers, log_config_param, mask_sensitive
from ..utils.logging import get_masked_session_headers, mask_sensitive
from ..utils.oauth import configure_oauth_session
from ..utils.proxy import apply_proxy_configuration
from ..utils.ssl import configure_ssl_verification
from .config import ConfluenceConfig

Expand All @@ -32,6 +32,7 @@ def __init__(self, config: ConfluenceConfig | None = None) -> None:
MCPAtlassianAuthenticationError: If OAuth authentication fails
"""
self.config = config or ConfluenceConfig.from_env()
outbound_url = self.config.url

# Initialize the Confluence client based on auth type
if self.config.auth_type == "oauth":
Expand Down Expand Up @@ -65,6 +66,8 @@ def __init__(self, config: ConfluenceConfig | None = None) -> None:
api_url = f"https://api.atlassian.com/ex/confluence/{self.config.oauth_config.cloud_id}"
is_cloud = True

outbound_url = api_url

# Initialize Confluence with the session
self.confluence = Confluence(
url=api_url,
Expand Down Expand Up @@ -123,23 +126,13 @@ def __init__(self, config: ConfluenceConfig | None = None) -> None:
client_key_password=self.config.client_key_password,
)

# Proxy configuration
proxies = {}
if self.config.http_proxy:
proxies["http"] = self.config.http_proxy
if self.config.https_proxy:
proxies["https"] = self.config.https_proxy
if self.config.socks_proxy:
proxies["socks"] = self.config.socks_proxy
if proxies:
self.confluence._session.proxies.update(proxies)
for k, v in proxies.items():
log_config_param(
logger, "Confluence", f"{k.upper()}_PROXY", v, sensitive=True
)
if self.config.no_proxy and isinstance(self.config.no_proxy, str):
os.environ["NO_PROXY"] = self.config.no_proxy
log_config_param(logger, "Confluence", "NO_PROXY", self.config.no_proxy)
self.confluence._session = apply_proxy_configuration(
logger=logger,
service_name="Confluence",
session=self.confluence._session,
config=self.config,
target_url=outbound_url,
)

# Apply custom headers if configured
if self.config.custom_headers:
Expand Down
18 changes: 10 additions & 8 deletions src/mcp_atlassian/confluence/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
OAuthConfig,
get_oauth_config_from_env,
)
from ..utils.proxy import get_proxy_settings_from_env
from ..utils.urls import is_atlassian_cloud_url


Expand All @@ -35,6 +36,8 @@ class ConfluenceConfig:
https_proxy: str | None = None # HTTPS proxy URL
no_proxy: str | None = None # Comma-separated list of hosts to bypass proxy
socks_proxy: str | None = None # SOCKS proxy URL (optional)
proxy_wpad_enable: bool = False # Whether to load PAC/WPAD configuration
proxy_wpad_url: str | None = None # PAC URL used when WPAD is enabled
custom_headers: dict[str, str] | None = None # Custom HTTP headers
client_cert: str | None = None # Client certificate file path (.pem)
client_key: str | None = None # Client private key file path (.pem)
Expand Down Expand Up @@ -166,10 +169,7 @@ def from_env(cls) -> "ConfluenceConfig":
spaces_filter = os.getenv("CONFLUENCE_SPACES_FILTER")

# Proxy settings
http_proxy = os.getenv("CONFLUENCE_HTTP_PROXY", os.getenv("HTTP_PROXY"))
https_proxy = os.getenv("CONFLUENCE_HTTPS_PROXY", os.getenv("HTTPS_PROXY"))
no_proxy = os.getenv("CONFLUENCE_NO_PROXY", os.getenv("NO_PROXY"))
socks_proxy = os.getenv("CONFLUENCE_SOCKS_PROXY", os.getenv("SOCKS_PROXY"))
proxy_settings = get_proxy_settings_from_env("CONFLUENCE")

# Custom headers - service-specific only
custom_headers = get_custom_headers("CONFLUENCE_CUSTOM_HEADERS")
Expand All @@ -196,10 +196,12 @@ def from_env(cls) -> "ConfluenceConfig":
oauth_config=oauth_config,
ssl_verify=ssl_verify,
spaces_filter=spaces_filter,
http_proxy=http_proxy,
https_proxy=https_proxy,
no_proxy=no_proxy,
socks_proxy=socks_proxy,
http_proxy=proxy_settings["http_proxy"],
https_proxy=proxy_settings["https_proxy"],
no_proxy=proxy_settings["no_proxy"],
socks_proxy=proxy_settings["socks_proxy"],
proxy_wpad_enable=bool(proxy_settings["proxy_wpad_enable"]),
proxy_wpad_url=proxy_settings["proxy_wpad_url"],
custom_headers=custom_headers,
client_cert=client_cert,
client_key=client_key,
Expand Down
30 changes: 11 additions & 19 deletions src/mcp_atlassian/jira/client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Base client module for Jira API interactions."""

import logging
import os
from typing import Any, Literal

from atlassian import Jira
Expand All @@ -12,10 +11,10 @@
from mcp_atlassian.preprocessing import JiraPreprocessor
from mcp_atlassian.utils.logging import (
get_masked_session_headers,
log_config_param,
mask_sensitive,
)
from mcp_atlassian.utils.oauth import configure_oauth_session
from mcp_atlassian.utils.proxy import apply_proxy_configuration
from mcp_atlassian.utils.ssl import configure_ssl_verification

from ..models.jira.adf import markdown_to_adf
Expand Down Expand Up @@ -46,6 +45,7 @@ def __init__(self, config: JiraConfig | None = None) -> None:
"""
# Load configuration from environment variables if not provided
self.config = config or JiraConfig.from_env()
outbound_url = self.config.url

# Initialize the Jira client based on auth type
if self.config.auth_type == "oauth":
Expand Down Expand Up @@ -79,6 +79,8 @@ def __init__(self, config: JiraConfig | None = None) -> None:
api_url = f"https://api.atlassian.com/ex/jira/{self.config.oauth_config.cloud_id}"
is_cloud = True

outbound_url = api_url

# Initialize Jira with the session
self.jira = Jira(
url=api_url,
Expand Down Expand Up @@ -136,23 +138,13 @@ def __init__(self, config: JiraConfig | None = None) -> None:
client_key_password=self.config.client_key_password,
)

# Proxy configuration
proxies = {}
if self.config.http_proxy:
proxies["http"] = self.config.http_proxy
if self.config.https_proxy:
proxies["https"] = self.config.https_proxy
if self.config.socks_proxy:
proxies["socks"] = self.config.socks_proxy
if proxies:
self.jira._session.proxies.update(proxies)
for k, v in proxies.items():
log_config_param(
logger, "Jira", f"{k.upper()}_PROXY", v, sensitive=True
)
if self.config.no_proxy and isinstance(self.config.no_proxy, str):
os.environ["NO_PROXY"] = self.config.no_proxy
log_config_param(logger, "Jira", "NO_PROXY", self.config.no_proxy)
self.jira._session = apply_proxy_configuration(
logger=logger,
service_name="Jira",
session=self.jira._session,
config=self.config,
target_url=outbound_url,
)

# Apply custom headers if configured
if self.config.custom_headers:
Expand Down
Loading
Loading