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.
@@ -30,18 +30,18 @@ class TodoAction(str, Enum):
30
30
  obj.description = description
31
31
  return obj
32
32
 
33
- CREATE = ("todo_create", "Cria um novo TODO.")
34
- LIST = ("todo_list", "Lista TODOs com filtros opcionais.")
35
- GET = ("todo_get", "Obtém detalhes de um TODO pelo ID.")
36
- UPDATE = ("todo_update", "Atualiza campos de um TODO existente.")
37
- DELETE = ("todo_delete", "Remove um TODO pelo ID.")
38
- STATS = ("todo_stats", "Retorna estatísticas agregadas de TODOs.")
39
- SEARCH = ("todo_search", "Busca TODOs por termo textual.")
40
- OVERDUE = ("todo_overdue", "Lista TODOs atrasados.")
41
- UPCOMING = ("todo_upcoming", "Lista TODOs com vencimento próximo.")
42
- CATEGORIES = ("todo_categories", "Lista categorias registradas.")
43
- TAGS = ("todo_tags", "Lista tags registradas.")
44
- HELP = ("todo_help", "Mostra as ações suportadas e seus usos.")
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
- "| **Ação** | **Descrição** |",
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
- "Ação de produtividade (TODO). Escolha um dos valores: "
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="ID do item TODO (UUID).")
71
+ id: Optional[UUIDStr] = Field(default=None, description="TODO item ID (UUID).")
72
72
  title: Optional[TitleStr] = Field(
73
- default=None, description="Título do TODO (obrigatório em create)."
73
+ default=None, description="TODO title (required for create)."
74
74
  )
75
75
  content: Optional[MarkdownStr] = Field(
76
- default=None, description="Conteúdo em Markdown (obrigatório em create)."
76
+ default=None, description="Markdown content (required for create)."
77
77
  )
78
78
  status: Optional[str] = Field(
79
79
  default=None,
80
- description="Status do TODO (pending, in_progress, completed, cancelled).",
80
+ description="TODO status (pending, in_progress, completed, cancelled).",
81
81
  )
82
82
  priority: Optional[str] = Field(
83
- default=None, description="Prioridade do TODO (low, medium, high, urgent)."
83
+ default=None, description="TODO priority (low, medium, high, urgent)."
84
84
  )
85
85
  category: Optional[CategoryStr] = Field(
86
- default=None, description="Categoria opcional."
86
+ default=None, description="Optional category."
87
87
  )
88
- tags: Optional[List[TagStr]] = Field(default=None, description="Lista de tags.")
88
+ tags: Optional[List[TagStr]] = Field(default=None, description="Tag list.")
89
89
  due_date: Optional[DateTimeStr] = Field(
90
- default=None, description="Data de vencimento do TODO (ISO 8601)."
90
+ default=None, description="TODO due date (ISO 8601)."
91
91
  )
92
92
  limit: int = Field(
93
- default=20, ge=1, le=100, description="Limite de resultados em list/search."
93
+ default=20, ge=1, le=100, description="Result limit for list/search."
94
94
  )
95
- offset: int = Field(default=0, ge=0, description="Offset de paginação.")
96
- query: Optional[str] = Field(default=None, description="Termo de busca.")
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="Janela de dias para upcoming."
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 = "Operações de produtividade do Fênix Cloud (TODOs)."
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
- "❌ Ação inválida para productivity.\n\nEscolha um dos valores:\n"
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("❌ Forneça título, conteúdo e due_date para criar um TODO.")
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 criado com sucesso!"))
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("📋 Nenhum TODO encontrado.")
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("❌ Informe o ID para consultar um TODO.")
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 encontrado"))
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("❌ Informe o ID para atualizar um TODO.")
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 atualizado"))
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("❌ Informe o ID para excluir um TODO.")
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} removido com sucesso.")
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 = ["📊 **Estatísticas de TODOs**"]
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("❌ Informe um termo de busca (query).")
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("🔍 Nenhum TODO encontrado para a busca.")
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"🔍 **Resultados da busca ({len(todos)}):**\n\n{body}")
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("✅ Sem TODOs atrasados no momento.")
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 atrasados ({len(todos)}):**\n\n{body}")
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("📅 Nenhum TODO previsto para o período informado.")
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 programados ({len(todos)}):"
226
+ header = f"📅 Scheduled TODOs ({len(todos)}):"
227
227
  if payload.days:
228
- header += f" próximos {payload.days} dias"
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("🏷️ Nenhuma categoria registrada ainda.")
234
+ return text("🏷️ No categories registered yet.")
235
235
  body = "\n".join(f"- {category}" for category in categories)
236
- return text(f"🏷️ **Categorias utilizadas:**\n{body}")
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("🔖 Nenhuma tag registrada ainda.")
241
+ return text("🔖 No tags registered yet.")
242
242
  body = "\n".join(f"- {tag}" for tag in tags)
243
- return text(f"🔖 **Tags utilizadas:**\n{body}")
243
+ return text(f"🔖 **Tags in use:**\n{body}")
244
244
 
245
245
  async def _handle_help(self):
246
246
  return text(
247
- "📚 **Ações disponíveis para productivity**\n\n"
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", "Cria um novo user core document.")
31
- LIST = ("list", "Lista documentos com paginação opcional.")
32
- GET = ("get", "Obtém detalhes de um documento específico.")
33
- UPDATE = ("update", "Atualiza campos de um documento existente.")
34
- DELETE = ("delete", "Remove um documento.")
35
- HELP = ("help", "Mostra as ações disponíveis e seus usos.")
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
- "| **Ação** | **Descrição** |",
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 = "Ação a executar. Escolha um dos valores: " + ", ".join(
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 do documento (UUID).")
61
- name: Optional[TitleStr] = Field(default=None, description="Nome do documento.")
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="Conteúdo em Markdown/JSON."
63
+ default=None, description="Content in Markdown/JSON."
64
64
  )
65
65
  mode_id: Optional[UUIDStr] = Field(
66
- default=None, description="ID do modo associado (UUID)."
66
+ default=None, description="Associated mode ID (UUID)."
67
67
  )
68
68
  is_default: Optional[bool] = Field(
69
- default=None, description="Marca o documento como padrão."
69
+ default=None, description="Marks the document as default."
70
70
  )
71
71
  metadata: Optional[MarkdownStr] = Field(
72
- default=None, description="Metadata estruturada do documento (Markdown)."
72
+ default=None, description="Structured document metadata (Markdown)."
73
73
  )
74
- limit: int = Field(default=20, ge=1, le=100, description="Limite para listagem.")
75
- offset: int = Field(default=0, ge=0, description="Offset para listagem.")
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="Retorna conteúdo completo."
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 = "Gerencia documentos de configuração do usuário (Core Documents)."
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("❌ Informe name e content para criar o documento.")
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="✅ Documento criado"))
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("📂 Nenhum documento encontrado.")
117
+ return text("📂 No documents found.")
118
118
  body = "\n\n".join(_format_doc(doc) for doc in docs)
119
- return text(f"📂 **Documentos ({len(docs)}):**\n\n{body}")
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("❌ Informe o ID do documento.")
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="📂 Detalhes do documento"))
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("❌ Informe o ID do documento para atualizar.")
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="✅ Documento atualizado"))
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("❌ Informe o ID do documento.")
147
+ return text("❌ Provide the document ID.")
148
148
  await self._service.delete(payload.id)
149
- return text(f"🗑️ Documento {payload.id} removido.")
149
+ return text(f"🗑️ Document {payload.id} removed.")
150
150
 
151
151
  return text(
152
- "❌ Ação de user_config não suportada.\n\nEscolha um dos valores:\n"
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
- "📚 **Ações disponíveis para user_config**\n\n"
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', 'Sem nome')}**",
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"Modo associado: {doc['mode_id']}")
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"])
@@ -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._call(
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, *, team_id: str) -> List[Dict[str, Any]]:
86
- return await self._call_list(self.api.list_work_items_backlog, team_id=team_id)
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, *, team_id: str) -> Dict[str, Any]:
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, team_id: str) -> List[Dict[str, Any]]:
182
- result = await self._call(self.api.list_work_boards_by_team, team_id=team_id)
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, team_id: str) -> List[Dict[str, Any]]:
284
- return await self._call_list(self.api.list_sprints_by_team, team_id=team_id)
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, *, team_id: str, limit: int) -> List[Dict[str, Any]]:
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, team_id: str) -> Dict[str, Any]:
303
- return await self._call(self.api.get_active_sprint, team_id=team_id) or {}
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
- return await self._call(self.api.create_mode, _strip_none(payload))
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
- return await self._call(self.api.update_mode, mode_id, _strip_none(payload))
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
- return await self._call(self.api.create_rule, _strip_none(payload))
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
- return await self._call(self.api.update_rule, rule_id, _strip_none(payload))
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, *, team_id: str) -> List[Dict[str, Any]]:
488
- result = await self._call(self.api.list_documentation_roots, team_id=team_id)
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, *, team_id: str, limit: int) -> List[Dict[str, Any]]:
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, *, team_id: str) -> Dict[str, Any]:
500
- result = await self._call(self.api.get_documentation_analytics, team_id=team_id)
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]]:
@@ -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
- return await asyncio.to_thread(self._api.create_user_core_document, payload)
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
- return await asyncio.to_thread(
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)