fenix-mcp 1.12.0__tar.gz → 1.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/PKG-INFO +1 -1
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/__init__.py +1 -1
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/http_client.py +7 -0
- fenix_mcp-1.13.0/fenix_mcp/infrastructure/request_context.py +37 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/interface/transports.py +11 -1
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/SOURCES.txt +1 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/README.md +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/presenters.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tool_base.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tool_registry.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/__init__.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/health.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/initialize.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/intelligence.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/knowledge.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/productivity.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/user_config.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/domain/initialization.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/domain/intelligence.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/domain/knowledge.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/domain/productivity.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/domain/user_config.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/config.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/context.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/fenix_api/client.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/logging.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/interface/mcp_server.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp/main.py +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/requires.txt +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/top_level.txt +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/pyproject.toml +0 -0
- {fenix_mcp-1.12.0 → fenix_mcp-1.13.0}/setup.cfg +0 -0
|
@@ -12,6 +12,8 @@ from requests import Response, Session
|
|
|
12
12
|
from requests.adapters import HTTPAdapter
|
|
13
13
|
from urllib3.util.retry import Retry
|
|
14
14
|
|
|
15
|
+
from fenix_mcp.infrastructure.request_context import get_request_token
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
@dataclass(slots=True)
|
|
17
19
|
class HttpClient:
|
|
@@ -53,6 +55,11 @@ class HttpClient:
|
|
|
53
55
|
merged_headers = dict(self.default_headers or {})
|
|
54
56
|
merged_headers.update(headers or {})
|
|
55
57
|
|
|
58
|
+
# Use request-scoped token if available (overrides default)
|
|
59
|
+
request_token = get_request_token()
|
|
60
|
+
if request_token:
|
|
61
|
+
merged_headers["Authorization"] = f"Bearer {request_token}"
|
|
62
|
+
|
|
56
63
|
self._logger.debug(
|
|
57
64
|
"HTTP request",
|
|
58
65
|
extra={
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Request-scoped context using contextvars for async isolation."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
# Context variable for the current request's token
|
|
11
|
+
# This is automatically isolated per async task/request
|
|
12
|
+
_current_token: ContextVar[Optional[str]] = ContextVar("current_token", default=None)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(slots=True, frozen=True)
|
|
16
|
+
class RequestContext:
|
|
17
|
+
"""Holds request-scoped data like the user's token."""
|
|
18
|
+
|
|
19
|
+
token: Optional[str] = None
|
|
20
|
+
user_id: Optional[str] = None
|
|
21
|
+
tenant_id: Optional[str] = None
|
|
22
|
+
team_id: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_request_token(token: Optional[str]) -> None:
|
|
26
|
+
"""Set the token for the current async context/request."""
|
|
27
|
+
_current_token.set(token)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_request_token() -> Optional[str]:
|
|
31
|
+
"""Get the token for the current async context/request."""
|
|
32
|
+
return _current_token.get()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def clear_request_token() -> None:
|
|
36
|
+
"""Clear the token for the current async context/request."""
|
|
37
|
+
_current_token.set(None)
|
|
@@ -14,6 +14,10 @@ from typing import Iterable, List, Protocol
|
|
|
14
14
|
from aiohttp import web
|
|
15
15
|
|
|
16
16
|
from fenix_mcp.infrastructure.config import Settings
|
|
17
|
+
from fenix_mcp.infrastructure.request_context import (
|
|
18
|
+
set_request_token,
|
|
19
|
+
clear_request_token,
|
|
20
|
+
)
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
class Transport(Protocol):
|
|
@@ -147,17 +151,20 @@ class HttpTransport:
|
|
|
147
151
|
return self._with_cors(web.Response(status=204))
|
|
148
152
|
|
|
149
153
|
async def _handle_jsonrpc(self, request: web.Request) -> web.StreamResponse:
|
|
154
|
+
# Extract token from Authorization header (per-request isolation)
|
|
150
155
|
auth_header = request.headers.get("Authorization") or request.headers.get(
|
|
151
156
|
"authorization"
|
|
152
157
|
)
|
|
153
158
|
if auth_header and auth_header.lower().startswith("bearer "):
|
|
154
159
|
token = auth_header.split(" ", 1)[1].strip()
|
|
155
160
|
if token:
|
|
156
|
-
|
|
161
|
+
# Set token in contextvar (isolated per async request)
|
|
162
|
+
set_request_token(token)
|
|
157
163
|
|
|
158
164
|
try:
|
|
159
165
|
payload = await request.json()
|
|
160
166
|
except Exception: # pragma: no cover - defensive
|
|
167
|
+
clear_request_token()
|
|
161
168
|
return self._with_cors(
|
|
162
169
|
web.json_response(
|
|
163
170
|
{"error": {"code": -32700, "message": "Invalid JSON"}}, status=400
|
|
@@ -175,6 +182,9 @@ class HttpTransport:
|
|
|
175
182
|
{"error": {"code": -32000, "message": str(exc)}}, status=500
|
|
176
183
|
)
|
|
177
184
|
)
|
|
185
|
+
finally:
|
|
186
|
+
# Clear token after request completes
|
|
187
|
+
clear_request_token()
|
|
178
188
|
|
|
179
189
|
if response is None:
|
|
180
190
|
return self._with_cors(web.Response(status=204))
|
|
@@ -27,6 +27,7 @@ fenix_mcp/infrastructure/config.py
|
|
|
27
27
|
fenix_mcp/infrastructure/context.py
|
|
28
28
|
fenix_mcp/infrastructure/http_client.py
|
|
29
29
|
fenix_mcp/infrastructure/logging.py
|
|
30
|
+
fenix_mcp/infrastructure/request_context.py
|
|
30
31
|
fenix_mcp/infrastructure/fenix_api/client.py
|
|
31
32
|
fenix_mcp/interface/mcp_server.py
|
|
32
33
|
fenix_mcp/interface/transports.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|