fenix-mcp 1.11.1__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.11.1 → fenix_mcp-1.13.0}/PKG-INFO +1 -1
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/__init__.py +1 -1
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/initialize.py +19 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/intelligence.py +7 -1
- {fenix_mcp-1.11.1 → 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.11.1 → fenix_mcp-1.13.0}/fenix_mcp/interface/transports.py +11 -1
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/SOURCES.txt +1 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/README.md +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/presenters.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tool_base.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tool_registry.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/__init__.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/health.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/knowledge.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/productivity.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/application/tools/user_config.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/domain/initialization.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/domain/intelligence.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/domain/knowledge.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/domain/productivity.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/domain/user_config.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/config.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/context.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/fenix_api/client.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/infrastructure/logging.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/interface/mcp_server.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp/main.py +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/requires.txt +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/fenix_mcp.egg-info/top_level.txt +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/pyproject.toml +0 -0
- {fenix_mcp-1.11.1 → fenix_mcp-1.13.0}/setup.cfg +0 -0
|
@@ -124,6 +124,25 @@ class InitializeTool(Tool):
|
|
|
124
124
|
]
|
|
125
125
|
)
|
|
126
126
|
|
|
127
|
+
# Add memory usage instructions
|
|
128
|
+
message_lines.extend(
|
|
129
|
+
[
|
|
130
|
+
"",
|
|
131
|
+
"🧠 **Memory Usage Instructions**",
|
|
132
|
+
"",
|
|
133
|
+
"To provide continuity across sessions, use the `intelligence` tool:",
|
|
134
|
+
"",
|
|
135
|
+
'1. **At conversation START**: Call `intelligence(action="memory_query", query="<relevant_topic>")` to retrieve context from previous sessions',
|
|
136
|
+
'2. **When learning important info**: Call `intelligence(action="memory_smart_create", ...)` to save:',
|
|
137
|
+
" - User preferences and decisions",
|
|
138
|
+
" - Project context and architecture choices",
|
|
139
|
+
" - Problems solved and solutions found",
|
|
140
|
+
" - Technical learnings and patterns",
|
|
141
|
+
"",
|
|
142
|
+
"This prevents users from repeating themselves and enables personalized assistance.",
|
|
143
|
+
]
|
|
144
|
+
)
|
|
145
|
+
|
|
127
146
|
return text("\n".join(message_lines))
|
|
128
147
|
|
|
129
148
|
async def _handle_setup(self, payload: InitializeRequest):
|
|
@@ -172,7 +172,13 @@ class IntelligenceRequest(ToolRequest):
|
|
|
172
172
|
|
|
173
173
|
class IntelligenceTool(Tool):
|
|
174
174
|
name = "intelligence"
|
|
175
|
-
description =
|
|
175
|
+
description = (
|
|
176
|
+
"Fenix persistent memory system for cross-session continuity. "
|
|
177
|
+
"RECOMMENDED USAGE: "
|
|
178
|
+
"(1) Call memory_query at the START of conversations to retrieve relevant context from previous sessions. "
|
|
179
|
+
"(2) Call memory_smart_create to save important information, decisions, user preferences, and learnings. "
|
|
180
|
+
"This enables personalized responses and prevents users from repeating themselves."
|
|
181
|
+
)
|
|
176
182
|
request_model = IntelligenceRequest
|
|
177
183
|
|
|
178
184
|
def __init__(self, context: AppContext):
|
|
@@ -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
|