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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,29 @@ Check your PATH(s):

```python --version || python3 --version```

#### SecOps SOAR: SSL Certificate Verification Error

If the SOAR MCP server fails to start with an error about SSL certificate verification
(or the generic "Failed to fetch valid scopes from SOAR"), this is typically caused by
Python not having access to the correct CA certificates. This is especially common on
**macOS**.

**Fix for macOS** — run the `Install Certificates.command` script bundled with your
Python installation (replace `3.12` with your actual Python minor version):

```bash
/Applications/Python\ 3.12/Install\ Certificates.command
```

**Fix for all platforms:**

```bash
pip install --upgrade certifi
```

Then restart the MCP server. See the [SOAR server README](server/secops-soar/README.md#troubleshooting)
for more details.



### Installing in Claude Desktop
Expand Down
22 changes: 22 additions & 0 deletions docs/usage_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,25 @@ If you encounter issues with the MCP servers:
3. **Check server logs**: Look for error messages in the server output
4. **Restart the client**: Sometimes restarting the LLM Desktop or VS Code can resolve connection issues
5. **Verify uv installation**: Ensure that `uv` is properly installed and accessible in your PATH

### SecOps SOAR: SSL Certificate Verification Error

If the SOAR MCP server fails to start with an error mentioning **SSL certificate verification failed**, this typically means Python cannot find the correct CA (Certificate Authority) certificates. This is a common issue on **macOS**.

**Fix for macOS:**

Run the `Install Certificates.command` script that ships with Python. Replace `3.12` with your installed Python minor version:

```bash
/Applications/Python\ 3.12/Install\ Certificates.command
```

**Fix for all platforms:**

```bash
pip install --upgrade certifi
```

Then restart the MCP server.

> **Note:** The SOAR MCP server now detects this specific issue and prints an actionable error message with fix instructions. If you still see the generic "Failed to fetch valid scopes from SOAR" message, make sure you are running the latest version of the server.
63 changes: 63 additions & 0 deletions server/secops-soar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,69 @@ $Env:SOAR_INTEGRATIONS = "ServiceNow,CSV,Siemplify"
- Python 3.11+
- SOAR URL and AppKey

## Troubleshooting

### SSL Certificate Verification Error

If you see an error like:

```
SSL certificate verification failed when connecting to SOAR.
```

or the older generic message:

```
Failed to fetch valid scopes from SOAR
```

This is typically caused by Python not having access to the correct CA (Certificate Authority) certificates. This is especially common on **macOS**.

**Fix for macOS:**

Run the `Install Certificates.command` script that ships with your Python installation. Replace `3.12` with your actual Python minor version:

```bash
/Applications/Python\ 3.12/Install\ Certificates.command
```

This installs the [`certifi`](https://pypi.org/project/certifi/) CA bundle, which Python needs for SSL/TLS verification.

**Fix for all platforms:**

```bash
pip install --upgrade certifi
```

Then restart the MCP server.

For further details, see the [usage guide](https://google.github.io/mcp-security/usage_guide.html#mcp-server-configuration-reference).

### Connection Error

If you see:

```
Failed to connect to SOAR at '<your-url>'.
```

Verify that:
1. The `SOAR_URL` environment variable is set correctly (e.g., `https://yours-here.siemplify-soar.com:443`).
2. The SOAR server is reachable from your network.
3. Any required VPN or proxy is active.

### Invalid Credentials

If you see:

```
Failed to fetch valid scopes from SOAR.
```

Verify that:
1. `SOAR_URL` is set and correct.
2. `SOAR_APP_KEY` is set and valid.

## License

Apache 2.0
Expand Down
2 changes: 1 addition & 1 deletion server/secops-soar/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ secops-soar = "secops_soar_mcp.server:run_main"

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
build-backend = "setuptools.build_meta"
16 changes: 11 additions & 5 deletions server/secops-soar/secops_soar_mcp/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import dotenv
from logger_utils import get_logger
from secops_soar_mcp.http_client import HttpClient
from secops_soar_mcp.http_client import HttpClient, SoarSSLError, SoarConnectionError
from secops_soar_mcp.utils import consts

dotenv.load_dotenv()
Expand All @@ -32,14 +32,20 @@
async def _get_valid_scopes():
valid_scopes_list = await http_client.get(consts.Endpoints.GET_SCOPES)
if valid_scopes_list is None:
raise RuntimeError(
"Failed to fetch valid scopes from SOAR, please make sure you have configured the right SOAR credentials. Shutting down..."
)
raise RuntimeError(consts.CREDENTIALS_ERROR_MESSAGE)
return set(valid_scopes_list)


async def bind():
"""Binds global variables."""
"""Binds global variables.

Raises:
SoarSSLError: If an SSL certificate verification error occurs
when connecting to SOAR.
SoarConnectionError: If the SOAR server cannot be reached.
RuntimeError: If the SOAR credentials are invalid or scopes
cannot be fetched.
"""
global http_client, valid_scopes
http_client = HttpClient(
os.getenv(consts.ENV_SOAR_URL), os.getenv(consts.ENV_SOAR_APP_KEY)
Expand Down
92 changes: 92 additions & 0 deletions server/secops-soar/secops_soar_mcp/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,46 @@
"""HTTP client for making requests to the SecOps SOAR API."""

import json
import ssl
from typing import Any, Dict

import aiohttp
from logger_utils import get_logger
from secops_soar_mcp.utils import consts

logger = get_logger(__name__)


class SoarSSLError(Exception):
"""Raised when an SSL certificate error occurs connecting to SOAR."""


class SoarConnectionError(Exception):
"""Raised when a connection to the SOAR server cannot be established."""


def _is_cert_verify_error(error: BaseException) -> bool:
"""Check if an error is caused by SSL certificate verification failure.

Inspects the error message, its chain of causes (__cause__), and
type to determine if the root cause is a certificate verification
failure.
"""
# Walk the cause chain to find CERTIFICATE_VERIFY_FAILED anywhere
current = error
while current is not None:
error_str = str(current).lower()
if (
"certificate_verify_failed" in error_str
or "certificate verify failed" in error_str
):
return True
if isinstance(current, ssl.SSLCertVerificationError):
return True
current = getattr(current, "__cause__", None)
return False


class HttpClient:
"""HTTP client for making requests to the SecOps SOAR API."""

Expand All @@ -41,6 +73,51 @@ async def _get_headers(self):
headers["AppKey"] = self.app_key
return headers

def _handle_ssl_error(self, error: Exception) -> None:
"""Check for SSL errors and raise descriptive exceptions.

Args:
error: The exception to inspect.

Raises:
SoarSSLError: If the error is an SSL certificate verification issue.
SoarConnectionError: If the error is a non-SSL connection issue.
"""
if isinstance(error, aiohttp.ClientConnectorSSLError):
if _is_cert_verify_error(error):
logger.error(
"SSL certificate verification failed: %s", error
)
raise SoarSSLError(
consts.SSL_CERTIFI_ERROR_MESSAGE
) from error
else:
logger.error("SSL/TLS error: %s", error)
raise SoarSSLError(
consts.SSL_GENERIC_ERROR_MESSAGE.format(error=error)
) from error

if isinstance(error, aiohttp.ClientConnectorError):
logger.error("Connection error: %s", error)
raise SoarConnectionError(
consts.CONNECTION_ERROR_MESSAGE.format(url=self.base_url)
) from error

# Also catch raw ssl.SSLError that may not be wrapped by aiohttp
if isinstance(error, ssl.SSLError):
if _is_cert_verify_error(error):
logger.error(
"SSL certificate verification failed: %s", error
)
raise SoarSSLError(
consts.SSL_CERTIFI_ERROR_MESSAGE
) from error
else:
logger.error("SSL/TLS error: %s", error)
raise SoarSSLError(
consts.SSL_GENERIC_ERROR_MESSAGE.format(error=error)
) from error

async def get(
self,
endpoint: str,
Expand All @@ -54,6 +131,10 @@ async def get(

Returns:
The response as a JSON object, or None if an error occurred.

Raises:
SoarSSLError: If an SSL certificate verification error occurs.
SoarConnectionError: If the SOAR server cannot be reached.
"""
headers = await self._get_headers()
try:
Expand All @@ -65,6 +146,7 @@ async def get(
except aiohttp.ClientResponseError as e:
logger.debug("HTTP error occurred: %s", e)
except Exception as e:
self._handle_ssl_error(e)
logger.debug("An error occurred: %s", e)
return None

Expand All @@ -83,6 +165,10 @@ async def post(

Returns:
The response as a JSON object, or None if an error occurred.

Raises:
SoarSSLError: If an SSL certificate verification error occurs.
SoarConnectionError: If the SOAR server cannot be reached.
"""
headers = await self._get_headers()
try:
Expand All @@ -96,6 +182,7 @@ async def post(
except aiohttp.ClientResponseError as e:
logger.debug("HTTP error occurred: %s", e)
except Exception as e:
self._handle_ssl_error(e)
logger.debug("An error occurred: %s", e)
return None

Expand All @@ -114,6 +201,10 @@ async def patch(

Returns:
The response as a JSON object, or None if an error occurred.

Raises:
SoarSSLError: If an SSL certificate verification error occurs.
SoarConnectionError: If the SOAR server cannot be reached.
"""
headers = await self._get_headers()
try:
Expand All @@ -125,6 +216,7 @@ async def patch(
except aiohttp.ClientResponseError as e:
logger.debug("HTTP error occurred: %s", e)
except Exception as e:
self._handle_ssl_error(e)
logger.debug("An error occurred: %s", e)
return None

Expand Down
5 changes: 5 additions & 0 deletions server/secops-soar/secops_soar_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import importlib
from pathlib import Path
from secops_soar_mcp import bindings
from secops_soar_mcp.http_client import SoarSSLError, SoarConnectionError
from mcp.server.fastmcp import FastMCP
from logger_utils import get_logger, setup_logging
from secops_soar_mcp.case_management import (
Expand Down Expand Up @@ -165,6 +166,10 @@ async def main():
await bindings.bind()
register_tools(args.integrations)
await mcp.run_stdio_async()
except SoarSSLError as e:
logger.error("\n%s", e)
except SoarConnectionError as e:
logger.error("\n%s", e)
except Exception as e:
logger.error("Error: %s", e)
finally:
Expand Down
45 changes: 45 additions & 0 deletions server/secops-soar/secops_soar_mcp/utils/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,54 @@
# limitations under the License.
"""Constants used in the SOAR integration."""

import platform
import sys

ENV_SOAR_URL = "SOAR_URL"
ENV_SOAR_APP_KEY = "SOAR_APP_KEY"

# Python version info for certifi fix instructions
_PYTHON_MINOR = f"{sys.version_info.major}.{sys.version_info.minor}"

SSL_CERTIFI_ERROR_MESSAGE = (
"SSL certificate verification failed when connecting to SOAR.\n"
"This is commonly caused by missing or outdated CA certificates in your "
"Python installation.\n\n"
"To fix this issue:\n"
+ (
f" macOS: Run: /Applications/Python\\ {_PYTHON_MINOR}/Install\\ Certificates.command\n"
if platform.system() == "Darwin"
else ""
)
+ " All platforms: pip install --upgrade certifi\n"
" Then restart the MCP server.\n\n"
"For more details, see: "
"https://google.github.io/mcp-security/usage_guide.html"
"#mcp-server-configuration-reference"
)

SSL_GENERIC_ERROR_MESSAGE = (
"An SSL/TLS error occurred when connecting to SOAR: {error}\n"
"Please verify that your SOAR_URL is correct and the server's "
"SSL certificate is valid."
)

CONNECTION_ERROR_MESSAGE = (
"Failed to connect to SOAR at '{url}'.\n"
"Please verify that:\n"
" 1. The SOAR_URL environment variable is set correctly.\n"
" 2. The SOAR server is reachable from your network.\n"
" 3. Any required VPN or proxy is active."
)

CREDENTIALS_ERROR_MESSAGE = (
"Failed to fetch valid scopes from SOAR.\n"
"Please make sure you have configured the right SOAR credentials:\n"
" 1. SOAR_URL is set and correct.\n"
" 2. SOAR_APP_KEY is set and valid.\n"
"Shutting down..."
)


class Endpoints:
"""Endpoints for SOAR."""
Expand Down
Loading