fenix-mcp 0.4.0__py3-none-any.whl → 0.5.1__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 +34 -5
- fenix_mcp/domain/intelligence.py +122 -11
- {fenix_mcp-0.4.0.dist-info → fenix_mcp-0.5.1.dist-info}/METADATA +1 -1
- {fenix_mcp-0.4.0.dist-info → fenix_mcp-0.5.1.dist-info}/RECORD +8 -8
- {fenix_mcp-0.4.0.dist-info → fenix_mcp-0.5.1.dist-info}/WHEEL +0 -0
- {fenix_mcp-0.4.0.dist-info → fenix_mcp-0.5.1.dist-info}/entry_points.txt +0 -0
- {fenix_mcp-0.4.0.dist-info → fenix_mcp-0.5.1.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,14 @@ 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."
|
|
73
77
|
)
|
|
74
78
|
include_content: bool = Field(
|
|
75
79
|
default=False,
|
|
@@ -151,13 +155,15 @@ class IntelligenceTool(Tool):
|
|
|
151
155
|
async def _handle_smart_create(self, payload: IntelligenceRequest):
|
|
152
156
|
if not payload.title or not payload.content:
|
|
153
157
|
return text("❌ Informe título e conteúdo para criar uma memória.")
|
|
158
|
+
normalized_tags = _ensure_tag_sequence(payload.tags)
|
|
154
159
|
memory = await self._service.smart_create_memory(
|
|
155
160
|
title=payload.title,
|
|
156
161
|
content=payload.content,
|
|
162
|
+
metadata=payload.metadata,
|
|
157
163
|
context=payload.context,
|
|
158
164
|
source=payload.source,
|
|
159
165
|
importance=payload.importance,
|
|
160
|
-
tags=
|
|
166
|
+
tags=normalized_tags,
|
|
161
167
|
)
|
|
162
168
|
lines = [
|
|
163
169
|
"🧠 **Memória criada com sucesso!**",
|
|
@@ -251,10 +257,21 @@ class IntelligenceTool(Tool):
|
|
|
251
257
|
async def _handle_update(self, payload: IntelligenceRequest):
|
|
252
258
|
if not payload.id:
|
|
253
259
|
return text("❌ Informe o ID da memória para atualização.")
|
|
260
|
+
existing = await self._service.get_memory(
|
|
261
|
+
payload.id, include_content=False, include_metadata=True
|
|
262
|
+
)
|
|
263
|
+
normalized_tags = _ensure_tag_sequence(payload.tags)
|
|
264
|
+
metadata = build_metadata(
|
|
265
|
+
payload.metadata,
|
|
266
|
+
importance=payload.importance,
|
|
267
|
+
tags=normalized_tags,
|
|
268
|
+
existing=existing.get("metadata") if isinstance(existing, dict) else None,
|
|
269
|
+
)
|
|
254
270
|
update_fields: Dict[str, Any] = {
|
|
255
271
|
"title": payload.title,
|
|
256
272
|
"content": payload.content,
|
|
257
|
-
"
|
|
273
|
+
"metadata": metadata,
|
|
274
|
+
"tags": normalized_tags,
|
|
258
275
|
"documentation_item_id": payload.documentation_item_id,
|
|
259
276
|
"mode_id": payload.mode_id,
|
|
260
277
|
"rule_id": payload.rule_id,
|
|
@@ -298,3 +315,15 @@ def format_percentage(value: Optional[float]) -> str:
|
|
|
298
315
|
if value is None:
|
|
299
316
|
return "N/A"
|
|
300
317
|
return f"{value * 100:.1f}%"
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _ensure_tag_sequence(raw: Optional[Any]) -> Optional[List[str]]:
|
|
321
|
+
if raw is None or raw == "":
|
|
322
|
+
return None
|
|
323
|
+
if isinstance(raw, (list, tuple, set)):
|
|
324
|
+
result = [str(item).strip() for item in raw if str(item).strip()]
|
|
325
|
+
return result or None
|
|
326
|
+
if isinstance(raw, str):
|
|
327
|
+
items = [part.strip() for part in raw.split(",") if part.strip()]
|
|
328
|
+
return items or None
|
|
329
|
+
return [str(raw).strip()]
|
fenix_mcp/domain/intelligence.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
-
from typing import Any, Dict, Iterable, List, Optional
|
|
8
|
+
from typing import Any, Dict, Iterable, List, Optional, Union
|
|
9
9
|
|
|
10
10
|
from fenix_mcp.infrastructure.fenix_api.client import FenixApiClient
|
|
11
11
|
|
|
@@ -20,23 +20,27 @@ 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
|
-
tags: Optional[Iterable[str]] = None,
|
|
27
|
+
tags: Optional[Union[Iterable[str], str]] = None,
|
|
27
28
|
) -> Dict[str, Any]:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
importance_value = importance or "medium"
|
|
30
|
+
normalized_tags = normalize_tags(tags)
|
|
31
|
+
metadata_str = build_metadata(
|
|
32
|
+
metadata,
|
|
33
|
+
importance=importance_value,
|
|
34
|
+
tags=normalized_tags,
|
|
35
|
+
context=context,
|
|
36
|
+
source=source,
|
|
37
|
+
)
|
|
34
38
|
payload = {
|
|
35
39
|
"title": title,
|
|
36
40
|
"content": content,
|
|
37
|
-
"metadata":
|
|
38
|
-
"priority_score": _importance_to_priority(
|
|
39
|
-
"tags":
|
|
41
|
+
"metadata": metadata_str,
|
|
42
|
+
"priority_score": _importance_to_priority(importance_value),
|
|
43
|
+
"tags": normalized_tags,
|
|
40
44
|
}
|
|
41
45
|
return await self._call(self.api.smart_create_memory, _strip_none(payload))
|
|
42
46
|
|
|
@@ -145,6 +149,20 @@ class IntelligenceService:
|
|
|
145
149
|
async def _call(self, func, *args, **kwargs):
|
|
146
150
|
return await asyncio.to_thread(func, *args, **kwargs)
|
|
147
151
|
|
|
152
|
+
async def get_memory(
|
|
153
|
+
self,
|
|
154
|
+
memory_id: str,
|
|
155
|
+
*,
|
|
156
|
+
include_content: bool = False,
|
|
157
|
+
include_metadata: bool = False,
|
|
158
|
+
) -> Dict[str, Any]:
|
|
159
|
+
return await self._call(
|
|
160
|
+
self.api.get_memory,
|
|
161
|
+
memory_id,
|
|
162
|
+
include_content=include_content,
|
|
163
|
+
include_metadata=include_metadata,
|
|
164
|
+
)
|
|
165
|
+
|
|
148
166
|
|
|
149
167
|
def _importance_to_priority(importance: Optional[str]) -> float:
|
|
150
168
|
mapping = {
|
|
@@ -176,3 +194,96 @@ def _coerce_bool(value: Any, default: bool = False) -> bool:
|
|
|
176
194
|
|
|
177
195
|
def _strip_none(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
178
196
|
return {key: value for key, value in data.items() if value not in (None, "")}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def build_metadata(
|
|
200
|
+
explicit: Optional[str],
|
|
201
|
+
*,
|
|
202
|
+
importance: Optional[str],
|
|
203
|
+
tags: Optional[Union[Iterable[str], str]],
|
|
204
|
+
context: Optional[str] = None,
|
|
205
|
+
source: Optional[str] = None,
|
|
206
|
+
existing: Optional[str] = None,
|
|
207
|
+
) -> str:
|
|
208
|
+
if explicit and explicit.strip():
|
|
209
|
+
return explicit.strip()
|
|
210
|
+
|
|
211
|
+
existing_map = _parse_metadata(existing) if existing else {}
|
|
212
|
+
metadata_map: Dict[str, str] = {}
|
|
213
|
+
|
|
214
|
+
metadata_map["t"] = existing_map.get("t", "memory")
|
|
215
|
+
metadata_map["src"] = _slugify(source) if source else existing_map.get("src", "mcp")
|
|
216
|
+
|
|
217
|
+
ctx_value = _slugify(context) if context else existing_map.get("ctx")
|
|
218
|
+
if ctx_value:
|
|
219
|
+
metadata_map["ctx"] = ctx_value
|
|
220
|
+
|
|
221
|
+
priority_key = importance.lower() if importance else existing_map.get("p")
|
|
222
|
+
if priority_key:
|
|
223
|
+
metadata_map["p"] = priority_key
|
|
224
|
+
|
|
225
|
+
tag_string = _format_tags(tags)
|
|
226
|
+
if tag_string:
|
|
227
|
+
metadata_map["tags"] = tag_string
|
|
228
|
+
elif "tags" in existing_map:
|
|
229
|
+
metadata_map["tags"] = existing_map["tags"]
|
|
230
|
+
|
|
231
|
+
for key, value in existing_map.items():
|
|
232
|
+
if key not in metadata_map:
|
|
233
|
+
metadata_map[key] = value
|
|
234
|
+
|
|
235
|
+
if not metadata_map:
|
|
236
|
+
metadata_map["t"] = "memory"
|
|
237
|
+
metadata_map["src"] = "mcp"
|
|
238
|
+
|
|
239
|
+
return "|".join(f"{key}:{metadata_map[key]}" for key in metadata_map)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _parse_metadata(metadata: str) -> Dict[str, str]:
|
|
243
|
+
items = {}
|
|
244
|
+
for entry in metadata.split("|"):
|
|
245
|
+
if ":" not in entry:
|
|
246
|
+
continue
|
|
247
|
+
key, value = entry.split(":", 1)
|
|
248
|
+
key = key.strip()
|
|
249
|
+
value = value.strip()
|
|
250
|
+
if key:
|
|
251
|
+
items[key] = value
|
|
252
|
+
return items
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _slugify(value: Optional[str]) -> str:
|
|
256
|
+
if not value:
|
|
257
|
+
return ""
|
|
258
|
+
sanitized = value.replace("|", " ").replace(":", " ")
|
|
259
|
+
return "-".join(part for part in sanitized.split() if part).lower()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _format_tags(tags: Optional[Union[Iterable[str], str]]) -> str:
|
|
263
|
+
if tags is None or tags == "":
|
|
264
|
+
return ""
|
|
265
|
+
if isinstance(tags, str):
|
|
266
|
+
iterable: Iterable[str] = [part.strip() for part in tags.split(",")]
|
|
267
|
+
else:
|
|
268
|
+
iterable = tags
|
|
269
|
+
normalized = {
|
|
270
|
+
_slugify(tag) for tag in iterable if isinstance(tag, str) and _slugify(tag)
|
|
271
|
+
}
|
|
272
|
+
if not normalized:
|
|
273
|
+
return ""
|
|
274
|
+
return ",".join(sorted(normalized))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def normalize_tags(tags: Optional[Union[Iterable[str], str]]) -> Optional[List[str]]:
|
|
278
|
+
if tags is None or tags == "":
|
|
279
|
+
return None
|
|
280
|
+
if isinstance(tags, str):
|
|
281
|
+
parts = [part.strip() for part in tags.split(",") if part.strip()]
|
|
282
|
+
else:
|
|
283
|
+
parts = [str(part).strip() for part in tags if str(part).strip()]
|
|
284
|
+
if not parts:
|
|
285
|
+
return None
|
|
286
|
+
slugged = [_slugify(part) for part in parts if _slugify(part)]
|
|
287
|
+
if not slugged:
|
|
288
|
+
return None
|
|
289
|
+
return sorted(set(slugged))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
fenix_mcp/__init__.py,sha256=
|
|
1
|
+
fenix_mcp/__init__.py,sha256=Nle0-p8uRDw1ypGPxnoKnAY0Mv0N8_6glE1whGPNnlw,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=DNWGu6NhPYZXahnHDVHIgYYdWn_jCP-Nw9FwS_Tat34,13945
|
|
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=IyZUA_fbCHDgHaWCgxkahXAaAT5qQlaz5AT7Cw2Gwko,9006
|
|
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.1.dist-info/METADATA,sha256=yvg8J1G-jemzju-BKR2tMd8Y_f5KKjHX6GrNQIONjR0,7260
|
|
26
|
+
fenix_mcp-0.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
+
fenix_mcp-0.5.1.dist-info/entry_points.txt,sha256=o52x_YHBupEd-1Z1GSfUjv3gJrx5_I-EkHhCgt1WBaE,49
|
|
28
|
+
fenix_mcp-0.5.1.dist-info/top_level.txt,sha256=2G1UtKpwjaIGQyE7sRoHecxaGLeuexfjrOUjv9DDKh4,10
|
|
29
|
+
fenix_mcp-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|