fenix-mcp 1.14.0__py3-none-any.whl → 2.0.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 +18 -56
- fenix_mcp/application/tools/intelligence.py +130 -304
- fenix_mcp/application/tools/knowledge.py +342 -10
- fenix_mcp/domain/initialization.py +11 -112
- fenix_mcp/domain/intelligence.py +57 -247
- fenix_mcp/domain/knowledge.py +22 -0
- fenix_mcp/infrastructure/fenix_api/client.py +108 -23
- fenix_mcp/interface/mcp_server.py +12 -0
- fenix_mcp-2.0.0.dist-info/METADATA +341 -0
- {fenix_mcp-1.14.0.dist-info → fenix_mcp-2.0.0.dist-info}/RECORD +14 -14
- {fenix_mcp-1.14.0.dist-info → fenix_mcp-2.0.0.dist-info}/WHEEL +1 -1
- fenix_mcp-1.14.0.dist-info/METADATA +0 -258
- {fenix_mcp-1.14.0.dist-info → fenix_mcp-2.0.0.dist-info}/entry_points.txt +0 -0
- {fenix_mcp-1.14.0.dist-info → fenix_mcp-2.0.0.dist-info}/top_level.txt +0 -0
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,
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
9
|
|
|
10
10
|
from fenix_mcp.infrastructure.fenix_api.client import FenixApiClient
|
|
11
11
|
|
|
@@ -15,270 +15,80 @@ class IntelligenceService:
|
|
|
15
15
|
api: FenixApiClient
|
|
16
16
|
logger: Any
|
|
17
17
|
|
|
18
|
-
async def
|
|
18
|
+
async def save_memory(
|
|
19
19
|
self,
|
|
20
20
|
*,
|
|
21
21
|
title: str,
|
|
22
22
|
content: str,
|
|
23
|
-
metadata: str,
|
|
24
|
-
context: Optional[str],
|
|
25
|
-
source: str,
|
|
26
|
-
importance: str,
|
|
27
23
|
tags: List[str],
|
|
24
|
+
documentation_item_id: Optional[str] = None,
|
|
25
|
+
work_item_id: Optional[str] = None,
|
|
26
|
+
sprint_id: Optional[str] = None,
|
|
28
27
|
) -> Dict[str, Any]:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
"""
|
|
29
|
+
Smart save memory - creates or updates based on semantic similarity.
|
|
30
|
+
|
|
31
|
+
Required:
|
|
32
|
+
- title: Memory title
|
|
33
|
+
- content: Memory content
|
|
34
|
+
- tags: List of tags for categorization
|
|
35
|
+
|
|
36
|
+
Optional:
|
|
37
|
+
- documentation_item_id: Related documentation
|
|
38
|
+
- work_item_id: Related work item
|
|
39
|
+
- sprint_id: Related sprint
|
|
40
|
+
"""
|
|
41
|
+
if not title or not title.strip():
|
|
42
|
+
raise ValueError("title is required")
|
|
43
|
+
if not content or not content.strip():
|
|
44
|
+
raise ValueError("content is required")
|
|
36
45
|
if not tags or not isinstance(tags, list) or len(tags) == 0:
|
|
37
|
-
raise ValueError("tags is required and must be a non-empty
|
|
38
|
-
|
|
39
|
-
# Validate all tags are strings
|
|
40
|
-
for i, tag in enumerate(tags):
|
|
41
|
-
if not isinstance(tag, str) or not tag.strip():
|
|
42
|
-
raise ValueError(
|
|
43
|
-
f"All tags must be non-empty strings, got {type(tag)} at index {i}"
|
|
44
|
-
)
|
|
46
|
+
raise ValueError("tags is required and must be a non-empty list")
|
|
45
47
|
|
|
46
|
-
importance_value = importance or "medium"
|
|
47
|
-
metadata_str = build_metadata(
|
|
48
|
-
metadata,
|
|
49
|
-
importance=importance_value,
|
|
50
|
-
tags=tags,
|
|
51
|
-
context=context,
|
|
52
|
-
source=source,
|
|
53
|
-
)
|
|
54
48
|
payload = {
|
|
55
|
-
"title": title,
|
|
56
|
-
"content": content,
|
|
57
|
-
"
|
|
58
|
-
"priority_score": _importance_to_priority(importance_value),
|
|
59
|
-
"tags": tags,
|
|
49
|
+
"title": title.strip(),
|
|
50
|
+
"content": content.strip(),
|
|
51
|
+
"tags": [t.strip() for t in tags if t.strip()],
|
|
60
52
|
}
|
|
61
|
-
return await self._call(self.api.smart_create_memory, _strip_none(payload))
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
params.pop("include_metadata", params.pop("metadata", None))
|
|
70
|
-
)
|
|
71
|
-
allowed_keys = {
|
|
72
|
-
"limit",
|
|
73
|
-
"offset",
|
|
74
|
-
"query",
|
|
75
|
-
"tags",
|
|
76
|
-
"modeId",
|
|
77
|
-
"ruleId",
|
|
78
|
-
"workItemId",
|
|
79
|
-
"sprintId",
|
|
80
|
-
"documentationItemId",
|
|
81
|
-
"importance",
|
|
82
|
-
}
|
|
83
|
-
cleaned_params = {key: params[key] for key in allowed_keys if key in params}
|
|
84
|
-
return (
|
|
85
|
-
await self._call(
|
|
86
|
-
self.api.list_memories,
|
|
87
|
-
include_content=include_content,
|
|
88
|
-
include_metadata=include_metadata,
|
|
89
|
-
**cleaned_params,
|
|
90
|
-
)
|
|
91
|
-
or []
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
async def similar_memories(
|
|
95
|
-
self, *, content: str, threshold: float, max_results: int
|
|
96
|
-
) -> List[Dict[str, Any]]:
|
|
97
|
-
payload = {
|
|
98
|
-
"content": content,
|
|
99
|
-
"threshold": threshold,
|
|
100
|
-
}
|
|
101
|
-
result = (
|
|
102
|
-
await self._call(self.api.find_similar_memories, _strip_none(payload)) or []
|
|
103
|
-
)
|
|
104
|
-
if isinstance(result, list) and max_results:
|
|
105
|
-
return result[:max_results]
|
|
106
|
-
return result
|
|
54
|
+
if documentation_item_id:
|
|
55
|
+
payload["documentationItemId"] = documentation_item_id
|
|
56
|
+
if work_item_id:
|
|
57
|
+
payload["workItemId"] = work_item_id
|
|
58
|
+
if sprint_id:
|
|
59
|
+
payload["sprintId"] = sprint_id
|
|
107
60
|
|
|
108
|
-
|
|
109
|
-
self, *, memory_ids: Iterable[str], strategy: str
|
|
110
|
-
) -> Dict[str, Any]:
|
|
111
|
-
payload = {
|
|
112
|
-
"memoryIds": list(memory_ids),
|
|
113
|
-
"strategy": strategy,
|
|
114
|
-
}
|
|
115
|
-
return await self._call(self.api.consolidate_memories, payload)
|
|
61
|
+
return await self._call(self.api.save_memory, payload)
|
|
116
62
|
|
|
117
|
-
async def
|
|
118
|
-
payload = _strip_none(fields)
|
|
119
|
-
if "importance" in payload:
|
|
120
|
-
payload["priority_score"] = _importance_to_priority(
|
|
121
|
-
payload.pop("importance")
|
|
122
|
-
)
|
|
123
|
-
mapping = {
|
|
124
|
-
"documentation_item_id": "documentationItemId",
|
|
125
|
-
"mode_id": "modeId",
|
|
126
|
-
"rule_id": "ruleId",
|
|
127
|
-
"work_item_id": "workItemId",
|
|
128
|
-
"sprint_id": "sprintId",
|
|
129
|
-
}
|
|
130
|
-
for old_key, new_key in mapping.items():
|
|
131
|
-
if old_key in payload:
|
|
132
|
-
payload[new_key] = payload.pop(old_key)
|
|
133
|
-
return await self._call(self.api.update_memory, memory_id, payload)
|
|
134
|
-
|
|
135
|
-
async def delete_memory(self, memory_id: str) -> None:
|
|
136
|
-
await self._call(self.api.delete_memory, memory_id)
|
|
137
|
-
|
|
138
|
-
async def _call(self, func, *args, **kwargs):
|
|
139
|
-
return await asyncio.to_thread(func, *args, **kwargs)
|
|
140
|
-
|
|
141
|
-
async def get_memory(
|
|
63
|
+
async def search_memories(
|
|
142
64
|
self,
|
|
143
|
-
memory_id: str,
|
|
144
65
|
*,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
include_content=include_content,
|
|
152
|
-
include_metadata=include_metadata,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _importance_to_priority(importance: Optional[str]) -> float:
|
|
157
|
-
mapping = {
|
|
158
|
-
"low": 0.2,
|
|
159
|
-
"medium": 0.5,
|
|
160
|
-
"high": 0.7,
|
|
161
|
-
"critical": 0.9,
|
|
162
|
-
}
|
|
163
|
-
if importance is None:
|
|
164
|
-
return 0.5
|
|
165
|
-
return mapping.get(importance.lower(), 0.5)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def _coerce_bool(value: Any, default: bool = False) -> bool:
|
|
169
|
-
if value is None or value == "":
|
|
170
|
-
return default
|
|
171
|
-
if isinstance(value, bool):
|
|
172
|
-
return value
|
|
173
|
-
if isinstance(value, (int, float)):
|
|
174
|
-
return bool(value)
|
|
175
|
-
if isinstance(value, str):
|
|
176
|
-
normalized = value.strip().lower()
|
|
177
|
-
if normalized in {"true", "1", "yes", "y"}:
|
|
178
|
-
return True
|
|
179
|
-
if normalized in {"false", "0", "no", "n"}:
|
|
180
|
-
return False
|
|
181
|
-
return bool(value)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _strip_none(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
185
|
-
return {key: value for key, value in data.items() if value not in (None, "")}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def build_metadata(
|
|
189
|
-
explicit: str,
|
|
190
|
-
*,
|
|
191
|
-
importance: Optional[str],
|
|
192
|
-
tags: List[str],
|
|
193
|
-
context: Optional[str] = None,
|
|
194
|
-
source: str,
|
|
195
|
-
existing: Optional[str] = None,
|
|
196
|
-
) -> str:
|
|
197
|
-
if explicit and explicit.strip():
|
|
198
|
-
return explicit.strip()
|
|
199
|
-
|
|
200
|
-
existing_map = _parse_metadata(existing) if existing else {}
|
|
201
|
-
metadata_map: Dict[str, str] = {}
|
|
202
|
-
|
|
203
|
-
metadata_map["t"] = existing_map.get("t", "memory")
|
|
204
|
-
metadata_map["src"] = _slugify(source) if source else existing_map.get("src", "mcp")
|
|
205
|
-
|
|
206
|
-
ctx_value = _slugify(context) if context else existing_map.get("ctx")
|
|
207
|
-
if ctx_value:
|
|
208
|
-
metadata_map["ctx"] = ctx_value
|
|
209
|
-
|
|
210
|
-
priority_key = importance.lower() if importance else existing_map.get("p")
|
|
211
|
-
if priority_key:
|
|
212
|
-
metadata_map["p"] = priority_key
|
|
213
|
-
|
|
214
|
-
tag_string = _format_tags(tags)
|
|
215
|
-
if tag_string:
|
|
216
|
-
metadata_map["tags"] = tag_string
|
|
217
|
-
elif "tags" in existing_map:
|
|
218
|
-
metadata_map["tags"] = existing_map["tags"]
|
|
219
|
-
|
|
220
|
-
for key, value in existing_map.items():
|
|
221
|
-
if key not in metadata_map:
|
|
222
|
-
metadata_map[key] = value
|
|
223
|
-
|
|
224
|
-
if not metadata_map:
|
|
225
|
-
metadata_map["t"] = "memory"
|
|
226
|
-
metadata_map["src"] = "mcp"
|
|
227
|
-
|
|
228
|
-
return "|".join(f"{key}:{metadata_map[key]}" for key in metadata_map)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def _parse_metadata(metadata: str) -> Dict[str, str]:
|
|
232
|
-
items = {}
|
|
233
|
-
for entry in metadata.split("|"):
|
|
234
|
-
if ":" not in entry:
|
|
235
|
-
continue
|
|
236
|
-
key, value = entry.split(":", 1)
|
|
237
|
-
key = key.strip()
|
|
238
|
-
value = value.strip()
|
|
239
|
-
if key:
|
|
240
|
-
items[key] = value
|
|
241
|
-
return items
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def _slugify(value: Optional[str]) -> str:
|
|
245
|
-
if not value:
|
|
246
|
-
return ""
|
|
247
|
-
sanitized = value.replace("|", " ").replace(":", " ")
|
|
248
|
-
return "-".join(part for part in sanitized.split() if part).lower()
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def _format_tags(tags: List[str]) -> str:
|
|
252
|
-
"""
|
|
253
|
-
Format tags list into a comma-separated string for metadata.
|
|
254
|
-
|
|
255
|
-
Args:
|
|
256
|
-
tags: List of string tags (required)
|
|
66
|
+
query: str,
|
|
67
|
+
limit: int = 5,
|
|
68
|
+
tags: Optional[List[str]] = None,
|
|
69
|
+
) -> List[Dict[str, Any]]:
|
|
70
|
+
"""
|
|
71
|
+
Search memories using semantic similarity (embeddings).
|
|
257
72
|
|
|
258
|
-
|
|
259
|
-
|
|
73
|
+
Required:
|
|
74
|
+
- query: Natural language search query
|
|
260
75
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
76
|
+
Optional:
|
|
77
|
+
- limit: Maximum results (default 5)
|
|
78
|
+
- tags: Filter by tags
|
|
79
|
+
"""
|
|
80
|
+
if not query or not query.strip():
|
|
81
|
+
raise ValueError("query is required")
|
|
267
82
|
|
|
268
|
-
|
|
269
|
-
|
|
83
|
+
payload = {
|
|
84
|
+
"query": query.strip(),
|
|
85
|
+
"limit": limit,
|
|
86
|
+
}
|
|
270
87
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
for tag in tags:
|
|
274
|
-
if isinstance(tag, str) and tag.strip():
|
|
275
|
-
cleaned_tags.append(tag.strip())
|
|
276
|
-
else:
|
|
277
|
-
raise ValueError(
|
|
278
|
-
f"All tags must be non-empty strings, got {type(tag)}: {tag}"
|
|
279
|
-
)
|
|
88
|
+
if tags:
|
|
89
|
+
payload["tags"] = [t.strip() for t in tags if t.strip()]
|
|
280
90
|
|
|
281
|
-
|
|
282
|
-
raise ValueError("No valid tags found after cleaning")
|
|
91
|
+
return await self._call(self.api.search_memories, payload) or []
|
|
283
92
|
|
|
284
|
-
|
|
93
|
+
async def _call(self, func, *args, **kwargs):
|
|
94
|
+
return await asyncio.to_thread(func, *args, **kwargs)
|
fenix_mcp/domain/knowledge.py
CHANGED
|
@@ -466,6 +466,28 @@ class KnowledgeService:
|
|
|
466
466
|
result = await self._call(self.api.export_rule, rule_id, format)
|
|
467
467
|
return result if isinstance(result, str) else str(result or "")
|
|
468
468
|
|
|
469
|
+
# ------------------------------------------------------------------
|
|
470
|
+
# API Catalog
|
|
471
|
+
# ------------------------------------------------------------------
|
|
472
|
+
async def api_catalog_list(self, **filters: Any) -> List[Dict[str, Any]]:
|
|
473
|
+
return await self._call_list(self.api.list_api_catalog, **_strip_none(filters))
|
|
474
|
+
|
|
475
|
+
async def api_catalog_get(self, spec_id: str) -> Dict[str, Any]:
|
|
476
|
+
return await self._call_dict(self.api.get_api_catalog, spec_id)
|
|
477
|
+
|
|
478
|
+
async def api_catalog_search(
|
|
479
|
+
self,
|
|
480
|
+
*,
|
|
481
|
+
query: str,
|
|
482
|
+
limit: int = 10,
|
|
483
|
+
) -> Dict[str, Any]:
|
|
484
|
+
result = await self._call(
|
|
485
|
+
self.api.search_api_catalog_semantic,
|
|
486
|
+
query=query,
|
|
487
|
+
limit=limit,
|
|
488
|
+
)
|
|
489
|
+
return _ensure_dict(result) if isinstance(result, dict) else {"data": result}
|
|
490
|
+
|
|
469
491
|
|
|
470
492
|
__all__ = [
|
|
471
493
|
"KnowledgeService",
|
|
@@ -228,9 +228,6 @@ class FenixApiClient:
|
|
|
228
228
|
# Memories
|
|
229
229
|
# ------------------------------------------------------------------
|
|
230
230
|
|
|
231
|
-
def create_memory(self, payload: Mapping[str, Any]) -> Any:
|
|
232
|
-
return self._request("POST", "/api/memories", json=payload)
|
|
233
|
-
|
|
234
231
|
def list_memories(
|
|
235
232
|
self,
|
|
236
233
|
*,
|
|
@@ -259,30 +256,16 @@ class FenixApiClient:
|
|
|
259
256
|
def update_memory(self, memory_id: str, payload: Mapping[str, Any]) -> Any:
|
|
260
257
|
return self._request("PATCH", f"/api/memories/{memory_id}", json=payload)
|
|
261
258
|
|
|
262
|
-
def delete_memory(self, memory_id: str) -> Any:
|
|
263
|
-
return self._request("DELETE", f"/api/memories/{memory_id}")
|
|
264
|
-
|
|
265
|
-
def list_memories_by_tags(self, *, tags: str) -> Any:
|
|
266
|
-
params = self._build_params(required={"tags": tags})
|
|
267
|
-
return self._request("GET", "/api/memories/tags", params=params)
|
|
268
|
-
|
|
269
259
|
def record_memory_access(self, memory_id: str) -> Any:
|
|
270
260
|
return self._request("POST", f"/api/memories/{memory_id}/access")
|
|
271
261
|
|
|
272
|
-
def
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
def consolidate_memories(self, payload: Mapping[str, Any]) -> Any:
|
|
278
|
-
return self._request(
|
|
279
|
-
"POST", "/api/memory-intelligence/consolidate", json=payload
|
|
280
|
-
)
|
|
262
|
+
def save_memory(self, payload: Mapping[str, Any]) -> Any:
|
|
263
|
+
"""Smart save memory - creates or updates based on semantic similarity."""
|
|
264
|
+
return self._request("POST", "/api/memories/save", json=payload)
|
|
281
265
|
|
|
282
|
-
def
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
266
|
+
def search_memories(self, payload: Mapping[str, Any]) -> Any:
|
|
267
|
+
"""Search memories using semantic similarity (embeddings)."""
|
|
268
|
+
return self._request("POST", "/api/memories/search", json=payload)
|
|
286
269
|
|
|
287
270
|
# ------------------------------------------------------------------
|
|
288
271
|
# Knowledge: documentation
|
|
@@ -642,3 +625,105 @@ class FenixApiClient:
|
|
|
642
625
|
def export_merged_rules(self, format: str) -> Any:
|
|
643
626
|
params = self._build_params(required={"format": format})
|
|
644
627
|
return self._request("GET", "/api/rules/export/merged", params=params)
|
|
628
|
+
|
|
629
|
+
# ------------------------------------------------------------------
|
|
630
|
+
# API Catalog
|
|
631
|
+
# ------------------------------------------------------------------
|
|
632
|
+
|
|
633
|
+
def list_api_catalog(self, **filters: Any) -> Any:
|
|
634
|
+
"""List API specifications with optional filters."""
|
|
635
|
+
return self._request("GET", "/api/api-catalog", params=_strip_none(filters))
|
|
636
|
+
|
|
637
|
+
def get_api_catalog(self, spec_id: str) -> Any:
|
|
638
|
+
"""Get API specification details by ID."""
|
|
639
|
+
return self._request("GET", f"/api/api-catalog/{spec_id}")
|
|
640
|
+
|
|
641
|
+
def search_api_catalog_text(
|
|
642
|
+
self,
|
|
643
|
+
*,
|
|
644
|
+
query: str,
|
|
645
|
+
limit: int = 20,
|
|
646
|
+
offset: int = 0,
|
|
647
|
+
status: Optional[str] = None,
|
|
648
|
+
tags: Optional[List[str]] = None,
|
|
649
|
+
) -> Any:
|
|
650
|
+
"""Full-text search in API specifications."""
|
|
651
|
+
params = self._build_params(
|
|
652
|
+
required={"q": query},
|
|
653
|
+
optional={
|
|
654
|
+
"limit": limit,
|
|
655
|
+
"offset": offset,
|
|
656
|
+
"status": status,
|
|
657
|
+
"tags": ",".join(tags) if tags else None,
|
|
658
|
+
},
|
|
659
|
+
)
|
|
660
|
+
return self._request("GET", "/api/api-catalog/search/apis", params=params)
|
|
661
|
+
|
|
662
|
+
def search_api_catalog_endpoints_text(
|
|
663
|
+
self,
|
|
664
|
+
*,
|
|
665
|
+
query: str,
|
|
666
|
+
limit: int = 20,
|
|
667
|
+
offset: int = 0,
|
|
668
|
+
specification_id: Optional[str] = None,
|
|
669
|
+
method: Optional[str] = None,
|
|
670
|
+
) -> Any:
|
|
671
|
+
"""Full-text search in API endpoints."""
|
|
672
|
+
params = self._build_params(
|
|
673
|
+
required={"q": query},
|
|
674
|
+
optional={
|
|
675
|
+
"limit": limit,
|
|
676
|
+
"offset": offset,
|
|
677
|
+
"specificationId": specification_id,
|
|
678
|
+
"method": method,
|
|
679
|
+
},
|
|
680
|
+
)
|
|
681
|
+
return self._request("GET", "/api/api-catalog/search/endpoints", params=params)
|
|
682
|
+
|
|
683
|
+
def search_api_catalog_semantic(
|
|
684
|
+
self,
|
|
685
|
+
*,
|
|
686
|
+
query: str,
|
|
687
|
+
limit: int = 20,
|
|
688
|
+
offset: int = 0,
|
|
689
|
+
threshold: Optional[float] = None,
|
|
690
|
+
status: Optional[str] = None,
|
|
691
|
+
tags: Optional[List[str]] = None,
|
|
692
|
+
) -> Any:
|
|
693
|
+
"""Semantic search in API specifications using embeddings."""
|
|
694
|
+
params = self._build_params(
|
|
695
|
+
required={"q": query},
|
|
696
|
+
optional={
|
|
697
|
+
"limit": limit,
|
|
698
|
+
"offset": offset,
|
|
699
|
+
"threshold": threshold,
|
|
700
|
+
"status": status,
|
|
701
|
+
"tags": ",".join(tags) if tags else None,
|
|
702
|
+
},
|
|
703
|
+
)
|
|
704
|
+
return self._request("GET", "/api/api-catalog/semantic/apis", params=params)
|
|
705
|
+
|
|
706
|
+
def search_api_catalog_endpoints_semantic(
|
|
707
|
+
self,
|
|
708
|
+
*,
|
|
709
|
+
query: str,
|
|
710
|
+
limit: int = 20,
|
|
711
|
+
offset: int = 0,
|
|
712
|
+
threshold: Optional[float] = None,
|
|
713
|
+
specification_id: Optional[str] = None,
|
|
714
|
+
method: Optional[str] = None,
|
|
715
|
+
) -> Any:
|
|
716
|
+
"""Semantic search in API endpoints using embeddings."""
|
|
717
|
+
params = self._build_params(
|
|
718
|
+
required={"q": query},
|
|
719
|
+
optional={
|
|
720
|
+
"limit": limit,
|
|
721
|
+
"offset": offset,
|
|
722
|
+
"threshold": threshold,
|
|
723
|
+
"specificationId": specification_id,
|
|
724
|
+
"method": method,
|
|
725
|
+
},
|
|
726
|
+
)
|
|
727
|
+
return self._request(
|
|
728
|
+
"GET", "/api/api-catalog/semantic/endpoints", params=params
|
|
729
|
+
)
|
|
@@ -63,6 +63,18 @@ class SimpleMcpServer:
|
|
|
63
63
|
# Notifications do not require a response
|
|
64
64
|
return None
|
|
65
65
|
|
|
66
|
+
if method == "notifications/cancelled":
|
|
67
|
+
# Client cancelled a request - no response needed
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
if method == "logging/setLevel":
|
|
71
|
+
# Acknowledge log level change request (we don't actually change anything)
|
|
72
|
+
return {
|
|
73
|
+
"jsonrpc": "2.0",
|
|
74
|
+
"id": request_id,
|
|
75
|
+
"result": {},
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
raise McpServerError(f"Unsupported method: {method}")
|
|
67
79
|
|
|
68
80
|
|