mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
203 lines
6.6 KiB
Python
203 lines
6.6 KiB
Python
"""MCP context provider."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional, List, TYPE_CHECKING
|
|
|
|
from codexlens.mcp.schema import (
|
|
MCPContext,
|
|
SymbolInfo,
|
|
ReferenceInfo,
|
|
RelatedSymbol,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from codexlens.storage.global_index import GlobalSymbolIndex
|
|
from codexlens.storage.registry import RegistryStore
|
|
from codexlens.search.chain_search import ChainSearchEngine
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MCPProvider:
|
|
"""Builds MCP context objects from codex-lens data."""
|
|
|
|
def __init__(
|
|
self,
|
|
global_index: "GlobalSymbolIndex",
|
|
search_engine: "ChainSearchEngine",
|
|
registry: "RegistryStore",
|
|
) -> None:
|
|
self.global_index = global_index
|
|
self.search_engine = search_engine
|
|
self.registry = registry
|
|
|
|
def build_context(
|
|
self,
|
|
symbol_name: str,
|
|
context_type: str = "symbol_explanation",
|
|
include_references: bool = True,
|
|
include_related: bool = True,
|
|
max_references: int = 10,
|
|
) -> Optional[MCPContext]:
|
|
"""Build comprehensive context for a symbol.
|
|
|
|
Args:
|
|
symbol_name: Name of the symbol to contextualize
|
|
context_type: Type of context being requested
|
|
include_references: Whether to include reference locations
|
|
include_related: Whether to include related symbols
|
|
max_references: Maximum number of references to include
|
|
|
|
Returns:
|
|
MCPContext object or None if symbol not found
|
|
"""
|
|
# Look up symbol
|
|
symbols = self.global_index.search(symbol_name, prefix_mode=False, limit=1)
|
|
|
|
if not symbols:
|
|
logger.debug(f"Symbol not found for MCP context: {symbol_name}")
|
|
return None
|
|
|
|
symbol = symbols[0]
|
|
|
|
# Build SymbolInfo
|
|
symbol_info = SymbolInfo(
|
|
name=symbol.name,
|
|
kind=symbol.kind,
|
|
file_path=symbol.file or "",
|
|
line_start=symbol.range[0],
|
|
line_end=symbol.range[1],
|
|
signature=None, # Symbol entity doesn't have signature
|
|
documentation=None, # Symbol entity doesn't have docstring
|
|
)
|
|
|
|
# Extract definition source code
|
|
definition = self._extract_definition(symbol)
|
|
|
|
# Get references
|
|
references = []
|
|
if include_references:
|
|
refs = self.search_engine.search_references(
|
|
symbol_name,
|
|
limit=max_references,
|
|
)
|
|
references = [
|
|
ReferenceInfo(
|
|
file_path=r.file_path,
|
|
line=r.line,
|
|
column=r.column,
|
|
context=r.context,
|
|
relationship_type=r.relationship_type,
|
|
)
|
|
for r in refs
|
|
]
|
|
|
|
# Get related symbols
|
|
related_symbols = []
|
|
if include_related:
|
|
related_symbols = self._get_related_symbols(symbol)
|
|
|
|
return MCPContext(
|
|
context_type=context_type,
|
|
symbol=symbol_info,
|
|
definition=definition,
|
|
references=references,
|
|
related_symbols=related_symbols,
|
|
metadata={
|
|
"source": "codex-lens",
|
|
},
|
|
)
|
|
|
|
def _extract_definition(self, symbol) -> Optional[str]:
|
|
"""Extract source code for symbol definition."""
|
|
try:
|
|
file_path = Path(symbol.file) if symbol.file else None
|
|
if not file_path or not file_path.exists():
|
|
return None
|
|
|
|
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
lines = content.split("\n")
|
|
|
|
start = symbol.range[0] - 1
|
|
end = symbol.range[1]
|
|
|
|
if start >= len(lines):
|
|
return None
|
|
|
|
return "\n".join(lines[start:end])
|
|
except Exception as e:
|
|
logger.debug(f"Failed to extract definition: {e}")
|
|
return None
|
|
|
|
def _get_related_symbols(self, symbol) -> List[RelatedSymbol]:
|
|
"""Get symbols related to the given symbol."""
|
|
related = []
|
|
|
|
try:
|
|
# Search for symbols that might be related by name patterns
|
|
# This is a simplified implementation - could be enhanced with relationship data
|
|
|
|
# Look for imports/callers via reference search
|
|
refs = self.search_engine.search_references(symbol.name, limit=20)
|
|
|
|
seen_names = set()
|
|
for ref in refs:
|
|
# Extract potential symbol name from context
|
|
if ref.relationship_type and ref.relationship_type not in seen_names:
|
|
related.append(RelatedSymbol(
|
|
name=f"{Path(ref.file_path).stem}",
|
|
kind="module",
|
|
relationship=ref.relationship_type,
|
|
file_path=ref.file_path,
|
|
))
|
|
seen_names.add(ref.relationship_type)
|
|
if len(related) >= 10:
|
|
break
|
|
|
|
except Exception as e:
|
|
logger.debug(f"Failed to get related symbols: {e}")
|
|
|
|
return related
|
|
|
|
def build_context_for_file(
|
|
self,
|
|
file_path: Path,
|
|
context_type: str = "file_overview",
|
|
) -> MCPContext:
|
|
"""Build context for an entire file."""
|
|
# Try to get symbols by searching with file path
|
|
# Note: GlobalSymbolIndex doesn't have search_by_file, so we use a different approach
|
|
symbols = []
|
|
|
|
# Search for common symbols that might be in this file
|
|
# This is a simplified approach - a full implementation would query by file path
|
|
try:
|
|
# Use the global index to search for symbols from this file
|
|
file_str = str(file_path.resolve())
|
|
# Get all symbols and filter by file path (not efficient but works)
|
|
all_symbols = self.global_index.search("", prefix_mode=True, limit=1000)
|
|
symbols = [s for s in all_symbols if s.file and str(Path(s.file).resolve()) == file_str]
|
|
except Exception as e:
|
|
logger.debug(f"Failed to get file symbols: {e}")
|
|
|
|
related = [
|
|
RelatedSymbol(
|
|
name=s.name,
|
|
kind=s.kind,
|
|
relationship="defines",
|
|
)
|
|
for s in symbols
|
|
]
|
|
|
|
return MCPContext(
|
|
context_type=context_type,
|
|
related_symbols=related,
|
|
metadata={
|
|
"file_path": str(file_path),
|
|
"symbol_count": len(symbols),
|
|
},
|
|
)
|