fenix-mcp 0.7.0__py3-none-any.whl → 1.1.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/initialize.py +15 -20
- fenix_mcp/application/tools/intelligence.py +71 -75
- fenix_mcp/application/tools/knowledge.py +284 -313
- fenix_mcp/application/tools/productivity.py +51 -51
- fenix_mcp/application/tools/user_config.py +32 -32
- fenix_mcp/domain/knowledge.py +43 -48
- fenix_mcp/domain/user_config.py +10 -2
- fenix_mcp/infrastructure/fenix_api/client.py +28 -44
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/METADATA +1 -1
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/RECORD +14 -14
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/WHEEL +0 -0
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/entry_points.txt +0 -0
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -30,18 +30,18 @@ class TodoAction(str, Enum):
|
|
|
30
30
|
obj.description = description
|
|
31
31
|
return obj
|
|
32
32
|
|
|
33
|
-
CREATE = ("todo_create", "
|
|
34
|
-
LIST = ("todo_list", "
|
|
35
|
-
GET = ("todo_get", "
|
|
36
|
-
UPDATE = ("todo_update", "
|
|
37
|
-
DELETE = ("todo_delete", "
|
|
38
|
-
STATS = ("todo_stats", "
|
|
39
|
-
SEARCH = ("todo_search", "
|
|
40
|
-
OVERDUE = ("todo_overdue", "
|
|
41
|
-
UPCOMING = ("todo_upcoming", "
|
|
42
|
-
CATEGORIES = ("todo_categories", "
|
|
43
|
-
TAGS = ("todo_tags", "
|
|
44
|
-
HELP = ("todo_help", "
|
|
33
|
+
CREATE = ("todo_create", "Creates a new TODO.")
|
|
34
|
+
LIST = ("todo_list", "Lists TODOs with optional filters.")
|
|
35
|
+
GET = ("todo_get", "Gets TODO details by ID.")
|
|
36
|
+
UPDATE = ("todo_update", "Updates fields of an existing TODO.")
|
|
37
|
+
DELETE = ("todo_delete", "Removes a TODO by ID.")
|
|
38
|
+
STATS = ("todo_stats", "Returns aggregated TODO statistics.")
|
|
39
|
+
SEARCH = ("todo_search", "Searches TODOs by text term.")
|
|
40
|
+
OVERDUE = ("todo_overdue", "Lists overdue TODOs.")
|
|
41
|
+
UPCOMING = ("todo_upcoming", "Lists TODOs with upcoming due dates.")
|
|
42
|
+
CATEGORIES = ("todo_categories", "Lists registered categories.")
|
|
43
|
+
TAGS = ("todo_tags", "Lists registered tags.")
|
|
44
|
+
HELP = ("todo_help", "Shows supported actions and their uses.")
|
|
45
45
|
|
|
46
46
|
@classmethod
|
|
47
47
|
def choices(cls) -> List[str]:
|
|
@@ -50,7 +50,7 @@ class TodoAction(str, Enum):
|
|
|
50
50
|
@classmethod
|
|
51
51
|
def formatted_help(cls) -> str:
|
|
52
52
|
lines = [
|
|
53
|
-
"| **
|
|
53
|
+
"| **Action** | **Description** |",
|
|
54
54
|
"| --- | --- |",
|
|
55
55
|
]
|
|
56
56
|
for member in cls:
|
|
@@ -59,7 +59,7 @@ class TodoAction(str, Enum):
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
ACTION_FIELD_DESCRIPTION = (
|
|
62
|
-
"
|
|
62
|
+
"Productivity action (TODO). Choose one of the values: "
|
|
63
63
|
+ ", ".join(
|
|
64
64
|
f"`{member.value}` ({member.description.rstrip('.')})." for member in TodoAction
|
|
65
65
|
)
|
|
@@ -68,40 +68,40 @@ ACTION_FIELD_DESCRIPTION = (
|
|
|
68
68
|
|
|
69
69
|
class ProductivityRequest(ToolRequest):
|
|
70
70
|
action: TodoAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
71
|
-
id: Optional[UUIDStr] = Field(default=None, description="
|
|
71
|
+
id: Optional[UUIDStr] = Field(default=None, description="TODO item ID (UUID).")
|
|
72
72
|
title: Optional[TitleStr] = Field(
|
|
73
|
-
default=None, description="
|
|
73
|
+
default=None, description="TODO title (required for create)."
|
|
74
74
|
)
|
|
75
75
|
content: Optional[MarkdownStr] = Field(
|
|
76
|
-
default=None, description="
|
|
76
|
+
default=None, description="Markdown content (required for create)."
|
|
77
77
|
)
|
|
78
78
|
status: Optional[str] = Field(
|
|
79
79
|
default=None,
|
|
80
|
-
description="
|
|
80
|
+
description="TODO status (pending, in_progress, completed, cancelled).",
|
|
81
81
|
)
|
|
82
82
|
priority: Optional[str] = Field(
|
|
83
|
-
default=None, description="
|
|
83
|
+
default=None, description="TODO priority (low, medium, high, urgent)."
|
|
84
84
|
)
|
|
85
85
|
category: Optional[CategoryStr] = Field(
|
|
86
|
-
default=None, description="
|
|
86
|
+
default=None, description="Optional category."
|
|
87
87
|
)
|
|
88
|
-
tags: Optional[List[TagStr]] = Field(default=None, description="
|
|
88
|
+
tags: Optional[List[TagStr]] = Field(default=None, description="Tag list.")
|
|
89
89
|
due_date: Optional[DateTimeStr] = Field(
|
|
90
|
-
default=None, description="
|
|
90
|
+
default=None, description="TODO due date (ISO 8601)."
|
|
91
91
|
)
|
|
92
92
|
limit: int = Field(
|
|
93
|
-
default=20, ge=1, le=100, description="
|
|
93
|
+
default=20, ge=1, le=100, description="Result limit for list/search."
|
|
94
94
|
)
|
|
95
|
-
offset: int = Field(default=0, ge=0, description="
|
|
96
|
-
query: Optional[str] = Field(default=None, description="
|
|
95
|
+
offset: int = Field(default=0, ge=0, description="Pagination offset.")
|
|
96
|
+
query: Optional[str] = Field(default=None, description="Search term.")
|
|
97
97
|
days: Optional[int] = Field(
|
|
98
|
-
default=None, ge=1, le=30, description="
|
|
98
|
+
default=None, ge=1, le=30, description="Day window for upcoming."
|
|
99
99
|
)
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
class ProductivityTool(Tool):
|
|
103
103
|
name = "productivity"
|
|
104
|
-
description = "
|
|
104
|
+
description = "Fenix Cloud productivity operations (TODOs)."
|
|
105
105
|
request_model = ProductivityRequest
|
|
106
106
|
|
|
107
107
|
def __init__(self, context: AppContext):
|
|
@@ -135,13 +135,13 @@ class ProductivityTool(Tool):
|
|
|
135
135
|
if action is TodoAction.TAGS:
|
|
136
136
|
return await self._handle_tags()
|
|
137
137
|
return text(
|
|
138
|
-
"❌
|
|
138
|
+
"❌ Invalid action for productivity.\n\nChoose one of the values:\n"
|
|
139
139
|
+ "\n".join(f"- `{value}`" for value in TodoAction.choices())
|
|
140
140
|
)
|
|
141
141
|
|
|
142
142
|
async def _handle_create(self, payload: ProductivityRequest):
|
|
143
143
|
if not payload.title or not payload.content or not payload.due_date:
|
|
144
|
-
return text("❌
|
|
144
|
+
return text("❌ Provide title, content and due_date to create a TODO.")
|
|
145
145
|
todo = await self._service.create_todo(
|
|
146
146
|
title=payload.title,
|
|
147
147
|
content=payload.content,
|
|
@@ -151,7 +151,7 @@ class ProductivityTool(Tool):
|
|
|
151
151
|
tags=payload.tags or [],
|
|
152
152
|
due_date=payload.due_date,
|
|
153
153
|
)
|
|
154
|
-
return text(self._format_single(todo, header="✅ TODO
|
|
154
|
+
return text(self._format_single(todo, header="✅ TODO created successfully!"))
|
|
155
155
|
|
|
156
156
|
async def _handle_list(self, payload: ProductivityRequest):
|
|
157
157
|
todos = await self._service.list_todos(
|
|
@@ -162,19 +162,19 @@ class ProductivityTool(Tool):
|
|
|
162
162
|
category=payload.category,
|
|
163
163
|
)
|
|
164
164
|
if not todos:
|
|
165
|
-
return text("📋
|
|
165
|
+
return text("📋 No TODOs found.")
|
|
166
166
|
body = "\n\n".join(ProductivityService.format_todo(todo) for todo in todos)
|
|
167
167
|
return text(f"📋 **TODOs ({len(todos)}):**\n\n{body}")
|
|
168
168
|
|
|
169
169
|
async def _handle_get(self, payload: ProductivityRequest):
|
|
170
170
|
if not payload.id:
|
|
171
|
-
return text("❌
|
|
171
|
+
return text("❌ Provide the ID to get a TODO.")
|
|
172
172
|
todo = await self._service.get_todo(payload.id)
|
|
173
|
-
return text(self._format_single(todo, header="📋 TODO
|
|
173
|
+
return text(self._format_single(todo, header="📋 TODO found"))
|
|
174
174
|
|
|
175
175
|
async def _handle_update(self, payload: ProductivityRequest):
|
|
176
176
|
if not payload.id:
|
|
177
|
-
return text("❌
|
|
177
|
+
return text("❌ Provide the ID to update a TODO.")
|
|
178
178
|
fields = {
|
|
179
179
|
"title": payload.title,
|
|
180
180
|
"content": payload.content,
|
|
@@ -185,66 +185,66 @@ class ProductivityTool(Tool):
|
|
|
185
185
|
"due_date": payload.due_date,
|
|
186
186
|
}
|
|
187
187
|
todo = await self._service.update_todo(payload.id, **fields)
|
|
188
|
-
return text(self._format_single(todo, header="✅ TODO
|
|
188
|
+
return text(self._format_single(todo, header="✅ TODO updated"))
|
|
189
189
|
|
|
190
190
|
async def _handle_delete(self, payload: ProductivityRequest):
|
|
191
191
|
if not payload.id:
|
|
192
|
-
return text("❌
|
|
192
|
+
return text("❌ Provide the ID to delete a TODO.")
|
|
193
193
|
await self._service.delete_todo(payload.id)
|
|
194
|
-
return text(f"🗑️ TODO {payload.id}
|
|
194
|
+
return text(f"🗑️ TODO {payload.id} removed successfully.")
|
|
195
195
|
|
|
196
196
|
async def _handle_stats(self):
|
|
197
197
|
stats = await self._service.stats()
|
|
198
|
-
lines = ["📊 **
|
|
198
|
+
lines = ["📊 **TODO Statistics**"]
|
|
199
199
|
for key, value in (stats or {}).items():
|
|
200
200
|
lines.append(f"- {key}: {value}")
|
|
201
201
|
return text("\n".join(lines))
|
|
202
202
|
|
|
203
203
|
async def _handle_search(self, payload: ProductivityRequest):
|
|
204
204
|
if not payload.query:
|
|
205
|
-
return text("❌
|
|
205
|
+
return text("❌ Provide a search term (query).")
|
|
206
206
|
todos = await self._service.search(
|
|
207
207
|
payload.query, limit=payload.limit, offset=payload.offset
|
|
208
208
|
)
|
|
209
209
|
if not todos:
|
|
210
|
-
return text("🔍
|
|
210
|
+
return text("🔍 No TODOs found for the search.")
|
|
211
211
|
body = "\n\n".join(ProductivityService.format_todo(todo) for todo in todos)
|
|
212
|
-
return text(f"🔍 **
|
|
212
|
+
return text(f"🔍 **Search results ({len(todos)}):**\n\n{body}")
|
|
213
213
|
|
|
214
214
|
async def _handle_overdue(self):
|
|
215
215
|
todos = await self._service.overdue()
|
|
216
216
|
if not todos:
|
|
217
|
-
return text("✅
|
|
217
|
+
return text("✅ No overdue TODOs at the moment.")
|
|
218
218
|
body = "\n\n".join(ProductivityService.format_todo(todo) for todo in todos)
|
|
219
|
-
return text(f"⏰ **TODOs
|
|
219
|
+
return text(f"⏰ **Overdue TODOs ({len(todos)}):**\n\n{body}")
|
|
220
220
|
|
|
221
221
|
async def _handle_upcoming(self, payload: ProductivityRequest):
|
|
222
222
|
todos = await self._service.upcoming(days=payload.days)
|
|
223
223
|
if not todos:
|
|
224
|
-
return text("📅
|
|
224
|
+
return text("📅 No TODOs scheduled for the specified period.")
|
|
225
225
|
body = "\n\n".join(ProductivityService.format_todo(todo) for todo in todos)
|
|
226
|
-
header = f"📅 TODOs
|
|
226
|
+
header = f"📅 Scheduled TODOs ({len(todos)}):"
|
|
227
227
|
if payload.days:
|
|
228
|
-
header += f"
|
|
228
|
+
header += f" next {payload.days} days"
|
|
229
229
|
return text(f"{header}\n\n{body}")
|
|
230
230
|
|
|
231
231
|
async def _handle_categories(self):
|
|
232
232
|
categories = await self._service.categories()
|
|
233
233
|
if not categories:
|
|
234
|
-
return text("🏷️
|
|
234
|
+
return text("🏷️ No categories registered yet.")
|
|
235
235
|
body = "\n".join(f"- {category}" for category in categories)
|
|
236
|
-
return text(f"🏷️ **
|
|
236
|
+
return text(f"🏷️ **Categories in use:**\n{body}")
|
|
237
237
|
|
|
238
238
|
async def _handle_tags(self):
|
|
239
239
|
tags = await self._service.tags()
|
|
240
240
|
if not tags:
|
|
241
|
-
return text("🔖
|
|
241
|
+
return text("🔖 No tags registered yet.")
|
|
242
242
|
body = "\n".join(f"- {tag}" for tag in tags)
|
|
243
|
-
return text(f"🔖 **Tags
|
|
243
|
+
return text(f"🔖 **Tags in use:**\n{body}")
|
|
244
244
|
|
|
245
245
|
async def _handle_help(self):
|
|
246
246
|
return text(
|
|
247
|
-
"📚 **
|
|
247
|
+
"📚 **Available actions for productivity**\n\n"
|
|
248
248
|
+ TodoAction.formatted_help()
|
|
249
249
|
)
|
|
250
250
|
|
|
@@ -27,12 +27,12 @@ class UserConfigAction(str, Enum):
|
|
|
27
27
|
obj.description = description
|
|
28
28
|
return obj
|
|
29
29
|
|
|
30
|
-
CREATE = ("create", "
|
|
31
|
-
LIST = ("list", "
|
|
32
|
-
GET = ("get", "
|
|
33
|
-
UPDATE = ("update", "
|
|
34
|
-
DELETE = ("delete", "
|
|
35
|
-
HELP = ("help", "
|
|
30
|
+
CREATE = ("create", "Creates a new user core document.")
|
|
31
|
+
LIST = ("list", "Lists documents with optional pagination.")
|
|
32
|
+
GET = ("get", "Gets details of a specific document.")
|
|
33
|
+
UPDATE = ("update", "Updates fields of an existing document.")
|
|
34
|
+
DELETE = ("delete", "Removes a document.")
|
|
35
|
+
HELP = ("help", "Shows available actions and their uses.")
|
|
36
36
|
|
|
37
37
|
@classmethod
|
|
38
38
|
def choices(cls) -> List[str]:
|
|
@@ -41,7 +41,7 @@ class UserConfigAction(str, Enum):
|
|
|
41
41
|
@classmethod
|
|
42
42
|
def formatted_help(cls) -> str:
|
|
43
43
|
lines = [
|
|
44
|
-
"| **
|
|
44
|
+
"| **Action** | **Description** |",
|
|
45
45
|
"| --- | --- |",
|
|
46
46
|
]
|
|
47
47
|
for member in cls:
|
|
@@ -49,7 +49,7 @@ class UserConfigAction(str, Enum):
|
|
|
49
49
|
return "\n".join(lines)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
ACTION_FIELD_DESCRIPTION = "
|
|
52
|
+
ACTION_FIELD_DESCRIPTION = "Action to execute. Choose one of the values: " + ", ".join(
|
|
53
53
|
f"`{member.value}` ({member.description.rstrip('.')})."
|
|
54
54
|
for member in UserConfigAction
|
|
55
55
|
)
|
|
@@ -57,30 +57,30 @@ ACTION_FIELD_DESCRIPTION = "Ação a executar. Escolha um dos valores: " + ", ".
|
|
|
57
57
|
|
|
58
58
|
class UserConfigRequest(ToolRequest):
|
|
59
59
|
action: UserConfigAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
60
|
-
id: Optional[UUIDStr] = Field(default=None, description="ID
|
|
61
|
-
name: Optional[TitleStr] = Field(default=None, description="
|
|
60
|
+
id: Optional[UUIDStr] = Field(default=None, description="Document ID (UUID).")
|
|
61
|
+
name: Optional[TitleStr] = Field(default=None, description="Document name.")
|
|
62
62
|
content: Optional[MarkdownStr] = Field(
|
|
63
|
-
default=None, description="
|
|
63
|
+
default=None, description="Content in Markdown/JSON."
|
|
64
64
|
)
|
|
65
65
|
mode_id: Optional[UUIDStr] = Field(
|
|
66
|
-
default=None, description="
|
|
66
|
+
default=None, description="Associated mode ID (UUID)."
|
|
67
67
|
)
|
|
68
68
|
is_default: Optional[bool] = Field(
|
|
69
|
-
default=None, description="
|
|
69
|
+
default=None, description="Marks the document as default."
|
|
70
70
|
)
|
|
71
71
|
metadata: Optional[MarkdownStr] = Field(
|
|
72
|
-
default=None, description="
|
|
72
|
+
default=None, description="Structured document metadata (Markdown)."
|
|
73
73
|
)
|
|
74
|
-
limit: int = Field(default=20, ge=1, le=100, description="
|
|
75
|
-
offset: int = Field(default=0, ge=0, description="
|
|
74
|
+
limit: int = Field(default=20, ge=1, le=100, description="Listing limit.")
|
|
75
|
+
offset: int = Field(default=0, ge=0, description="Listing offset.")
|
|
76
76
|
return_content: Optional[bool] = Field(
|
|
77
|
-
default=None, description="
|
|
77
|
+
default=None, description="Return full content."
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
class UserConfigTool(Tool):
|
|
82
82
|
name = "user_config"
|
|
83
|
-
description = "
|
|
83
|
+
description = "Manages user configuration documents (Core Documents)."
|
|
84
84
|
request_model = UserConfigRequest
|
|
85
85
|
|
|
86
86
|
def __init__(self, context: AppContext):
|
|
@@ -93,7 +93,7 @@ class UserConfigTool(Tool):
|
|
|
93
93
|
return await self._handle_help()
|
|
94
94
|
if action is UserConfigAction.CREATE:
|
|
95
95
|
if not payload.name or not payload.content:
|
|
96
|
-
return text("❌
|
|
96
|
+
return text("❌ Provide name and content to create the document.")
|
|
97
97
|
doc = await self._service.create(
|
|
98
98
|
_strip_none(
|
|
99
99
|
{
|
|
@@ -105,7 +105,7 @@ class UserConfigTool(Tool):
|
|
|
105
105
|
}
|
|
106
106
|
)
|
|
107
107
|
)
|
|
108
|
-
return text(_format_doc(doc, header="✅
|
|
108
|
+
return text(_format_doc(doc, header="✅ Document created"))
|
|
109
109
|
|
|
110
110
|
if action is UserConfigAction.LIST:
|
|
111
111
|
docs = await self._service.list(
|
|
@@ -114,22 +114,22 @@ class UserConfigTool(Tool):
|
|
|
114
114
|
returnContent=payload.return_content,
|
|
115
115
|
)
|
|
116
116
|
if not docs:
|
|
117
|
-
return text("📂
|
|
117
|
+
return text("📂 No documents found.")
|
|
118
118
|
body = "\n\n".join(_format_doc(doc) for doc in docs)
|
|
119
|
-
return text(f"📂 **
|
|
119
|
+
return text(f"📂 **Documents ({len(docs)}):**\n\n{body}")
|
|
120
120
|
|
|
121
121
|
if action is UserConfigAction.GET:
|
|
122
122
|
if not payload.id:
|
|
123
|
-
return text("❌
|
|
123
|
+
return text("❌ Provide the document ID.")
|
|
124
124
|
doc = await self._service.get(
|
|
125
125
|
payload.id,
|
|
126
126
|
returnContent=payload.return_content,
|
|
127
127
|
)
|
|
128
|
-
return text(_format_doc(doc, header="📂
|
|
128
|
+
return text(_format_doc(doc, header="📂 Document details"))
|
|
129
129
|
|
|
130
130
|
if action is UserConfigAction.UPDATE:
|
|
131
131
|
if not payload.id:
|
|
132
|
-
return text("❌
|
|
132
|
+
return text("❌ Provide the document ID to update.")
|
|
133
133
|
data = _strip_none(
|
|
134
134
|
{
|
|
135
135
|
"name": payload.name,
|
|
@@ -140,22 +140,22 @@ class UserConfigTool(Tool):
|
|
|
140
140
|
}
|
|
141
141
|
)
|
|
142
142
|
doc = await self._service.update(payload.id, data)
|
|
143
|
-
return text(_format_doc(doc, header="✅
|
|
143
|
+
return text(_format_doc(doc, header="✅ Document updated"))
|
|
144
144
|
|
|
145
145
|
if action is UserConfigAction.DELETE:
|
|
146
146
|
if not payload.id:
|
|
147
|
-
return text("❌
|
|
147
|
+
return text("❌ Provide the document ID.")
|
|
148
148
|
await self._service.delete(payload.id)
|
|
149
|
-
return text(f"🗑️
|
|
149
|
+
return text(f"🗑️ Document {payload.id} removed.")
|
|
150
150
|
|
|
151
151
|
return text(
|
|
152
|
-
"❌
|
|
152
|
+
"❌ Unsupported user_config action.\n\nChoose one of the values:\n"
|
|
153
153
|
+ "\n".join(f"- `{value}`" for value in UserConfigAction.choices())
|
|
154
154
|
)
|
|
155
155
|
|
|
156
156
|
async def _handle_help(self):
|
|
157
157
|
return text(
|
|
158
|
-
"📚 **
|
|
158
|
+
"📚 **Available actions for user_config**\n\n"
|
|
159
159
|
+ UserConfigAction.formatted_help()
|
|
160
160
|
)
|
|
161
161
|
|
|
@@ -167,13 +167,13 @@ def _format_doc(doc: Dict[str, Any], header: Optional[str] = None) -> str:
|
|
|
167
167
|
lines.append("")
|
|
168
168
|
lines.extend(
|
|
169
169
|
[
|
|
170
|
-
f"📂 **{doc.get('name', '
|
|
170
|
+
f"📂 **{doc.get('name', 'Unnamed')}**",
|
|
171
171
|
f"ID: {doc.get('id', 'N/A')}",
|
|
172
172
|
f"Default: {doc.get('is_default', False)}",
|
|
173
173
|
]
|
|
174
174
|
)
|
|
175
175
|
if doc.get("mode_id"):
|
|
176
|
-
lines.append(f"
|
|
176
|
+
lines.append(f"Associated mode: {doc['mode_id']}")
|
|
177
177
|
if doc.get("content") and len(doc["content"]) <= 400:
|
|
178
178
|
lines.append("")
|
|
179
179
|
lines.append(doc["content"])
|
fenix_mcp/domain/knowledge.py
CHANGED
|
@@ -75,38 +75,30 @@ class KnowledgeService:
|
|
|
75
75
|
async def work_update(
|
|
76
76
|
self, work_id: str, payload: Dict[str, Any]
|
|
77
77
|
) -> Dict[str, Any]:
|
|
78
|
-
return await self.
|
|
78
|
+
return await self._call_dict(
|
|
79
79
|
self.api.update_work_item, work_id, _strip_none(payload)
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
async def work_delete(self, work_id: str) -> None:
|
|
83
83
|
await self._call(self.api.delete_work_item, work_id)
|
|
84
84
|
|
|
85
|
-
async def work_backlog(self
|
|
86
|
-
return await self._call_list(self.api.list_work_items_backlog
|
|
85
|
+
async def work_backlog(self) -> List[Dict[str, Any]]:
|
|
86
|
+
return await self._call_list(self.api.list_work_items_backlog)
|
|
87
87
|
|
|
88
|
-
async def work_search(
|
|
89
|
-
self, *, query: str, team_id: str, limit: int
|
|
90
|
-
) -> List[Dict[str, Any]]:
|
|
88
|
+
async def work_search(self, *, query: str, limit: int) -> List[Dict[str, Any]]:
|
|
91
89
|
return await self._call_list(
|
|
92
90
|
self.api.search_work_items,
|
|
93
91
|
query=query,
|
|
94
|
-
team_id=team_id,
|
|
95
92
|
limit=limit,
|
|
96
93
|
)
|
|
97
94
|
|
|
98
|
-
async def work_analytics(self
|
|
99
|
-
return (
|
|
100
|
-
await self._call(self.api.get_work_items_analytics, team_id=team_id) or {}
|
|
101
|
-
)
|
|
95
|
+
async def work_analytics(self) -> Dict[str, Any]:
|
|
96
|
+
return await self._call(self.api.get_work_items_analytics) or {}
|
|
102
97
|
|
|
103
|
-
async def work_velocity(
|
|
104
|
-
self, *, team_id: str, sprints_count: int
|
|
105
|
-
) -> Dict[str, Any]:
|
|
98
|
+
async def work_velocity(self, *, sprints_count: int) -> Dict[str, Any]:
|
|
106
99
|
return (
|
|
107
100
|
await self._call(
|
|
108
101
|
self.api.get_work_items_velocity,
|
|
109
|
-
team_id=team_id,
|
|
110
102
|
sprints_count=sprints_count,
|
|
111
103
|
)
|
|
112
104
|
or {}
|
|
@@ -178,21 +170,18 @@ class KnowledgeService:
|
|
|
178
170
|
result = await self._call(self.api.list_work_boards, **_strip_none(filters))
|
|
179
171
|
return _ensure_list(result)
|
|
180
172
|
|
|
181
|
-
async def board_list_by_team(self
|
|
182
|
-
result = await self._call(self.api.list_work_boards_by_team
|
|
173
|
+
async def board_list_by_team(self) -> List[Dict[str, Any]]:
|
|
174
|
+
result = await self._call(self.api.list_work_boards_by_team)
|
|
183
175
|
return _ensure_list(result)
|
|
184
176
|
|
|
185
177
|
async def board_favorites(self) -> List[Dict[str, Any]]:
|
|
186
178
|
result = await self._call(self.api.list_favorite_work_boards)
|
|
187
179
|
return _ensure_list(result)
|
|
188
180
|
|
|
189
|
-
async def board_search(
|
|
190
|
-
self, *, query: str, team_id: str, limit: int
|
|
191
|
-
) -> List[Dict[str, Any]]:
|
|
181
|
+
async def board_search(self, *, query: str, limit: int) -> List[Dict[str, Any]]:
|
|
192
182
|
result = await self._call(
|
|
193
183
|
self.api.search_work_boards,
|
|
194
184
|
query=query,
|
|
195
|
-
team_id=team_id,
|
|
196
185
|
limit=limit,
|
|
197
186
|
)
|
|
198
187
|
return _ensure_list(result)
|
|
@@ -280,27 +269,21 @@ class KnowledgeService:
|
|
|
280
269
|
async def sprint_delete(self, sprint_id: str) -> None:
|
|
281
270
|
await self._call(self.api.delete_sprint, sprint_id)
|
|
282
271
|
|
|
283
|
-
async def sprint_list_by_team(self
|
|
284
|
-
return await self._call_list(self.api.list_sprints_by_team
|
|
272
|
+
async def sprint_list_by_team(self) -> List[Dict[str, Any]]:
|
|
273
|
+
return await self._call_list(self.api.list_sprints_by_team)
|
|
285
274
|
|
|
286
|
-
async def sprint_recent(self, *,
|
|
287
|
-
return (
|
|
288
|
-
await self._call(self.api.list_recent_sprints, team_id=team_id, limit=limit)
|
|
289
|
-
or []
|
|
290
|
-
)
|
|
275
|
+
async def sprint_recent(self, *, limit: int) -> List[Dict[str, Any]]:
|
|
276
|
+
return await self._call(self.api.list_recent_sprints, limit=limit) or []
|
|
291
277
|
|
|
292
|
-
async def sprint_search(
|
|
293
|
-
self, *, query: str, team_id: str, limit: int
|
|
294
|
-
) -> List[Dict[str, Any]]:
|
|
278
|
+
async def sprint_search(self, *, query: str, limit: int) -> List[Dict[str, Any]]:
|
|
295
279
|
return await self._call_list(
|
|
296
280
|
self.api.search_sprints,
|
|
297
281
|
query=query,
|
|
298
|
-
team_id=team_id,
|
|
299
282
|
limit=limit,
|
|
300
283
|
)
|
|
301
284
|
|
|
302
|
-
async def sprint_active(self
|
|
303
|
-
return await self._call(self.api.get_active_sprint
|
|
285
|
+
async def sprint_active(self) -> Dict[str, Any]:
|
|
286
|
+
return await self._call(self.api.get_active_sprint) or {}
|
|
304
287
|
|
|
305
288
|
async def sprint_velocity(self) -> Dict[str, Any]:
|
|
306
289
|
return await self._call(self.api.get_sprints_velocity) or {}
|
|
@@ -348,7 +331,11 @@ class KnowledgeService:
|
|
|
348
331
|
# Modes and rules
|
|
349
332
|
# ------------------------------------------------------------------
|
|
350
333
|
async def mode_create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
351
|
-
|
|
334
|
+
result = await self._call(self.api.create_mode, _strip_none(payload))
|
|
335
|
+
# API returns { message, mode } - extract mode
|
|
336
|
+
if isinstance(result, dict) and "mode" in result:
|
|
337
|
+
return result["mode"]
|
|
338
|
+
return result or {}
|
|
352
339
|
|
|
353
340
|
async def mode_list(
|
|
354
341
|
self,
|
|
@@ -384,7 +371,11 @@ class KnowledgeService:
|
|
|
384
371
|
async def mode_update(
|
|
385
372
|
self, mode_id: str, payload: Dict[str, Any]
|
|
386
373
|
) -> Dict[str, Any]:
|
|
387
|
-
|
|
374
|
+
result = await self._call(self.api.update_mode, mode_id, _strip_none(payload))
|
|
375
|
+
# API returns { message, mode } - extract mode
|
|
376
|
+
if isinstance(result, dict) and "mode" in result:
|
|
377
|
+
return result["mode"]
|
|
378
|
+
return result or {}
|
|
388
379
|
|
|
389
380
|
async def mode_delete(self, mode_id: str) -> None:
|
|
390
381
|
await self._call(self.api.delete_mode, mode_id)
|
|
@@ -402,7 +393,11 @@ class KnowledgeService:
|
|
|
402
393
|
return await self._call(self.api.list_modes_by_rule, rule_id) or []
|
|
403
394
|
|
|
404
395
|
async def rule_create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
405
|
-
|
|
396
|
+
result = await self._call(self.api.create_rule, _strip_none(payload))
|
|
397
|
+
# API returns { message, rule } - extract rule
|
|
398
|
+
if isinstance(result, dict) and "rule" in result:
|
|
399
|
+
return result["rule"]
|
|
400
|
+
return result or {}
|
|
406
401
|
|
|
407
402
|
async def rule_list(
|
|
408
403
|
self,
|
|
@@ -440,7 +435,11 @@ class KnowledgeService:
|
|
|
440
435
|
async def rule_update(
|
|
441
436
|
self, rule_id: str, payload: Dict[str, Any]
|
|
442
437
|
) -> Dict[str, Any]:
|
|
443
|
-
|
|
438
|
+
result = await self._call(self.api.update_rule, rule_id, _strip_none(payload))
|
|
439
|
+
# API returns { message, rule } - extract rule
|
|
440
|
+
if isinstance(result, dict) and "rule" in result:
|
|
441
|
+
return result["rule"]
|
|
442
|
+
return result or {}
|
|
444
443
|
|
|
445
444
|
async def rule_delete(self, rule_id: str) -> None:
|
|
446
445
|
await self._call(self.api.delete_rule, rule_id)
|
|
@@ -473,31 +472,27 @@ class KnowledgeService:
|
|
|
473
472
|
async def doc_delete(self, doc_id: str) -> None:
|
|
474
473
|
await self._call(self.api.delete_documentation_item, doc_id)
|
|
475
474
|
|
|
476
|
-
async def doc_search(
|
|
477
|
-
self, *, query: str, team_id: str, limit: int
|
|
478
|
-
) -> List[Dict[str, Any]]:
|
|
475
|
+
async def doc_search(self, *, query: str, limit: int) -> List[Dict[str, Any]]:
|
|
479
476
|
result = await self._call(
|
|
480
477
|
self.api.search_documentation_items,
|
|
481
478
|
query=query,
|
|
482
|
-
team_id=team_id,
|
|
483
479
|
limit=limit,
|
|
484
480
|
)
|
|
485
481
|
return _ensure_list(result)
|
|
486
482
|
|
|
487
|
-
async def doc_roots(self
|
|
488
|
-
result = await self._call(self.api.list_documentation_roots
|
|
483
|
+
async def doc_roots(self) -> List[Dict[str, Any]]:
|
|
484
|
+
result = await self._call(self.api.list_documentation_roots)
|
|
489
485
|
return _ensure_list(result)
|
|
490
486
|
|
|
491
|
-
async def doc_recent(self, *,
|
|
487
|
+
async def doc_recent(self, *, limit: int) -> List[Dict[str, Any]]:
|
|
492
488
|
result = await self._call(
|
|
493
489
|
self.api.list_documentation_recent,
|
|
494
|
-
team_id=team_id,
|
|
495
490
|
limit=limit,
|
|
496
491
|
)
|
|
497
492
|
return _ensure_list(result)
|
|
498
493
|
|
|
499
|
-
async def doc_analytics(self
|
|
500
|
-
result = await self._call(self.api.get_documentation_analytics
|
|
494
|
+
async def doc_analytics(self) -> Dict[str, Any]:
|
|
495
|
+
result = await self._call(self.api.get_documentation_analytics)
|
|
501
496
|
return _ensure_dict(result)
|
|
502
497
|
|
|
503
498
|
async def doc_children(self, doc_id: str) -> List[Dict[str, Any]]:
|
fenix_mcp/domain/user_config.py
CHANGED
|
@@ -19,7 +19,11 @@ class UserConfigService:
|
|
|
19
19
|
|
|
20
20
|
async def create(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
21
21
|
payload = _strip_none(data)
|
|
22
|
-
|
|
22
|
+
result = await asyncio.to_thread(self._api.create_user_core_document, payload)
|
|
23
|
+
# API returns { message, document } - extract document
|
|
24
|
+
if isinstance(result, dict) and "document" in result:
|
|
25
|
+
return result["document"]
|
|
26
|
+
return result or {}
|
|
23
27
|
|
|
24
28
|
async def list(
|
|
25
29
|
self, *, returnContent: Optional[bool] = None, **_: Any
|
|
@@ -43,9 +47,13 @@ class UserConfigService:
|
|
|
43
47
|
|
|
44
48
|
async def update(self, doc_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
45
49
|
payload = _strip_none(data)
|
|
46
|
-
|
|
50
|
+
result = await asyncio.to_thread(
|
|
47
51
|
self._api.update_user_core_document, doc_id, payload
|
|
48
52
|
)
|
|
53
|
+
# API returns { message, document } - extract document
|
|
54
|
+
if isinstance(result, dict) and "document" in result:
|
|
55
|
+
return result["document"]
|
|
56
|
+
return result or {}
|
|
49
57
|
|
|
50
58
|
async def delete(self, doc_id: str) -> None:
|
|
51
59
|
await asyncio.to_thread(self._api.delete_user_core_document, doc_id)
|