fenix-mcp 0.3.0__tar.gz → 0.5.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.
Files changed (34) hide show
  1. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/PKG-INFO +1 -1
  2. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/__init__.py +1 -1
  3. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tools/intelligence.py +28 -3
  4. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/domain/intelligence.py +121 -10
  5. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
  6. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/README.md +0 -0
  7. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/presenters.py +0 -0
  8. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tool_base.py +0 -0
  9. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tool_registry.py +0 -0
  10. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tools/__init__.py +0 -0
  11. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tools/health.py +0 -0
  12. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tools/initialize.py +0 -0
  13. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tools/knowledge.py +0 -0
  14. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tools/productivity.py +0 -0
  15. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/application/tools/user_config.py +0 -0
  16. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/domain/initialization.py +0 -0
  17. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/domain/knowledge.py +0 -0
  18. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/domain/productivity.py +0 -0
  19. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/domain/user_config.py +0 -0
  20. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/infrastructure/config.py +0 -0
  21. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/infrastructure/context.py +0 -0
  22. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/infrastructure/fenix_api/client.py +0 -0
  23. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/infrastructure/http_client.py +0 -0
  24. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/infrastructure/logging.py +0 -0
  25. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/interface/mcp_server.py +0 -0
  26. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/interface/transports.py +0 -0
  27. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp/main.py +0 -0
  28. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp.egg-info/SOURCES.txt +0 -0
  29. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
  30. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
  31. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp.egg-info/requires.txt +0 -0
  32. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/fenix_mcp.egg-info/top_level.txt +0 -0
  33. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/pyproject.toml +0 -0
  34. {fenix_mcp-0.3.0 → fenix_mcp-0.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fenix-mcp
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Fênix Cloud MCP server implemented in Python
5
5
  Author: Fenix Inc
6
6
  Requires-Python: >=3.10
@@ -17,4 +17,4 @@ Updated with improved error handling and better documentation.
17
17
 
18
18
  __all__ = ["__version__"]
19
19
 
20
- __version__ = "0.3.0"
20
+ __version__ = "0.5.0"
@@ -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="medium", description="Nível de importância da memória."
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,
@@ -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
- metadata_parts: List[str] = []
29
- if context:
30
- metadata_parts.append(f"Contexto: {context}")
31
- if source:
32
- metadata_parts.append(f"Fonte: {source}")
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": "\n".join(metadata_parts) if metadata_parts else None,
38
- "priority_score": _importance_to_priority(importance),
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 = bool(params.pop("content", False))
46
- include_metadata = bool(params.pop("metadata", False))
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fenix-mcp
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Fênix Cloud MCP server implemented in Python
5
5
  Author: Fenix Inc
6
6
  Requires-Python: >=3.10
File without changes
File without changes
File without changes
File without changes