fenix-mcp 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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/__init__.py +1 -1
- fenix_mcp/application/tools/intelligence.py +28 -3
- fenix_mcp/domain/intelligence.py +121 -10
- {fenix_mcp-0.3.0.dist-info → fenix_mcp-0.5.0.dist-info}/METADATA +1 -1
- {fenix_mcp-0.3.0.dist-info → fenix_mcp-0.5.0.dist-info}/RECORD +8 -8
- {fenix_mcp-0.3.0.dist-info → fenix_mcp-0.5.0.dist-info}/WHEEL +0 -0
- {fenix_mcp-0.3.0.dist-info → fenix_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
- {fenix_mcp-0.3.0.dist-info → fenix_mcp-0.5.0.dist-info}/top_level.txt +0 -0
fenix_mcp/__init__.py
CHANGED
|
@@ -10,7 +10,7 @@ from pydantic import Field
|
|
|
10
10
|
|
|
11
11
|
from fenix_mcp.application.presenters import text
|
|
12
12
|
from fenix_mcp.application.tool_base import Tool, ToolRequest
|
|
13
|
-
from fenix_mcp.domain.intelligence import IntelligenceService
|
|
13
|
+
from fenix_mcp.domain.intelligence import IntelligenceService, build_metadata
|
|
14
14
|
from fenix_mcp.infrastructure.context import AppContext
|
|
15
15
|
|
|
16
16
|
|
|
@@ -66,10 +66,22 @@ class IntelligenceRequest(ToolRequest):
|
|
|
66
66
|
content: Optional[str] = Field(
|
|
67
67
|
default=None, description="Conteúdo/texto da memória."
|
|
68
68
|
)
|
|
69
|
+
metadata: Optional[str] = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="Metadata estruturada da memória (formato pipe, toml compacto, etc.).",
|
|
72
|
+
)
|
|
69
73
|
context: Optional[str] = Field(default=None, description="Contexto adicional.")
|
|
70
74
|
source: Optional[str] = Field(default=None, description="Fonte da memória.")
|
|
71
|
-
importance: str = Field(
|
|
72
|
-
default=
|
|
75
|
+
importance: Optional[str] = Field(
|
|
76
|
+
default=None, description="Nível de importância da memória."
|
|
77
|
+
)
|
|
78
|
+
include_content: bool = Field(
|
|
79
|
+
default=False,
|
|
80
|
+
description="Retornar conteúdo completo das memórias? Defina true para incluir o texto integral.",
|
|
81
|
+
)
|
|
82
|
+
include_metadata: bool = Field(
|
|
83
|
+
default=False,
|
|
84
|
+
description="Retornar metadata completa das memórias? Defina true para incluir o campo bruto.",
|
|
73
85
|
)
|
|
74
86
|
tags: Optional[List[str]] = Field(default=None, description="Tags da memória.")
|
|
75
87
|
limit: int = Field(default=20, ge=1, le=100, description="Limite de resultados.")
|
|
@@ -146,6 +158,7 @@ class IntelligenceTool(Tool):
|
|
|
146
158
|
memory = await self._service.smart_create_memory(
|
|
147
159
|
title=payload.title,
|
|
148
160
|
content=payload.content,
|
|
161
|
+
metadata=payload.metadata,
|
|
149
162
|
context=payload.context,
|
|
150
163
|
source=payload.source,
|
|
151
164
|
importance=payload.importance,
|
|
@@ -167,6 +180,8 @@ class IntelligenceTool(Tool):
|
|
|
167
180
|
offset=payload.offset,
|
|
168
181
|
query=payload.query,
|
|
169
182
|
tags=payload.tags,
|
|
183
|
+
include_content=payload.include_content,
|
|
184
|
+
include_metadata=payload.include_metadata,
|
|
170
185
|
modeId=payload.mode_id,
|
|
171
186
|
ruleId=payload.rule_id,
|
|
172
187
|
workItemId=payload.work_item_id,
|
|
@@ -241,9 +256,19 @@ class IntelligenceTool(Tool):
|
|
|
241
256
|
async def _handle_update(self, payload: IntelligenceRequest):
|
|
242
257
|
if not payload.id:
|
|
243
258
|
return text("❌ Informe o ID da memória para atualização.")
|
|
259
|
+
existing = await self._service.get_memory(
|
|
260
|
+
payload.id, include_content=False, include_metadata=True
|
|
261
|
+
)
|
|
262
|
+
metadata = build_metadata(
|
|
263
|
+
payload.metadata,
|
|
264
|
+
importance=payload.importance,
|
|
265
|
+
tags=payload.tags,
|
|
266
|
+
existing=existing.get("metadata") if isinstance(existing, dict) else None,
|
|
267
|
+
)
|
|
244
268
|
update_fields: Dict[str, Any] = {
|
|
245
269
|
"title": payload.title,
|
|
246
270
|
"content": payload.content,
|
|
271
|
+
"metadata": metadata,
|
|
247
272
|
"tags": payload.tags,
|
|
248
273
|
"documentation_item_id": payload.documentation_item_id,
|
|
249
274
|
"mode_id": payload.mode_id,
|
fenix_mcp/domain/intelligence.py
CHANGED
|
@@ -20,30 +20,37 @@ class IntelligenceService:
|
|
|
20
20
|
*,
|
|
21
21
|
title: str,
|
|
22
22
|
content: str,
|
|
23
|
+
metadata: Optional[str],
|
|
23
24
|
context: Optional[str],
|
|
24
25
|
source: Optional[str],
|
|
25
26
|
importance: str,
|
|
26
27
|
tags: Optional[Iterable[str]] = None,
|
|
27
28
|
) -> Dict[str, Any]:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
importance_value = importance or "medium"
|
|
30
|
+
metadata_str = build_metadata(
|
|
31
|
+
metadata,
|
|
32
|
+
importance=importance_value,
|
|
33
|
+
tags=tags,
|
|
34
|
+
context=context,
|
|
35
|
+
source=source,
|
|
36
|
+
)
|
|
34
37
|
payload = {
|
|
35
38
|
"title": title,
|
|
36
39
|
"content": content,
|
|
37
|
-
"metadata":
|
|
38
|
-
"priority_score": _importance_to_priority(
|
|
40
|
+
"metadata": metadata_str,
|
|
41
|
+
"priority_score": _importance_to_priority(importance_value),
|
|
39
42
|
"tags": list(tags) if tags else None,
|
|
40
43
|
}
|
|
41
44
|
return await self._call(self.api.smart_create_memory, _strip_none(payload))
|
|
42
45
|
|
|
43
46
|
async def query_memories(self, **filters: Any) -> List[Dict[str, Any]]:
|
|
44
47
|
params = _strip_none(filters)
|
|
45
|
-
include_content =
|
|
46
|
-
|
|
48
|
+
include_content = _coerce_bool(
|
|
49
|
+
params.pop("include_content", params.pop("content", None))
|
|
50
|
+
)
|
|
51
|
+
include_metadata = _coerce_bool(
|
|
52
|
+
params.pop("include_metadata", params.pop("metadata", None))
|
|
53
|
+
)
|
|
47
54
|
allowed_keys = {
|
|
48
55
|
"limit",
|
|
49
56
|
"offset",
|
|
@@ -141,6 +148,20 @@ class IntelligenceService:
|
|
|
141
148
|
async def _call(self, func, *args, **kwargs):
|
|
142
149
|
return await asyncio.to_thread(func, *args, **kwargs)
|
|
143
150
|
|
|
151
|
+
async def get_memory(
|
|
152
|
+
self,
|
|
153
|
+
memory_id: str,
|
|
154
|
+
*,
|
|
155
|
+
include_content: bool = False,
|
|
156
|
+
include_metadata: bool = False,
|
|
157
|
+
) -> Dict[str, Any]:
|
|
158
|
+
return await self._call(
|
|
159
|
+
self.api.get_memory,
|
|
160
|
+
memory_id,
|
|
161
|
+
include_content=include_content,
|
|
162
|
+
include_metadata=include_metadata,
|
|
163
|
+
)
|
|
164
|
+
|
|
144
165
|
|
|
145
166
|
def _importance_to_priority(importance: Optional[str]) -> float:
|
|
146
167
|
mapping = {
|
|
@@ -154,5 +175,95 @@ def _importance_to_priority(importance: Optional[str]) -> float:
|
|
|
154
175
|
return mapping.get(importance.lower(), 0.5)
|
|
155
176
|
|
|
156
177
|
|
|
178
|
+
def _coerce_bool(value: Any, default: bool = False) -> bool:
|
|
179
|
+
if value is None or value == "":
|
|
180
|
+
return default
|
|
181
|
+
if isinstance(value, bool):
|
|
182
|
+
return value
|
|
183
|
+
if isinstance(value, (int, float)):
|
|
184
|
+
return bool(value)
|
|
185
|
+
if isinstance(value, str):
|
|
186
|
+
normalized = value.strip().lower()
|
|
187
|
+
if normalized in {"true", "1", "yes", "y"}:
|
|
188
|
+
return True
|
|
189
|
+
if normalized in {"false", "0", "no", "n"}:
|
|
190
|
+
return False
|
|
191
|
+
return bool(value)
|
|
192
|
+
|
|
193
|
+
|
|
157
194
|
def _strip_none(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
158
195
|
return {key: value for key, value in data.items() if value not in (None, "")}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def build_metadata(
|
|
199
|
+
explicit: Optional[str],
|
|
200
|
+
*,
|
|
201
|
+
importance: Optional[str],
|
|
202
|
+
tags: Optional[Iterable[str]],
|
|
203
|
+
context: Optional[str] = None,
|
|
204
|
+
source: Optional[str] = None,
|
|
205
|
+
existing: Optional[str] = None,
|
|
206
|
+
) -> str:
|
|
207
|
+
if explicit and explicit.strip():
|
|
208
|
+
return explicit.strip()
|
|
209
|
+
|
|
210
|
+
existing_map = _parse_metadata(existing) if existing else {}
|
|
211
|
+
metadata_map: Dict[str, str] = {}
|
|
212
|
+
|
|
213
|
+
metadata_map["t"] = existing_map.get("t", "memory")
|
|
214
|
+
metadata_map["src"] = _slugify(source) if source else existing_map.get("src", "mcp")
|
|
215
|
+
|
|
216
|
+
ctx_value = _slugify(context) if context else existing_map.get("ctx")
|
|
217
|
+
if ctx_value:
|
|
218
|
+
metadata_map["ctx"] = ctx_value
|
|
219
|
+
|
|
220
|
+
priority_key = importance.lower() if importance else existing_map.get("p")
|
|
221
|
+
if priority_key:
|
|
222
|
+
metadata_map["p"] = priority_key
|
|
223
|
+
|
|
224
|
+
tag_string = _format_tags(tags)
|
|
225
|
+
if tag_string:
|
|
226
|
+
metadata_map["tags"] = tag_string
|
|
227
|
+
elif "tags" in existing_map:
|
|
228
|
+
metadata_map["tags"] = existing_map["tags"]
|
|
229
|
+
|
|
230
|
+
for key, value in existing_map.items():
|
|
231
|
+
if key not in metadata_map:
|
|
232
|
+
metadata_map[key] = value
|
|
233
|
+
|
|
234
|
+
if not metadata_map:
|
|
235
|
+
metadata_map["t"] = "memory"
|
|
236
|
+
metadata_map["src"] = "mcp"
|
|
237
|
+
|
|
238
|
+
return "|".join(f"{key}:{metadata_map[key]}" for key in metadata_map)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _parse_metadata(metadata: str) -> Dict[str, str]:
|
|
242
|
+
items = {}
|
|
243
|
+
for entry in metadata.split("|"):
|
|
244
|
+
if ":" not in entry:
|
|
245
|
+
continue
|
|
246
|
+
key, value = entry.split(":", 1)
|
|
247
|
+
key = key.strip()
|
|
248
|
+
value = value.strip()
|
|
249
|
+
if key:
|
|
250
|
+
items[key] = value
|
|
251
|
+
return items
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _slugify(value: Optional[str]) -> str:
|
|
255
|
+
if not value:
|
|
256
|
+
return ""
|
|
257
|
+
sanitized = value.replace("|", " ").replace(":", " ")
|
|
258
|
+
return "-".join(part for part in sanitized.split() if part).lower()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _format_tags(tags: Optional[Iterable[str]]) -> str:
|
|
262
|
+
if not tags:
|
|
263
|
+
return ""
|
|
264
|
+
normalized = {
|
|
265
|
+
_slugify(tag) for tag in tags if isinstance(tag, str) and _slugify(tag)
|
|
266
|
+
}
|
|
267
|
+
if not normalized:
|
|
268
|
+
return ""
|
|
269
|
+
return ",".join(sorted(normalized))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
fenix_mcp/__init__.py,sha256=
|
|
1
|
+
fenix_mcp/__init__.py,sha256=TUKG6aTaYq_EQ3TiKSaS-m7s_p9d5sURwutx4MxHVvQ,615
|
|
2
2
|
fenix_mcp/main.py,sha256=iJV-9btNMDJMObvcn7wBQdbLLKjkYCQ1ANGEwHGHlMU,2857
|
|
3
3
|
fenix_mcp/application/presenters.py,sha256=fGME54PdCDhTBhXO-JUB9yLdBHiE1aeXLTC2fCuxnxM,689
|
|
4
4
|
fenix_mcp/application/tool_base.py,sha256=qUcb46qx9gHQfrSHgj4RD4NCHW-OIvKQdR5G9uxZ5l4,1316
|
|
@@ -6,12 +6,12 @@ fenix_mcp/application/tool_registry.py,sha256=bPT5g8GfxG_qu28R1WaDOZHvtmG6TPDvZi
|
|
|
6
6
|
fenix_mcp/application/tools/__init__.py,sha256=Gi1YvYh-KdL9HD8gLVrknHrxiKKEOhHBEZ02KBXJaKQ,796
|
|
7
7
|
fenix_mcp/application/tools/health.py,sha256=m5DxhoRbdwl6INzd6PISxv1NAv-ljCrezsr773VB0wE,834
|
|
8
8
|
fenix_mcp/application/tools/initialize.py,sha256=f33DNDn9u8IYwpqiBj6bjJ-wHgaUP1zEuAvUM1rMYPc,4674
|
|
9
|
-
fenix_mcp/application/tools/intelligence.py,sha256=
|
|
9
|
+
fenix_mcp/application/tools/intelligence.py,sha256=dyHjEdEW4PksDARcdPoYhy4fcu3gneWMnI_SjNzPBNo,13380
|
|
10
10
|
fenix_mcp/application/tools/knowledge.py,sha256=4ClGoFRqyFIPuzzg2DAg-w2eMvTP37mH0THXDGftinw,44634
|
|
11
11
|
fenix_mcp/application/tools/productivity.py,sha256=2IMkNdZ-Kd1CFAO7geruAVjtf_BWoDdbnwkl76vhtC8,9973
|
|
12
12
|
fenix_mcp/application/tools/user_config.py,sha256=8mPOZuwszO0TapxgrA7Foe15VQE874_mvfQYlGzyv4Y,6230
|
|
13
13
|
fenix_mcp/domain/initialization.py,sha256=AZhdSNITQ7O3clELBuqGvjJc-c8pFKc7zQz-XR2xXPc,6933
|
|
14
|
-
fenix_mcp/domain/intelligence.py,sha256=
|
|
14
|
+
fenix_mcp/domain/intelligence.py,sha256=Z2k7mrfNlUgIV_rw6suukS3TU3kfAQjv5-ZSvcRZXi4,8246
|
|
15
15
|
fenix_mcp/domain/knowledge.py,sha256=fKQOTt20u5aa5Yo7gPeQ1Qxa_K5pBxn1yn8FEfOFltM,20241
|
|
16
16
|
fenix_mcp/domain/productivity.py,sha256=nmHRuVJGRRu1s4eMoAv8vXHKsSauCPl-FvFx3I_yCTE,6661
|
|
17
17
|
fenix_mcp/domain/user_config.py,sha256=LzBDCk31gLMtKHTbBmYb9VoFPHDW6OydpmDXeHHd0Mw,1642
|
|
@@ -22,8 +22,8 @@ fenix_mcp/infrastructure/logging.py,sha256=bHrWlSi_0HshRe3--BK_5nzUszW-gh37q6jsd
|
|
|
22
22
|
fenix_mcp/infrastructure/fenix_api/client.py,sha256=Navi7cGAOradghcbYkFIQQINpjFrdINlNhdfZ4iSSYQ,28338
|
|
23
23
|
fenix_mcp/interface/mcp_server.py,sha256=5UM2NJuNbwHkmCEprIFataJ5nFZiO8efTtP_oW3_iX0,2331
|
|
24
24
|
fenix_mcp/interface/transports.py,sha256=PxdhfjH8UMl03f7nuCLc-M6tMx6-Y-btVz_mSqXKrSI,8138
|
|
25
|
-
fenix_mcp-0.
|
|
26
|
-
fenix_mcp-0.
|
|
27
|
-
fenix_mcp-0.
|
|
28
|
-
fenix_mcp-0.
|
|
29
|
-
fenix_mcp-0.
|
|
25
|
+
fenix_mcp-0.5.0.dist-info/METADATA,sha256=CJDz4x6SYkyBzpIPXhk3WEjT1vamMXFp2yY5jqAhpRE,7260
|
|
26
|
+
fenix_mcp-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
+
fenix_mcp-0.5.0.dist-info/entry_points.txt,sha256=o52x_YHBupEd-1Z1GSfUjv3gJrx5_I-EkHhCgt1WBaE,49
|
|
28
|
+
fenix_mcp-0.5.0.dist-info/top_level.txt,sha256=2G1UtKpwjaIGQyE7sRoHecxaGLeuexfjrOUjv9DDKh4,10
|
|
29
|
+
fenix_mcp-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|