mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
170 lines
5.8 KiB
Python
170 lines
5.8 KiB
Python
import httpx
|
|
from prowler_mcp_server import __version__
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class SearchResult(BaseModel):
|
|
"""Search result model."""
|
|
|
|
path: str = Field(description="Document path")
|
|
title: str = Field(description="Document title")
|
|
url: str = Field(description="Documentation URL")
|
|
highlights: list[str] = Field(
|
|
description="Highlighted content snippets showing query matches with <mark><b> tags",
|
|
default_factory=list,
|
|
)
|
|
score: float = Field(
|
|
description="Relevance score for the search result", default=0.0
|
|
)
|
|
|
|
|
|
class ProwlerDocsSearchEngine:
|
|
"""Prowler documentation search using Mintlify API."""
|
|
|
|
def __init__(self):
|
|
"""Initialize the search engine."""
|
|
self.api_base_url = (
|
|
"https://api.mintlifytrieve.com/api/chunk_group/group_oriented_autocomplete"
|
|
)
|
|
self.dataset_id = "0096ba11-3f72-463b-9d95-b788495ac392"
|
|
self.api_key = "tr-T6JLeTkFXeNbNPyhijtI9XhIncydQQ3O"
|
|
self.docs_base_url = "https://prowler.mintlify.app"
|
|
|
|
# HTTP client for Mintlify API
|
|
self.mintlify_client = httpx.Client(
|
|
timeout=30.0,
|
|
headers={
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
"User-Agent": f"prowler-mcp-server/{__version__}",
|
|
"TR-Dataset": self.dataset_id,
|
|
"Authorization": self.api_key,
|
|
"X-API-Version": "V2",
|
|
},
|
|
)
|
|
|
|
# HTTP client for Mintlify documentation
|
|
self.docs_client = httpx.Client(
|
|
timeout=30.0,
|
|
headers={
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
"User-Agent": f"prowler-mcp-server/{__version__}",
|
|
},
|
|
)
|
|
|
|
def search(self, query: str, page_size: int = 5) -> list[SearchResult]:
|
|
"""
|
|
Search documentation using Mintlify API.
|
|
|
|
Args:
|
|
query: Search query string
|
|
page_size: Maximum number of results to return
|
|
|
|
Returns:
|
|
list of search results
|
|
"""
|
|
try:
|
|
# Construct request body
|
|
payload = {
|
|
"query": query,
|
|
"search_type": "fulltext",
|
|
"extend_results": True,
|
|
"highlight_options": {
|
|
"highlight_window": 10,
|
|
"highlight_max_num": 1,
|
|
"highlight_max_length": 2,
|
|
"highlight_strategy": "exactmatch",
|
|
"highlight_delimiters": ["?", ",", ".", "!", "\n"],
|
|
},
|
|
"score_threshold": 0.2,
|
|
"filters": {"must_not": [{"field": "tag_set", "match": ["code"]}]},
|
|
"page_size": page_size,
|
|
"group_size": 3,
|
|
}
|
|
|
|
# Make request to Mintlify API
|
|
response = self.mintlify_client.post(
|
|
self.api_base_url,
|
|
json=payload,
|
|
)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
# Parse results
|
|
results = []
|
|
for result in data.get("results", []):
|
|
group = result.get("group", {})
|
|
chunks = result.get("chunks", [])
|
|
|
|
# Get document path and title from group
|
|
doc_path = group.get("name", "")
|
|
group_title = group.get("name", "").replace("/", " / ").title()
|
|
|
|
# If chunks exist, use the first chunk's title from metadata
|
|
title = group_title
|
|
if chunks:
|
|
first_chunk = chunks[0].get("chunk", {})
|
|
metadata = first_chunk.get("metadata", {})
|
|
title = metadata.get("title", group_title)
|
|
|
|
# Construct full URL to docs
|
|
full_url = f"{self.docs_base_url}/{doc_path}"
|
|
|
|
# Extract highlights and scores from chunks
|
|
highlights = []
|
|
max_score = 0.0
|
|
for chunk_data in chunks:
|
|
chunk_highlights = chunk_data.get("highlights", [])
|
|
highlights.extend(chunk_highlights)
|
|
# Track the highest score among all chunks in this group
|
|
chunk_score = chunk_data.get("score", 0.0)
|
|
max_score = max(max_score, chunk_score)
|
|
|
|
results.append(
|
|
SearchResult(
|
|
path=doc_path,
|
|
title=title,
|
|
url=full_url,
|
|
highlights=highlights,
|
|
score=max_score,
|
|
)
|
|
)
|
|
|
|
return results
|
|
|
|
except Exception as e:
|
|
# Return empty list on error
|
|
print(f"Search error: {e}")
|
|
return []
|
|
|
|
def get_document(self, doc_path: str) -> str | None:
|
|
"""
|
|
Get full document content from Mintlify documentation.
|
|
|
|
Args:
|
|
doc_path: Path to the documentation file (e.g., "getting-started/installation")
|
|
|
|
Returns:
|
|
Full markdown content of the documentation, or None if not found
|
|
"""
|
|
try:
|
|
# Clean up the path
|
|
doc_path = doc_path.rstrip("/")
|
|
|
|
# Add .md extension if not present (Mintlify serves both .md and .mdx)
|
|
if not doc_path.endswith(".md"):
|
|
doc_path = f"{doc_path}.md"
|
|
|
|
# Construct Mintlify URL
|
|
url = f"{self.docs_base_url}/{doc_path}"
|
|
|
|
# Fetch the documentation page
|
|
response = self.docs_client.get(url)
|
|
response.raise_for_status()
|
|
|
|
return response.text
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching document: {e}")
|
|
return None
|