""" MCP Extensions for Context Awareness Implements the Factory Pattern to wrap existing tools with context and defines 5 new tools for the Context Compiler. """ from typing import Any, Dict, List, Callable import functools import json import os from .compiler import ContextCompiler # Singleton compiler instance COMPILER = ContextCompiler(skins_dir=os.path.join(os.path.dirname(__file__), "skins")) # --- FACTORY PATTERN: Context Wrapper --- def with_context(default_manifest_path: str = None): """ Decorator that injects the compiled context into the tool's arguments. Use this to upgrade existing 'token extractor' tools to be 'context aware'. The manifest path is extracted from kwargs['manifest_path'] if present, otherwise falls back to the default_manifest_path provided at decoration time. """ def decorator(func: Callable): @functools.wraps(func) def wrapper(*args, **kwargs): # 1. Get manifest path (runtime kwarg or decorator default) manifest_path = kwargs.get('manifest_path', default_manifest_path) if not manifest_path: raise ValueError("No manifest_path provided to context-aware tool") # 2. Compile Context context = COMPILER.compile(manifest_path) # 3. Inject into kwargs kwargs['dss_context'] = context # 4. Execute Tool return func(*args, **kwargs) return wrapper return decorator # --- 5 NEW MCP TOOLS --- def get_active_context(manifest_path: str, debug: bool = False, force_refresh: bool = False) -> str: """ [Tool 1] Returns the fully resolved JSON context for the project. Set debug=True to see provenance (which layer defined which token). Set force_refresh=True to bypass cache (for long-running servers). """ context = COMPILER.compile(manifest_path, debug=debug, force_refresh=force_refresh) return json.dumps(context, indent=2) def resolve_token(manifest_path: str, token_path: str, force_refresh: bool = False) -> str: """ [Tool 2] Resolves a specific token value (e.g. 'colors.primary') through the cascade. Set force_refresh=True to bypass cache (for long-running servers). """ context = COMPILER.compile(manifest_path, force_refresh=force_refresh) keys = token_path.split('.') current = context.get("tokens", {}) for k in keys: if isinstance(current, dict) and k in current: current = current[k] else: return f"Token not found: {token_path}" return str(current) def validate_manifest(manifest_path: str) -> str: """ [Tool 3] Validates the ds.config.json against the schema. """ # In a full implementation, we would use 'jsonschema' library here. # For now, we perform a basic structural check via the Compiler's loader. try: COMPILER.compile(manifest_path) return "Valid: Project manifest builds successfully." except Exception as e: return f"Invalid: {str(e)}" def list_skins() -> str: """ [Tool 4] Lists all available skins in the registry. """ skins_path = COMPILER.skins_dir if not skins_path.exists(): return "No skins directory found." skins = [f.stem for f in skins_path.glob("*.json")] return json.dumps(skins) def get_compiler_status() -> str: """ [Tool 5] Returns the health and configuration of the Context Compiler. """ status = { "status": "active", "skins_directory": str(COMPILER.skins_dir), "cached_skins": list(COMPILER.cache.keys()), "safe_boot_ready": True } return json.dumps(status, indent=2) # Instructions for Main Server File: # 1. Import these tools # 2. Register them with the MCP server instance # 3. Apply @with_context wrapper to legacy tools if dynamic context is needed