Skip to content

Commit a8966bd

Browse files
fix: stateless HTTP cleanup and version bump to 0.6.0
- Fix transport_mode to include 'stdio' in Literal type (fixes CI failures) - Change contextlib.suppress(BaseException) to suppress(Exception) - Fix transport_mode field description (was copy-paste error) - Clean up stateless_http type from bool | None to bool - Document PURPLEMCP_TRANSPORT_MODE in README - Update CHANGELOG for 0.6.0 release
1 parent 8b29d03 commit a8966bd

File tree

10 files changed

+53
-36
lines changed

10 files changed

+53
-36
lines changed

CHANGELOG.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased] - YYYY-MM-DD
99

10-
## Changed
10+
## [0.6.0] - 2025-11-25
11+
12+
### Added
13+
14+
- Amazon Bedrock AgentCore deployment support with `--stateless-http` flag
15+
- New `PURPLEMCP_STATELESS_HTTP` environment variable for stateless HTTP mode
16+
- New `PURPLEMCP_TRANSPORT_MODE` environment variable for transport configuration
17+
- Comprehensive AWS Bedrock deployment guide (BEDROCK_AGENTCORE_DEPLOYMENT.md)
18+
- IAM and trust policy templates for AWS Bedrock AgentCore
19+
20+
### Changed
1121

1222
- Updated default values for client details to be more accurate
23+
- Transport mode now configurable via environment variable
24+
- Improved documentation for environment variables in README
25+
26+
### Fixed
27+
28+
- Exception handling in server.py uses `Exception` instead of `BaseException`
29+
- Type annotations for `stateless_http` field (removed unnecessary `| None`)
30+
- Corrected `transport_mode` field description in Settings
1331

1432
## [0.5.1] - 2025-11-08
1533

@@ -72,5 +90,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7290
- Automated CI/CD with GitHub Actions
7391
- Comprehensive documentation (README, CONTRIBUTING, SECURITY)
7492

93+
[0.6.0]: https://github.com/Sentinel-One/purple-mcp/compare/v0.5.1...v0.6.0
7594
[0.5.1]: https://github.com/Sentinel-One/purple-mcp/compare/v0.5.0...v0.5.1
7695
[0.5.0]: https://github.com/Sentinel-One/purple-mcp/releases/tag/v0.5.0

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
Purple AI MCP Server allows you to access SentinelOne Services with any MCP client.
1212

13-
**Coming Soon**: In early 2026, we will allow you to connect to this service hosted by SentinelOne.
14-
1513
## Features
1614

1715
This server exposes SentinelOne's platform through the Model Context Protocol:
@@ -202,7 +200,8 @@ We suggest you **do not** expose Purple AI MCP on a network at this time, as the
202200
## Environment Variables
203201
- `PURPLEMCP_CONSOLE_TOKEN` - Service user token (Account or Site level)
204202
- `PURPLEMCP_CONSOLE_BASE_URL` - Console URL (e.g., https://console.sentinelone.net)
205-
- `PURPLEMCP_STATELESS_HTTP` - For use with deployment in Amazon Bedrock Agent Core - Detailed instructions can be found [here](BEDROCK_AGENTCORE_DEPLOYMENT.md)
203+
- `PURPLEMCP_TRANSPORT_MODE` - MCP transport mode: `stdio` (default), `sse`, or `streamable-http`
204+
- `PURPLEMCP_STATELESS_HTTP` - Enable stateless HTTP mode for serverless deployments (e.g., Amazon Bedrock AgentCore) - see [deployment guide](BEDROCK_AGENTCORE_DEPLOYMENT.md)
206205

207206

208207
## Development

SECURITY.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ This guide documents the security expectations for every contributor and operato
88

99
- **Project maintainers** provide secure-by-default libraries, tools, and configuration primitives.
1010
- **Operators and deployers** are responsible for securing runtime environments, network boundaries, secrets, observability pipelines, and user access.
11-
- **Users running Purple MCP as a remote service** must place the instance behind a reverse proxy (for example, Nginx, Envoy, or an API gateway) that enforces strong authentication and authorization. Purple MCP does not ship its own auth layer.
12-
- **Hosted MCP offering**: SentinelOne plans to launch an official hosted Purple MCP service in early 2026. Until that release, all external-facing deployments demand operator-managed network controls and authentication.
11+
- **Users running Purple MCP as a remote service** must place the instance behind a reverse proxy (for example, Nginx, Envoy, or an API gateway) that enforces strong authentication and authorization. Purple MCP does not ship its own auth layer. All external-facing deployments require operator-managed network controls and authentication.
1312

1413
## Threat Model Overview
1514

@@ -60,8 +59,7 @@ This guide documents the security expectations for every contributor and operato
6059
- Terminate TLS at a reverse proxy that enforces strong client authentication (SAML/OIDC SSO, mutual TLS, signed API tokens).
6160
- Implement rate limiting, audit logging, and IP allowlists at the proxy layer.
6261
- Restrict network access to SentinelOne control planes and internal assets required by your workflows.
63-
- Document all access paths and routinely review who can reach the MCP instance.
64-
- Upcoming hosted service (early 2026) will provide managed authentication, centralized auditing, and turnkey deployments. Until then, the operator bears full responsibility for access control.
62+
- Document all access paths and routinely review who can reach the MCP instance. The operator bears full responsibility for access control.
6563

6664
## Deployment Guidance
6765

@@ -86,10 +84,6 @@ This guide documents the security expectations for every contributor and operato
8684
- Leverage orchestrator features (Kubernetes NetworkPolicies, PodSecurityStandards, IAM roles for service accounts).
8785
- Inject configuration via secrets and config maps—never bake secrets into container images.
8886

89-
### Anticipated Hosted MCP (Early 2026)
90-
91-
- A managed SentinelOne-hosted MCP service is planned to launch in early 2026, delivering integrated authentication, network isolation, and operational monitoring.
92-
9387
## Logging and Telemetry
9488

9589
- Logging is sanitized by default to prevent leakage of queries, tokens, or personally identifiable information.
@@ -136,6 +130,5 @@ This guide documents the security expectations for every contributor and operato
136130

137131
- [`CONTRIBUTING.md`](CONTRIBUTING.md) – Development workflow and coding standards.
138132
- [`README.md`](README.md) – Project overview and setup instructions.
139-
- SentinelOne internal security policies and the upcoming hosted MCP documentation (target release: early 2026).
140133

141134
Security is a continuous effort. Revisit this guide regularly, automate compliance checks where possible, and surface improvements to the team so that Purple MCP remains secure throughout its lifecycle.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "purple-mcp"
3-
version = "0.5.1"
3+
version = "0.6.0"
44
description = "Purple AI MCP Server"
55
readme = "README.md"
66
requires-python = ">=3.10"

ruff.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ docstring-code-format = true
128128

129129
[lint.per-file-ignores]
130130
"__init__.py" = ["F401"] # unused-import: Allow unused imports in __init__.py files
131-
"src/indigo_workshop/**" = ["C901"] # complex-structure: Allow complex functions in workshop code
132131

133132
[lint.isort]
134133
combine-as-imports = true

src/purple_mcp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
and interacting with AI-powered security analysis services.
66
"""
77

8-
__version__ = "0.5.1"
8+
__version__ = "0.6.0"

src/purple_mcp/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,15 @@ class Settings(BaseSettings):
172172
validation_alias=LOGFIRE_TOKEN_ENV,
173173
)
174174

175-
stateless_http: bool | None = Field(
175+
stateless_http: bool = Field(
176176
default=False,
177177
description="Stateless mode (new transport per request)",
178178
validation_alias=STATELESS_HTTP_ENV,
179179
)
180180

181-
transport_mode: Literal["http", "streamable-http", "sse"] = Field(
182-
default="sse",
183-
description="Stateless mode (new transport per request)",
181+
transport_mode: Literal["stdio", "http", "streamable-http", "sse"] = Field(
182+
default="stdio",
183+
description="MCP transport mode (stdio, http, streamable-http, or sse)",
184184
validation_alias=TRANSPORT_MODE_ENV,
185185
)
186186

src/purple_mcp/server.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"""
4343

4444
import contextlib
45+
from typing import Literal
4546

4647
import fastmcp
4748
from fastmcp.server.http import StarletteWithLifespan
@@ -142,21 +143,25 @@ async def health_check(request: Request) -> JSONResponse:
142143
settings = None
143144

144145
# Use get_settings to ensure usage of lru_cache decorator.
145-
with contextlib.suppress(BaseException):
146+
with contextlib.suppress(Exception):
146147
settings = get_settings()
147148

148149

149-
def get_http_app(app: fastmcp.FastMCP, settings: Settings | None) -> StarletteWithLifespan:
150-
"""Returns a http_app using environment variable settings."""
151-
return (
152-
app.http_app(transport=settings.transport_mode, stateless_http=settings.stateless_http)
153-
if settings and settings.transport_mode in ("streamable-http", "http")
154-
# stateless_http arg has no effect if using "sse" transport mode when instantiating a http_app, AND there
155-
# is only a choice of three transport modes, hence if settings is None, it is safe to assume "sse" transport
156-
# mode even if the settings object is None - this also preserves the original module-level implementation of
157-
# http_app.
158-
else app.http_app(transport="sse")
159-
)
150+
def get_http_app(
151+
mcp_app: fastmcp.FastMCP[None], settings: Settings | None
152+
) -> StarletteWithLifespan:
153+
"""Returns a http_app using environment variable settings.
154+
155+
For stdio mode or when settings is None, defaults to SSE transport for the HTTP app.
156+
The stateless_http setting only applies to streamable-http and http transports.
157+
"""
158+
if settings and settings.transport_mode in ("streamable-http", "http"):
159+
# Type narrowing: transport_mode is "streamable-http" or "http" here
160+
transport: Literal["http", "streamable-http"] = (
161+
"streamable-http" if settings.transport_mode == "streamable-http" else "http"
162+
)
163+
return mcp_app.http_app(transport=transport, stateless_http=settings.stateless_http)
164+
return mcp_app.http_app(transport="sse")
160165

161166

162167
http_app = get_http_app(app, settings)

tests/unit/test_server.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class TestServerInitialization:
2424
"""Tests for server initialization and configuration."""
2525

2626
def _test_http_app_mode(
27-
self, mode: Literal["http", "streamable-http", "sse"], stateless_http: bool
27+
self, mode: Literal["stdio", "http", "streamable-http", "sse"], stateless_http: bool
2828
) -> None:
2929
mock_settings = MagicMock()
3030
mock_settings.transport_mode = mode
@@ -47,8 +47,8 @@ def _test_http_app_mode(
4747
session_manager = cast(StreamableHTTPSessionManager, endpoint.session_manager)
4848
assert session_manager.stateless == stateless_http
4949
else:
50-
# stateless setting doesn't apply to sse mode - the prop isn't accessible in any
51-
# meaningful sense when http_app is initialized using sse.
50+
# stateless setting doesn't apply to sse or stdio modes - for stdio the http_app
51+
# falls back to sse, and for sse the prop isn't accessible in any meaningful sense.
5252
pass
5353

5454
def test_server_name(self) -> None:
@@ -70,9 +70,11 @@ def test_http_app_permutations(self) -> None:
7070
self._test_http_app_mode("http", stateless_http=True)
7171
self._test_http_app_mode("sse", stateless_http=True)
7272
self._test_http_app_mode("streamable-http", stateless_http=True)
73+
self._test_http_app_mode("stdio", stateless_http=True)
7374
self._test_http_app_mode("http", stateless_http=False)
7475
self._test_http_app_mode("sse", stateless_http=False)
7576
self._test_http_app_mode("streamable-http", stateless_http=False)
77+
self._test_http_app_mode("stdio", stateless_http=False)
7678

7779

7880
class TestHealthEndpoint:

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)