//! MCP server orchestration: tool routing, session-scoped workspace adoption, runtime caches,
//! and the public tool handlers agents invoke over streamable HTTP or stdio.

use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{
    Arc, Mutex, RwLock,
    atomic::{AtomicU64, Ordering},
};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use crate::domain::model::{GeneratedStructuralFollowUp, ReferenceMatch, SymbolMatch};
use crate::domain::{
    ChannelResult, EvidenceChannel, FriggError, FriggResult, WorkloadPrecisionMode,
};
use crate::graph::{
    PreciseRelationshipKind, RelationKind, ScipIngestError, ScipResourceBudgets, SymbolGraph,
};
use crate::indexer::{
    FileMetadataDigest, HeuristicReference, HeuristicReferenceConfidence,
    HeuristicReferenceEvidence, HeuristicReferenceResolver, IndexMode, ManifestBuilder,
    ManifestDiagnosticKind, SourceSpan, SymbolDefinition, SymbolExtractionOutput,
    byte_offset_for_line_column, extract_php_source_evidence_from_source,
    extract_symbols_for_paths, extract_symbols_from_source,
    generated_follow_up_structural_at_location_in_source, index_repository_with_runtime_config,
    inspect_syntax_tree_in_source, inspect_syntax_tree_with_follow_up_in_source,
    navigation_symbol_target_rank, php_declaration_relation_edges_for_file,
    php_heuristic_implementation_candidates_for_target, register_symbol_definitions,
    resolve_php_target_evidence_edges, search_structural_in_source,
    search_structural_with_follow_up_in_source,
};
use crate::languages::{
    FLUX_REGISTRY_VERSION, HeuristicImplementationStrategy, LanguageCapability,
    LanguageSupportCapability, SymbolLanguage, extract_blade_source_evidence_from_source,
    heuristic_implementation_strategy, heuristic_rust_implementation_candidates,
    mark_local_flux_overlays, parse_rust_impl_signature, parse_supported_language,
    resolve_blade_relation_evidence_edges, rust_enclosing_symbol_context,
    rust_navigation_query_hint_from_source, rust_relative_path_module_segments,
    rust_source_suffix_looks_like_call, supported_language_for_path,
};
use crate::manifest_validation::{
    RepositoryManifestFreshness, RepositorySemanticFreshness, repository_freshness_status,
};
use crate::path_class::{repository_path_class, repository_path_class_rank};
use crate::searcher::{
    HybridChannelWeights, ProjectionStoreService, SearchDiagnosticKind, SearchFilters,
    SearchHybridQuery, SearchTextQuery, TextSearcher, ValidatedManifestCandidateCache,
    compile_safe_regex,
};
use crate::settings::SemanticRuntimeCredentials;
use crate::settings::{FriggConfig, SemanticRuntimeConfig};
use crate::storage::{Storage, ensure_provenance_db_parent_dir, resolve_provenance_db_path};
use protobuf::Enum;
use rmcp::handler::server::router::tool::ToolRouter;
use rmcp::handler::server::wrapper::{Json, Parameters};
use rmcp::model::{
    CallToolResult, ContentBlock, Implementation, Meta, ServerCapabilities, ServerInfo,
};
use rmcp::transport::{
    StreamableHttpServerConfig, StreamableHttpService,
    streamable_http_server::session::local::LocalSessionManager,
};
use rmcp::{
    ErrorData, Peer, RoleServer, ServerHandler, ServiceExt, tool, tool_handler, tool_router,
};
use scip::types::symbol_information::Kind as ScipSymbolKind;
use serde_json::{Value, json};
use tokio::task;
use tracing::{info, warn};
use uuid::Uuid;

use crate::agent_directive;
#[cfg(feature = "playbook")]
use crate::mcp::advanced::deep_search::{
    DeepSearchHarness, DeepSearchPlaybook, DeepSearchTraceArtifact, DeepSearchTraceOutcome,
};
use crate::mcp::explorer::{
    DEFAULT_CONTEXT_LINES, DEFAULT_MAX_MATCHES, ExploreMatcher, ExploreScopeRequest,
    LossyLineSliceError, MAX_CONTEXT_LINES, line_window_around_anchor, validate_anchor,
    validate_cursor,
};
use crate::mcp::guidance::{
    ROUTING_GUIDE_PROMPT_NAME, SHELL_GUIDANCE_RESOURCE_URI, SHELL_REPLACEMENT_MAP_RESOURCE_URI,
    SUPPORT_MATRIX_RESOURCE_URI, TOOL_SURFACE_RESOURCE_URI, guidance_prompts, policy_resources,
    read_guidance_prompt, read_policy_resource,
};
use crate::mcp::server_cache::{
    CachedHeuristicReferences, CachedPreciseGeneratorProbe, CachedRepositoryResponseFreshness,
    CachedWorkspacePreciseGeneration, FileContentSnapshot, HeuristicReferenceCacheKey,
    PreciseGeneratorProbeCacheKey, RepositoryFreshnessCacheScope, RepositoryResponseCacheFreshness,
    RepositoryResponseCacheFreshnessMode, RepositoryResponseFreshnessCacheKey, RuntimeCacheBudget,
    RuntimeCacheEvent, RuntimeCacheFamily, RuntimeCacheRegistry, RuntimeCacheTelemetry,
    SessionResultHandleCache, SessionResultHandleEntry, WorkspaceSemanticRefreshPlan,
};
use crate::mcp::server_state::{
    CachedPreciseGraph, DeterministicSignatureHasher, DisambiguationRequiredSymbolTarget,
    ExploreExecution, FindReferencesExecution, FindReferencesResourceBudgets,
    NavigationTargetSelection, PreciseArtifactFailureSample, PreciseCoverageMode,
    PreciseGraphCacheKey, PreciseIngestStats, RankedSymbolMatch, ReadFileExecution,
    RepositoryDiagnosticsSummary, RepositorySymbolCorpus, ResolvedNavigationTarget,
    ResolvedSymbolTarget, RuntimeTaskGuard, RuntimeTaskRegistry, ScipArtifactDigest,
    ScipArtifactDiscovery, ScipArtifactFormat, ScipCandidateDirectoryDigest, SearchHybridExecution,
    SearchSymbolExecution, SearchTextExecution, SymbolCandidate, SymbolCorpusCacheKey,
};
use crate::mcp::tool_surface::{
    TOOL_SURFACE_PROFILE_ENV, ToolSurfaceParityDiff, ToolSurfaceProfile,
    active_runtime_tool_surface_profile, diff_runtime_against_profile_manifest,
    manifest_for_tool_surface_profile,
};
use crate::mcp::types::{
    CallHierarchyMatch, DocumentSymbolsParams, DocumentSymbolsResponse, ExploreMatch,
    ExploreMetadata, ExploreOperation, ExploreParams, ExploreResponse, ExploreWindow,
    FindDeclarationsParams, FindDeclarationsResponse, FindImplementationsParams,
    FindImplementationsResponse, FindReferencesParams, FindReferencesResponse,
    GoToDefinitionParams, GoToDefinitionResponse, ImplementationMatch, IncomingCallsParams,
    IncomingCallsResponse, InspectSyntaxTreeParams, InspectSyntaxTreeResponse, ListFilesParams,
    ListFilesResponse, ListRepositoriesParams, ListRepositoriesResponse, NavigationAvailability,
    NavigationLocation, NavigationMode, NavigationTargetSelectionStatus,
    NavigationTargetSelectionSummary, OutgoingCallsParams, OutgoingCallsResponse, ReadFileParams,
    ReadFileResponse, ReadMatchParams, ReadMatchResponse, ReadPresentationMode, RepositorySummary,
    ResponseMode, RuntimeStatusSummary, RuntimeTaskKind, RuntimeTaskStatus, RuntimeTaskSummary,
    SearchHybridChannelWeightsParams, SearchHybridMatch, SearchHybridParams, SearchHybridResponse,
    SearchPatternType, SearchStructuralParams, SearchStructuralResponse, SearchSymbolParams,
    SearchSymbolPathClass, SearchSymbolResponse, SearchTextParams, SearchTextResponse,
    SyntaxTreeNodeItem, WRITE_CONFIRM_PARAM, WRITE_CONFIRMATION_REQUIRED_ERROR_CODE,
    WorkspaceAttachAction, WorkspaceAttachIndexMode, WorkspaceAttachParams,
    WorkspaceAttachResponse, WorkspaceCurrentParams, WorkspaceCurrentResponse,
    WorkspaceDetachParams, WorkspaceDetachResponse, WorkspaceIndexAction,
    WorkspaceIndexComponentState, WorkspaceIndexComponentSummary, WorkspaceIndexHealthSummary,
    WorkspaceIndexLifecyclePhase, WorkspaceIndexLifecycleSummary, WorkspaceIndexParams,
    WorkspaceIndexResponse, WorkspaceParams, WorkspacePreciseArtifactFailureSummary,
    WorkspacePreciseCoverageMode, WorkspacePreciseGenerationAction,
    WorkspacePreciseGenerationStatus, WorkspacePreciseGenerationSummary,
    WorkspacePreciseGeneratorState, WorkspacePreciseGeneratorSummary, WorkspacePreciseIngestState,
    WorkspacePreciseIngestSummary, WorkspacePreciseLifecyclePhase,
    WorkspacePreciseLifecycleSummary, WorkspacePreciseState, WorkspacePreciseSummary,
    WorkspacePrepareParams, WorkspacePrepareResponse, WorkspaceRecommendedAction,
    WorkspaceResolveMode, WorkspaceResponse, WorkspaceStorageIndexState, WorkspaceStorageSummary,
};
#[cfg(feature = "playbook")]
use crate::mcp::types::{
    PlaybookComposeCitationsParams, PlaybookComposeCitationsResponse, PlaybookReplayParams,
    PlaybookReplayResponse, PlaybookRunParams, PlaybookRunResponse,
};
use crate::mcp::workspace_registry::{AttachedWorkspace, WorkspaceRegistry};
use crate::settings::RuntimeProfile;

mod content;
mod errors;
mod execution;
mod navigation_cache;
mod navigation_metadata;
mod navigation_precise;
mod navigation_resolution;
mod navigation_tools;
#[cfg(feature = "playbook")]
mod playbook;
mod precise_graph;
mod presentation;
mod provenance;
mod runtime_cache;
mod runtime_status;
mod search_tools;
mod symbol_index;
mod workspace;
mod workspace_session;

/// Aggregate counts from building repository symbol corpora for diagnostics benchmarks.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct SymbolCorpusBenchmarkSummary {
    pub repository_count: usize,
    pub source_file_count: usize,
    pub symbol_count: usize,
    pub php_evidence_files: usize,
    pub blade_evidence_files: usize,
}

/// Aggregate counts from building or reusing a workspace precise graph for diagnostics benchmarks.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PreciseGraphBenchmarkSummary {
    pub artifact_count: usize,
    pub artifacts_ingested: usize,
    pub artifacts_failed: usize,
    pub precise_symbol_count: usize,
    pub precise_occurrence_count: usize,
    pub precise_relationship_count: usize,
    pub reused_cache: bool,
}

/// Completion status for a display-only MCP tool call event.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolCallDisplayStatus {
    Ok,
    Failed,
}

impl ToolCallDisplayStatus {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Ok => "ok",
            Self::Failed => "failed",
        }
    }
}

/// Display-only summary emitted when an MCP tool call completes.
#[derive(Debug, Clone, PartialEq)]
pub struct ToolCallDisplayEvent {
    pub tool_name: String,
    pub duration_ms: u64,
    pub status: ToolCallDisplayStatus,
    pub context_saved_percent: Option<f64>,
    pub session_id: String,
}

/// Callback used by CLI transports to mirror completed MCP tool calls in human progress output.
pub type ToolCallDisplaySink = Arc<dyn Fn(ToolCallDisplayEvent) + Send + Sync + 'static>;

#[doc(hidden)]
pub fn benchmark_build_symbol_corpora_for_server(
    server: &FriggMcpServer,
    repository_id: Option<&str>,
) -> crate::domain::FriggResult<SymbolCorpusBenchmarkSummary> {
    let corpora = server
        .collect_repository_symbol_corpora(repository_id)
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to build symbol corpora for benchmark: {err:?}"
            ))
        })?;
    let mut summary = SymbolCorpusBenchmarkSummary {
        repository_count: corpora.len(),
        source_file_count: 0,
        symbol_count: 0,
        php_evidence_files: 0,
        blade_evidence_files: 0,
    };
    for corpus in corpora {
        summary.source_file_count = summary
            .source_file_count
            .saturating_add(corpus.source_paths.len());
        summary.symbol_count = summary.symbol_count.saturating_add(corpus.symbols.len());
        summary.php_evidence_files = summary
            .php_evidence_files
            .saturating_add(corpus.php_evidence_by_relative_path.len());
        summary.blade_evidence_files = summary
            .blade_evidence_files
            .saturating_add(corpus.blade_evidence_by_relative_path.len());
    }
    Ok(summary)
}

#[doc(hidden)]
pub fn benchmark_build_symbol_corpora(
    config: FriggConfig,
    repository_id: Option<&str>,
) -> crate::domain::FriggResult<SymbolCorpusBenchmarkSummary> {
    let server = FriggMcpServer::new(config);
    if server.attached_workspaces().is_empty() {
        let known = server.known_workspaces();
        let workspace = if let Some(repository_id) = repository_id {
            known
                .into_iter()
                .find(|workspace| workspace.repository_id == repository_id)
        } else {
            known.into_iter().next()
        }
        .ok_or_else(|| {
            FriggError::Internal(
                "failed to prepare benchmark server: no known workspace roots".to_owned(),
            )
        })?;
        server.adopt_workspace(&workspace, true).map_err(|err| {
            FriggError::Internal(format!("failed to adopt workspace for benchmark: {err:?}"))
        })?;
    }
    benchmark_build_symbol_corpora_for_server(&server, repository_id)
}

#[doc(hidden)]
pub fn benchmark_precise_graph_for_server(
    server: &FriggMcpServer,
    repository_id: &str,
) -> crate::domain::FriggResult<PreciseGraphBenchmarkSummary> {
    let mut roots = server
        .roots_for_repository(Some(repository_id))
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to resolve repository roots for precise benchmark: {err:?}"
            ))
        })?;
    roots.sort_by(|left, right| left.0.cmp(&right.0).then(left.1.cmp(&right.1)));
    let (_, root) = roots.into_iter().next().ok_or_else(|| {
        FriggError::Internal(
            "no attached workspace roots available for precise benchmark".to_owned(),
        )
    })?;
    let reused_cache = server
        .try_reuse_latest_precise_graph_for_repository(repository_id, &root)
        .is_some();
    let cached = server
        .precise_graph_for_repository_root(
            repository_id,
            &root,
            server.find_references_resource_budgets(),
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to build precise graph for benchmark: {err:?}"
            ))
        })?;
    let counts = cached.graph.precise_counts();
    Ok(PreciseGraphBenchmarkSummary {
        artifact_count: cached.ingest_stats.artifacts_discovered,
        artifacts_ingested: cached.ingest_stats.artifacts_ingested,
        artifacts_failed: cached.ingest_stats.artifacts_failed,
        precise_symbol_count: counts.symbols,
        precise_occurrence_count: counts.occurrences,
        precise_relationship_count: counts.relationships,
        reused_cache,
    })
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::mcp::server) enum NavigationPhpHelperKind {
    Translation,
    Route,
    Config,
    Env,
}

#[derive(Debug, Clone)]
pub(in crate::mcp::server) struct NavigationLocationTokenHint {
    symbol_query: String,
    relative_path: String,
    resolution_source: &'static str,
    helper_kind: Option<NavigationPhpHelperKind>,
    rust_hint: Option<crate::languages::RustNavigationQueryHint>,
}
/// Concrete streamable HTTP service type used when Frigg is exposed over MCP transport.
pub type FriggMcpService = StreamableHttpService<FriggMcpServer, LocalSessionManager>;

type PendingPreciseDirtyPathSets = (BTreeSet<String>, BTreeSet<String>);
type PendingPreciseDirtyPathMap = Arc<RwLock<BTreeMap<String, PendingPreciseDirtyPathSets>>>;

#[derive(Clone)]
/// Orchestrates Frigg's public MCP tool surface over shared config, caches, session state,
/// provenance, and optional watch-backed refresh state.
pub struct FriggMcpServer {
    config: Arc<FriggConfig>,
    tool_router: ToolRouter<Self>,
    tool_surface_profile: ToolSurfaceProfile,
    runtime_state: FriggMcpRuntimeState,
    session_state: FriggMcpSessionState,
    cache_state: FriggMcpCacheState,
}

#[derive(Clone)]
struct FriggMcpRuntimeState {
    runtime_profile: RuntimeProfile,
    runtime_watch_active: bool,
    semantic_runtime_credentials: Option<SemanticRuntimeCredentials>,
    workspace_registry: Arc<RwLock<WorkspaceRegistry>>,
    watch_runtime: Arc<RwLock<Option<Arc<crate::watch::WatchRuntime>>>>,
    runtime_task_registry: Arc<RwLock<RuntimeTaskRegistry>>,
    validated_manifest_candidate_cache: Arc<RwLock<ValidatedManifestCandidateCache>>,
    searcher_projection_store_service: ProjectionStoreService,
    runtime_cache_registry: Arc<RwLock<RuntimeCacheRegistry>>,
    runtime_cache_telemetry: Arc<RwLock<BTreeMap<RuntimeCacheFamily, RuntimeCacheTelemetry>>>,
    precise_generation_status_cache:
        Arc<RwLock<BTreeMap<String, CachedWorkspacePreciseGeneration>>>,
    precise_generation_pending_dirty_paths: PendingPreciseDirtyPathMap,
    tool_call_display_sink: Arc<RwLock<Option<ToolCallDisplaySink>>>,
}

#[derive(Clone)]
struct FriggMcpSessionState {
    inner: Arc<FriggMcpSessionStateInner>,
}

// Session adoption boundary: per-transport session tracks adopted repository_ids separately
// from the process-wide workspace registry and watch lease refcounts.
struct FriggMcpSessionStateInner {
    display_session_id: String,
    workspace_registry: Arc<RwLock<WorkspaceRegistry>>,
    watch_runtime: Arc<RwLock<Option<Arc<crate::watch::WatchRuntime>>>>,
    adopted_repository_ids: RwLock<BTreeSet<String>>,
    workspace_attach_states: RwLock<BTreeMap<String, WorkspaceAttachSessionState>>,
    session_default_repository_id: RwLock<Option<String>>,
    result_handles: RwLock<SessionResultHandleCache>,
}

#[derive(Debug, Default)]
struct WorkspaceAttachSessionState {
    in_flight: usize,
    completed: bool,
    rollback_requested: bool,
    rollback_previous_default_repository_id: Option<String>,
    default_restore_requested: bool,
    default_restore_previous_default_repository_id: Option<String>,
    default_confirmed: bool,
}

#[derive(Clone)]
struct FriggMcpCacheState {
    symbol_corpus_cache: Arc<RwLock<BTreeMap<SymbolCorpusCacheKey, Arc<RepositorySymbolCorpus>>>>,
    symbol_corpus_cache_epoch: Arc<AtomicU64>,
    precise_graph_cache: Arc<RwLock<BTreeMap<PreciseGraphCacheKey, Arc<CachedPreciseGraph>>>>,
    latest_precise_graph_cache: Arc<RwLock<BTreeMap<String, Arc<CachedPreciseGraph>>>>,
    repository_response_freshness_cache: Arc<
        RwLock<BTreeMap<RepositoryResponseFreshnessCacheKey, CachedRepositoryResponseFreshness>>,
    >,
    repository_response_freshness_cache_epoch: Arc<AtomicU64>,
    precise_generator_probe_cache:
        Arc<RwLock<BTreeMap<PreciseGeneratorProbeCacheKey, CachedPreciseGeneratorProbe>>>,
    heuristic_reference_cache:
        Arc<RwLock<BTreeMap<HeuristicReferenceCacheKey, CachedHeuristicReferences>>>,
    compiled_safe_regex_cache: Arc<RwLock<BTreeMap<String, regex::Regex>>>,
}

#[allow(clippy::enum_variant_names)]
#[derive(Debug, Clone)]
pub(super) struct ReadOnlyToolExecutionContext {
    pub(super) tool_name: &'static str,
    pub(super) repository_hint: Option<String>,
    pub(super) started_at: Instant,
    display_context_saved_percent: Arc<Mutex<Option<f64>>>,
}

#[derive(Debug, Clone)]
pub(super) struct ScopedReadOnlyToolExecutionContext {
    #[cfg(test)]
    pub(super) base: ReadOnlyToolExecutionContext,
    pub(super) scoped_workspaces: Vec<AttachedWorkspace>,
    pub(super) scoped_repository_ids: Vec<String>,
    pub(super) cache_freshness: RepositoryResponseCacheFreshness,
}

impl FriggMcpServer {
    const PROVENANCE_MAX_TEXT_CHARS: usize = 512;
    const FIND_REFERENCES_MAX_SCIP_ARTIFACTS: usize = 2_048;
    const FIND_REFERENCES_MAX_SOURCE_FILES: usize = 20_000;
    const FIND_REFERENCES_SCIP_ARTIFACT_BYTES_MULTIPLIER: usize = 8;
    const FIND_REFERENCES_SOURCE_FILE_BYTES_MULTIPLIER: usize = 4;
    const FIND_REFERENCES_TOTAL_BYTES_MULTIPLIER: usize = 128;
    const FIND_REFERENCES_SCIP_MAX_ELAPSED_MS: u64 = 5_000;
    const FIND_REFERENCES_SOURCE_MAX_ELAPSED_MS: u64 = 5_000;
    const FIND_REFERENCES_MIN_SCIP_DOCUMENT_BUDGET: usize = 1_024;
    const FIND_REFERENCES_DOCUMENT_BUDGET_MULTIPLIER: usize = 512;
    const PRECISE_FAILURE_SAMPLE_LIMIT: usize = 8;
    const PRECISE_DISCOVERY_SAMPLE_LIMIT: usize = 16;
    const WORKSPACE_INDEX_RESPONSE_PATH_LIMIT: usize = 50;
    const SEARCH_STRUCTURAL_MAX_QUERY_CHARS: usize = 4_096;
    const PROVENANCE_MATCH_SAMPLE_LIMIT: usize = 4;
    const REPOSITORY_RESPONSE_FRESHNESS_CACHE_MAX_ENTRIES: usize = 64;
    const PRECISE_GENERATOR_PROBE_CACHE_TTL: Duration = Duration::from_secs(30);
    const PRECISE_GENERATOR_PROBE_CACHE_MAX_ENTRIES: usize = 128;
    const SESSION_RESULT_HANDLE_TTL: Duration = Duration::from_secs(300);
    const SESSION_RESULT_HANDLE_MAX_ENTRIES: usize = 64;
    pub fn new(config: FriggConfig) -> Self {
        let enable_extended_tools =
            active_runtime_tool_surface_profile() == ToolSurfaceProfile::Extended;
        Self::new_with_runtime_options(config, enable_extended_tools)
    }

    pub fn new_with_runtime(
        config: FriggConfig,
        runtime_profile: RuntimeProfile,
        runtime_watch_active: bool,
        runtime_task_registry: Arc<RwLock<RuntimeTaskRegistry>>,
        validated_manifest_candidate_cache: Arc<RwLock<ValidatedManifestCandidateCache>>,
    ) -> Self {
        let enable_extended_tools =
            active_runtime_tool_surface_profile() == ToolSurfaceProfile::Extended;
        Self::new_with_runtime_context(
            config,
            enable_extended_tools,
            runtime_profile,
            runtime_watch_active,
            None,
            runtime_task_registry,
            validated_manifest_candidate_cache,
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub fn new_with_runtime_context(
        config: FriggConfig,
        enable_extended_tools: bool,
        runtime_profile: RuntimeProfile,
        runtime_watch_active: bool,
        watch_runtime: Option<Arc<crate::watch::WatchRuntime>>,
        runtime_task_registry: Arc<RwLock<RuntimeTaskRegistry>>,
        validated_manifest_candidate_cache: Arc<RwLock<ValidatedManifestCandidateCache>>,
    ) -> Self {
        let workspace_registry = WorkspaceRegistry::from_startup_repositories(
            config.repositories().into_iter().map(|repository| {
                (
                    repository.repository_id.0,
                    repository.display_name,
                    repository.root_path,
                )
            }),
        );
        let workspace_registry = Arc::new(RwLock::new(workspace_registry));
        let tool_surface_profile = if enable_extended_tools {
            ToolSurfaceProfile::Extended
        } else {
            ToolSurfaceProfile::Core
        };
        let watch_runtime = Arc::new(RwLock::new(watch_runtime));
        Self {
            config: Arc::new(config),
            tool_router: Self::filtered_tool_router(tool_surface_profile),
            tool_surface_profile,
            runtime_state: FriggMcpRuntimeState {
                runtime_profile,
                runtime_watch_active,
                semantic_runtime_credentials: None,
                workspace_registry: Arc::clone(&workspace_registry),
                watch_runtime: Arc::clone(&watch_runtime),
                runtime_task_registry,
                validated_manifest_candidate_cache,
                searcher_projection_store_service: ProjectionStoreService::new(),
                runtime_cache_registry: Arc::new(RwLock::new(RuntimeCacheRegistry::default())),
                runtime_cache_telemetry: Arc::new(RwLock::new(BTreeMap::new())),
                precise_generation_status_cache: Arc::new(RwLock::new(BTreeMap::new())),
                precise_generation_pending_dirty_paths: Arc::new(RwLock::new(BTreeMap::new())),
                tool_call_display_sink: Arc::new(RwLock::new(None)),
            },
            session_state: FriggMcpSessionState::new(workspace_registry, watch_runtime),
            cache_state: FriggMcpCacheState {
                symbol_corpus_cache: Arc::new(RwLock::new(BTreeMap::new())),
                symbol_corpus_cache_epoch: Arc::new(AtomicU64::new(0)),
                precise_graph_cache: Arc::new(RwLock::new(BTreeMap::new())),
                latest_precise_graph_cache: Arc::new(RwLock::new(BTreeMap::new())),
                repository_response_freshness_cache: Arc::new(RwLock::new(BTreeMap::new())),
                repository_response_freshness_cache_epoch: Arc::new(AtomicU64::new(0)),
                precise_generator_probe_cache: Arc::new(RwLock::new(BTreeMap::new())),
                heuristic_reference_cache: Arc::new(RwLock::new(BTreeMap::new())),
                compiled_safe_regex_cache: Arc::new(RwLock::new(BTreeMap::new())),
            },
        }
    }

    pub fn new_with_runtime_options(config: FriggConfig, enable_extended_tools: bool) -> Self {
        Self::new_with_runtime_context(
            config,
            enable_extended_tools,
            RuntimeProfile::StdioEphemeral,
            false,
            None,
            Arc::new(RwLock::new(RuntimeTaskRegistry::new())),
            Arc::new(RwLock::new(ValidatedManifestCandidateCache::default())),
        )
    }

    #[doc(hidden)]
    pub fn new_with_semantic_runtime_credentials(
        config: FriggConfig,
        credentials: SemanticRuntimeCredentials,
    ) -> Self {
        let mut server = Self::new(config);
        server.runtime_state.semantic_runtime_credentials = Some(credentials);
        server
    }

    pub fn runtime_registered_tool_names(&self) -> Vec<String> {
        self.tool_router
            .list_all()
            .into_iter()
            .map(|tool| tool.name.into_owned())
            .collect::<Vec<_>>()
    }

    pub fn runtime_tool_surface_parity(
        &self,
        profile: ToolSurfaceProfile,
    ) -> ToolSurfaceParityDiff {
        let runtime_names = self.runtime_registered_tool_names();
        diff_runtime_against_profile_manifest(profile, &runtime_names)
    }

    pub async fn serve_stdio(self) -> Result<(), rmcp::RmcpError> {
        let service = self.serve(rmcp::transport::stdio()).await?;
        service.waiting().await?;
        Ok(())
    }

    pub fn streamable_http_service(self, config: StreamableHttpServerConfig) -> FriggMcpService {
        StreamableHttpService::new(
            move || Ok(self.clone_for_new_session()),
            Arc::new(LocalSessionManager::default()),
            config,
        )
    }

    fn workspace_response(&self) -> WorkspaceResponse {
        let current_workspace = self.current_workspace();
        let repository = current_workspace
            .as_ref()
            .map(|workspace| self.public_repository_summary(workspace));
        let repositories = self
            .visible_workspaces()
            .into_iter()
            .map(|workspace| self.public_repository_summary(&workspace))
            .collect::<Vec<_>>();

        WorkspaceResponse {
            repository,
            session_default: current_workspace.is_some(),
            repositories,
            runtime: Some(self.runtime_status_summary()),
        }
    }

    async fn ensure_workspace_for_status(&self, params: &WorkspaceParams) -> Result<(), ErrorData> {
        let set_default = params.set_default.unwrap_or(true);
        let resolve_mode = params.resolve_mode.unwrap_or(WorkspaceResolveMode::GitRoot);
        let target_path = params.path.as_deref();
        let target_repository_id = params.repository_id.as_deref();

        if target_path.is_some() || target_repository_id.is_some() {
            let outcome = self.attach_workspace_target_internal(
                target_path,
                target_repository_id,
                set_default,
                resolve_mode,
                WorkspaceAttachIndexMode::Ensure,
            )?;
            let repository_id = outcome.response.repository.repository_id.clone();
            if let Some(workspace) = self.workspace_by_repository_id(&repository_id) {
                let (index_lifecycle, index_summary) = self
                    .ensure_workspace_index_for_attach(
                        &workspace,
                        true,
                        Duration::from_millis(30_000),
                    )
                    .await;
                if matches!(index_lifecycle.phase, WorkspaceIndexLifecyclePhase::Ready) {
                    match index_summary.as_ref() {
                        Some(summary) => {
                            self.maybe_spawn_workspace_precise_generation_for_paths(
                                &workspace,
                                &summary.changed_paths,
                                &summary.deleted_paths,
                            );
                        }
                        None => {
                            self.maybe_spawn_workspace_precise_generation_for_paths(
                                &workspace,
                                &[],
                                &[],
                            );
                        }
                    }
                }
            }
            if let Some(guard) = outcome.rollback_guard {
                guard.disarm();
            }
            return Ok(());
        }

        if self.current_repository_id().is_some() || !self.attached_workspaces().is_empty() {
            return Ok(());
        }

        let auto_adoptable_workspaces = self.auto_adoptable_workspaces();
        if let [workspace] = auto_adoptable_workspaces.as_slice() {
            self.adopt_workspace(workspace, true)?;
            return Ok(());
        }

        if let Ok(current_dir) = std::env::current_dir() {
            let current_dir = current_dir.display().to_string();
            let outcome = self.attach_workspace_target_internal(
                Some(&current_dir),
                None,
                true,
                WorkspaceResolveMode::GitRoot,
                WorkspaceAttachIndexMode::Ensure,
            )?;
            let repository_id = outcome.response.repository.repository_id.clone();
            if let Some(workspace) = self.workspace_by_repository_id(&repository_id) {
                let (index_lifecycle, index_summary) = self
                    .ensure_workspace_index_for_attach(
                        &workspace,
                        true,
                        Duration::from_millis(30_000),
                    )
                    .await;
                if matches!(index_lifecycle.phase, WorkspaceIndexLifecyclePhase::Ready) {
                    match index_summary.as_ref() {
                        Some(summary) => {
                            self.maybe_spawn_workspace_precise_generation_for_paths(
                                &workspace,
                                &summary.changed_paths,
                                &summary.deleted_paths,
                            );
                        }
                        None => {
                            self.maybe_spawn_workspace_precise_generation_for_paths(
                                &workspace,
                                &[],
                                &[],
                            );
                        }
                    }
                }
            }
            if let Some(guard) = outcome.rollback_guard {
                guard.disarm();
            }
        }

        Ok(())
    }
}

#[tool_router(router = tool_router)]
impl FriggMcpServer {
    #[tool(
        name = "workspace",
        description = "Show workspace status; adopt path/repository_id or auto-adopt a default when detached.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = false
        )
    )]
    pub async fn workspace(
        &self,
        params: Parameters<WorkspaceParams>,
    ) -> Result<Json<WorkspaceResponse>, ErrorData> {
        let params = params.0;
        let repository_hint = params.repository_id.clone();
        let started_at = Instant::now();
        self.ensure_workspace_for_status(&params).await?;
        let response = self.workspace_response();
        let repository_ids = response
            .repositories
            .iter()
            .map(|repository| repository.repository_id.clone())
            .collect::<Vec<_>>();
        let current_repository_id = response
            .repository
            .as_ref()
            .map(|repository| repository.repository_id.clone());
        let finalization = self.tool_execution_finalization(
            json!({
                "repository_id": current_repository_id,
                "repository_ids": repository_ids.clone(),
                "session_default": response.session_default,
                "runtime_profile": response
                    .runtime
                    .as_ref()
                    .map(|runtime| runtime.profile.as_str().to_owned()),
                "watch_active": response.runtime.as_ref().map(|runtime| runtime.watch_active),
                "active_task_count": response
                    .runtime
                    .as_ref()
                    .map(|runtime| runtime.active_tasks.len()),
                "recent_task_count": response
                    .runtime
                    .as_ref()
                    .map(|runtime| runtime.recent_tasks.len()),
            }),
            Some(FriggMcpServer::provenance_normalized_workload_metadata(
                "workspace",
                &repository_ids,
                WorkloadPrecisionMode::Exact,
                None,
                None,
                None,
            )),
        );
        let result = Ok(Json(response));
        let provenance_result = self
            .record_provenance_blocking(
                "workspace",
                repository_hint.as_deref(),
                json!({
                    "path": params.path.as_deref().map(Self::bounded_text),
                    "repository_id": params.repository_id,
                    "set_default": params.set_default,
                    "resolve_mode": params.resolve_mode,
                }),
                finalization.source_refs,
                &result,
            )
            .await;
        self.finalize_with_provenance_timed(
            "workspace",
            started_at,
            result,
            provenance_result,
            None,
        )
    }

    #[tool(
        name = "list_repositories",
        description = "List session-visible repositories with compact session, watch, and storage state.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn list_repositories(
        &self,
        params: Parameters<ListRepositoriesParams>,
    ) -> Result<Json<ListRepositoriesResponse>, ErrorData> {
        let _params = params.0;
        let execution_context = self.read_only_tool_execution_context("list_repositories", None);
        let execution_context_for_blocking = execution_context.clone();
        let server = self.clone();
        let (result, provenance_result) = self
            .run_read_only_tool_blocking(&execution_context, move || {
                let repositories = server
                    .visible_workspaces()
                    .into_iter()
                    .map(|workspace| server.public_repository_summary(&workspace))
                    .collect::<Vec<_>>();
                let repository_ids = repositories
                    .iter()
                    .map(|repo| repo.repository_id.clone())
                    .collect::<Vec<_>>();

                let response = ListRepositoriesResponse { repositories };
                let finalization = server.tool_execution_finalization(
                    json!({
                        "repository_ids": repository_ids,
                    }),
                    Some(
                        execution_context_for_blocking.normalized_workload(
                            &response
                                .repositories
                                .iter()
                                .map(|repo| repo.repository_id.clone())
                                .collect::<Vec<_>>(),
                            WorkloadPrecisionMode::Exact,
                        ),
                    ),
                );
                let result = Ok(Json(response));
                let provenance_result = server.record_provenance_with_outcome(
                    "list_repositories",
                    None,
                    json!({}),
                    finalization.source_refs,
                    Self::provenance_outcome(&result),
                );
                (result, provenance_result)
            })
            .await?;
        self.finalize_read_only_tool(&execution_context, result, provenance_result)
    }

    #[tool(
        name = "list_files",
        description = "List repository files with optional path, glob, language, class, hidden-file, and pagination filters.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn list_files(
        &self,
        params: Parameters<ListFilesParams>,
    ) -> Result<Json<ListFilesResponse>, ErrorData> {
        self.list_files_impl(params.0).await
    }

    #[tool(
        name = "workspace_attach",
        description = "Adopt a repository into this session. Use this before repo-aware tools when detached or when you want a stable default repository.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = false
        )
    )]
    pub async fn workspace_attach(
        &self,
        params: Parameters<WorkspaceAttachParams>,
    ) -> Result<Json<WorkspaceAttachResponse>, ErrorData> {
        let params = params.0;
        let set_default = params.set_default.unwrap_or(true);
        let resolve_mode = params.resolve_mode.unwrap_or(WorkspaceResolveMode::GitRoot);
        let wait_for_precise = params.wait_for_precise.unwrap_or(true);
        let index_mode = WorkspaceAttachIndexMode::Ensure;
        let wait_for_index = true;
        let index_timeout = Duration::from_millis(30_000);
        let started_at = Instant::now();
        info!(
            requested_path = params.path.as_deref().unwrap_or(""),
            requested_repository_id = params.repository_id.as_deref().unwrap_or(""),
            set_default,
            resolve_mode = ?resolve_mode,
            "workspace attach started"
        );
        let attach_path = params.path.as_deref();
        let attach_outcome = match self.attach_workspace_target_internal(
            attach_path,
            params.repository_id.as_deref(),
            set_default,
            resolve_mode,
            index_mode,
        ) {
            Ok(response) => response,
            Err(err) => {
                warn!(
                    requested_path = params.path.as_deref().unwrap_or(""),
                    requested_repository_id = params.repository_id.as_deref().unwrap_or(""),
                    set_default,
                    resolve_mode = ?resolve_mode,
                    duration_ms = started_at.elapsed().as_millis() as u64,
                    error = %err.message,
                    "workspace attach failed"
                );
                return Err(err);
            }
        };
        let rollback_guard = attach_outcome.rollback_guard;
        let mut response = attach_outcome.response;
        if matches!(index_mode, WorkspaceAttachIndexMode::Ensure) {
            let repository_id = response.repository.repository_id.clone();
            if let Some(workspace) = self.workspace_by_repository_id(&repository_id) {
                let (index_lifecycle, index_summary) = self
                    .ensure_workspace_index_for_attach(&workspace, wait_for_index, index_timeout)
                    .await;
                response.index_lifecycle = index_lifecycle;
                let precise_generation_action = if matches!(
                    response.index_lifecycle.phase,
                    WorkspaceIndexLifecyclePhase::Ready
                ) {
                    match index_summary.as_ref() {
                        Some(summary) => self.maybe_spawn_workspace_precise_generation_for_paths(
                            &workspace,
                            &summary.changed_paths,
                            &summary.deleted_paths,
                        ),
                        None => self.maybe_spawn_workspace_precise_generation_for_paths(
                            &workspace,
                            &[],
                            &[],
                        ),
                    }
                } else {
                    WorkspacePreciseGenerationAction::NotApplicable
                };
                let mut repository = self.public_repository_summary(&workspace);
                let storage = repository
                    .storage
                    .clone()
                    .unwrap_or_else(|| Self::workspace_storage_summary(&workspace));
                repository.storage = None;
                let precise = self.workspace_precise_summary_for_workspace(
                    &workspace,
                    Some(precise_generation_action),
                );
                response.repository = repository;
                response.storage = storage;
                response.precise = precise.clone();
                response.precise_lifecycle = self.workspace_precise_lifecycle_summary(
                    &workspace,
                    precise_generation_action,
                    &precise,
                    false,
                    false,
                );
            }
        }
        if wait_for_precise {
            let repository_id = response.repository.repository_id.clone();
            let completed = self
                .wait_for_repository_precise_generation(&repository_id, Duration::from_secs(30))
                .await;
            if let Some(workspace) = self.workspace_by_repository_id(&repository_id) {
                let mut repository = self.public_repository_summary(&workspace);
                let storage = repository
                    .storage
                    .clone()
                    .unwrap_or_else(|| Self::workspace_storage_summary(&workspace));
                repository.storage = None;
                let generation_action = response
                    .precise
                    .generation_action
                    .unwrap_or(WorkspacePreciseGenerationAction::NotApplicable);
                let precise = self
                    .workspace_precise_summary_for_workspace(&workspace, Some(generation_action));
                response.repository = repository;
                response.storage = storage;
                response.precise = precise.clone();
                response.precise_lifecycle = self.workspace_precise_lifecycle_summary(
                    &workspace,
                    generation_action,
                    &precise,
                    true,
                    !completed,
                );
            }
        }
        info!(
            repository_id = %response.repository.repository_id,
            root = %response.repository.root_path,
            action = ?response.action,
            resolution = ?response.resolution,
            session_default = response.session_default,
            precise_state = ?response.precise.state,
            precise_generation_action = ?response.precise.generation_action,
            precise_phase = ?response.precise_lifecycle.phase,
            index_phase = ?response.index_lifecycle.phase,
            index_mode = ?response.index_lifecycle.mode,
            duration_ms = started_at.elapsed().as_millis() as u64,
            "workspace attach completed"
        );
        let finalization = self.tool_execution_finalization(
            json!({
                "repository_id": response.repository.repository_id.clone(),
                "root_path": response.repository.root_path.clone(),
                "resolved_from": response.resolved_from.clone(),
                "resolution": response.resolution,
                "session_default": response.session_default,
                "storage": {
                    "db_path": response.storage.db_path.clone(),
                    "exists": response.storage.exists,
                    "initialized": response.storage.initialized,
                    "index_state": response.storage.index_state,
                },
                "precise_lifecycle": {
                    "phase": response.precise_lifecycle.phase,
                    "waited_for_completion": response.precise_lifecycle.waited_for_completion,
                    "generation_action": response.precise_lifecycle.generation_action,
                },
            }),
            Some(FriggMcpServer::provenance_normalized_workload_metadata(
                "workspace_attach",
                std::slice::from_ref(&response.repository.repository_id),
                WorkloadPrecisionMode::Exact,
                None,
                None,
                None,
            )),
        );
        let result = Ok(Json(response.clone()));
        let provenance_result = self
            .record_provenance_blocking(
                "workspace_attach",
                None,
                json!({
                    "path": params.path.as_deref().map(Self::bounded_text),
                    "repository_id": params.repository_id,
                    "set_default": params.set_default,
                    "resolve_mode": params.resolve_mode,
                    "wait_for_precise": params.wait_for_precise,
                }),
                finalization.source_refs,
                &result,
            )
            .await;
        if let Some(guard) = rollback_guard {
            guard.disarm();
        }
        self.finalize_with_provenance_timed(
            "workspace_attach",
            started_at,
            result,
            provenance_result,
            None,
        )
    }

    #[tool(
        name = "workspace_detach",
        description = "Remove a repository from this session and release its watch lease when applicable.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = false
        )
    )]
    pub async fn workspace_detach(
        &self,
        params: Parameters<WorkspaceDetachParams>,
    ) -> Result<Json<WorkspaceDetachResponse>, ErrorData> {
        let params = params.0;
        let started_at = Instant::now();
        let repository_id = params
            .repository_id
            .or_else(|| self.current_repository_id())
            .ok_or_else(|| Self::no_attached_workspaces_error("workspace_detach"))?;
        let detached = self.detach_workspace(&repository_id)?;
        let Some(workspace) = detached else {
            return Err(Self::resource_not_found(
                "repository_id is not adopted in this session",
                Some(json!({ "repository_id": repository_id })),
            ));
        };
        self.invalidate_repository_symbol_corpus_cache(&workspace.repository_id);
        self.invalidate_repository_response_freshness_cache(&workspace.repository_id);
        self.invalidate_repository_precise_generator_probe_cache(&workspace.repository_id);
        self.scip_invalidate_repository_precise_generation_cache(&workspace.repository_id);
        self.invalidate_repository_precise_graph_caches(&workspace.repository_id);
        let active_session_count = self
            .runtime_state
            .workspace_registry
            .read()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
            .active_session_count(&workspace.repository_id);
        if active_session_count == 0
            && !self.repository_has_active_watch_lease(&workspace.repository_id)
        {
            self.clear_pending_precise_dirty_paths(&workspace.repository_id);
        }
        let response = WorkspaceDetachResponse {
            repository_id: workspace.repository_id.clone(),
            session_default: self.current_repository_id().as_deref()
                == Some(workspace.repository_id.as_str()),
            detached: true,
        };
        let finalization = self.tool_execution_finalization(
            json!({
                "repository_id": response.repository_id,
                "detached": response.detached,
                "session_default": response.session_default,
            }),
            Some(FriggMcpServer::provenance_normalized_workload_metadata(
                "workspace_detach",
                std::slice::from_ref(&workspace.repository_id),
                WorkloadPrecisionMode::Exact,
                None,
                None,
                None,
            )),
        );
        let result = Ok(Json(response));
        let provenance_result = self
            .record_provenance_blocking(
                "workspace_detach",
                None,
                json!({ "repository_id": repository_id }),
                finalization.source_refs,
                &result,
            )
            .await;
        self.finalize_with_provenance_timed(
            "workspace_detach",
            started_at,
            result,
            provenance_result,
            None,
        )
    }

    #[tool(
        name = "workspace_prepare",
        description = "Initialize Frigg repository state and adopt it into this session; requires confirmation.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = false
        )
    )]
    pub async fn workspace_prepare(
        &self,
        meta: Meta,
        client: Peer<RoleServer>,
        params: Parameters<WorkspacePrepareParams>,
    ) -> Result<Json<WorkspacePrepareResponse>, ErrorData> {
        let params = params.0;
        Self::require_confirm("workspace_prepare", params.confirm)?;
        let set_default = params.set_default.unwrap_or(true);
        let resolve_mode = params.resolve_mode.unwrap_or(WorkspaceResolveMode::GitRoot);
        let started_at = Instant::now();
        let (workspace, resolved_from, resolution, _resolution_guard) = self
            .resolve_workspace_target(
                params.path.as_deref(),
                params.repository_id.as_deref(),
                resolve_mode,
            )?;
        info!(
            repository_id = %workspace.repository_id,
            root = %workspace.root.display(),
            set_default,
            resolve_mode = ?resolve_mode,
            requested_path = params.path.as_deref().unwrap_or(""),
            requested_repository_id = params.repository_id.as_deref().unwrap_or(""),
            "workspace prepare started"
        );
        let mut task_guard = match self.try_start_repository_runtime_task(
            &workspace,
            RuntimeTaskKind::WorkspacePrepare,
            "workspace_prepare",
            Some(format!("prepare {}", workspace.root.display())),
        ) {
            Ok(task_guard) => task_guard,
            Err(active_tasks) => {
                warn!(
                    repository_id = %workspace.repository_id,
                    root = %workspace.root.display(),
                    active_tasks = active_tasks.len(),
                    duration_ms = started_at.elapsed().as_millis() as u64,
                    "workspace prepare rejected because runtime work is active"
                );
                return Err(Self::invalid_params(
                    "repository already has active runtime work",
                    Some(json!({
                        "repository_id": workspace.repository_id,
                        "active_tasks": active_tasks,
                    })),
                ));
            }
        };
        Self::notify_progress(&meta, &client, 0.0, 4.0, "resolve target").await;

        Self::notify_progress(&meta, &client, 1.0, 4.0, "initialize storage").await;
        let prepared_storage_result = match Self::run_blocking_task("workspace_prepare", {
            let workspace = workspace.clone();
            move || -> FriggResult<WorkspaceStorageSummary> {
                let db_path = ensure_provenance_db_parent_dir(&workspace.root)?;
                let storage = Storage::new(&db_path);
                storage.initialize_with_auto_repair()?;
                Ok(FriggMcpServer::workspace_storage_summary(&workspace))
            }
        })
        .await
        {
            Ok(result) => result,
            Err(error) => {
                task_guard.finish(RuntimeTaskStatus::Failed, Some(error.message.to_string()));
                return Err(error);
            }
        };
        let prepared_storage = prepared_storage_result.map_err(|err| {
            let error_message = err.to_string();
            warn!(
                repository_id = %workspace.repository_id,
                root = %workspace.root.display(),
                duration_ms = started_at.elapsed().as_millis() as u64,
                error = %error_message,
                "workspace prepare failed during storage initialization"
            );
            task_guard.finish(RuntimeTaskStatus::Failed, Some(error_message));
            Self::map_frigg_error(err)
        })?;

        Self::notify_progress(&meta, &client, 2.0, 4.0, "validate storage").await;
        self.runtime_state
            .validated_manifest_candidate_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
            .invalidate_root(&workspace.root);
        self.invalidate_repository_symbol_corpus_cache(&workspace.repository_id);
        self.invalidate_repository_response_freshness_cache(&workspace.repository_id);
        self.invalidate_repository_precise_generator_probe_cache(&workspace.repository_id);
        self.invalidate_repository_precise_graph_caches(&workspace.repository_id);
        self.invalidate_repository_navigation_caches(&workspace.repository_id);

        Self::notify_progress(&meta, &client, 3.0, 4.0, "activate watcher lease").await;
        self.adopt_workspace(&workspace, set_default)
            .inspect_err(|error| {
                warn!(
                    repository_id = %workspace.repository_id,
                    root = %workspace.root.display(),
                    duration_ms = started_at.elapsed().as_millis() as u64,
                    error = %error.message,
                    "workspace prepare failed while adopting workspace"
                );
                task_guard.finish(RuntimeTaskStatus::Failed, Some(error.message.to_string()));
            })?;
        task_guard.finish(RuntimeTaskStatus::Succeeded, None);
        self.maybe_spawn_workspace_runtime_prewarm(&workspace);
        let _ = self.maybe_spawn_workspace_precise_generation_for_paths(&workspace, &[], &[]);

        let mut repository = self.public_repository_summary(&workspace);
        repository.storage = None;
        let response = WorkspacePrepareResponse {
            repository,
            resolved_from,
            resolution,
            session_default: self.current_repository_id().as_deref()
                == Some(workspace.repository_id.as_str()),
            storage: prepared_storage,
        };
        info!(
            repository_id = %response.repository.repository_id,
            root = %response.repository.root_path,
            session_default = response.session_default,
            resolution = ?response.resolution,
            storage_db_path = %response.storage.db_path,
            storage_index_state = ?response.storage.index_state,
            duration_ms = started_at.elapsed().as_millis() as u64,
            "workspace prepare completed"
        );
        Self::notify_progress(&meta, &client, 4.0, 4.0, "finalize").await;

        let finalization = self.tool_execution_finalization(
            json!({
                "repository_id": response.repository.repository_id.clone(),
                "resolved_from": response.resolved_from,
                "resolution": response.resolution,
                "session_default": response.session_default,
                "storage": {
                    "db_path": response.storage.db_path.clone(),
                    "exists": response.storage.exists,
                    "initialized": response.storage.initialized,
                    "index_state": response.storage.index_state,
                },
            }),
            Some(FriggMcpServer::provenance_normalized_workload_metadata(
                "workspace_prepare",
                std::slice::from_ref(&response.repository.repository_id),
                WorkloadPrecisionMode::Exact,
                None,
                None,
                None,
            )),
        );
        let result = Ok(Json(response));
        let provenance_result = self
            .record_provenance_blocking(
                "workspace_prepare",
                None,
                json!({
                    "path": params.path.as_deref().map(Self::bounded_text),
                    "repository_id": params.repository_id,
                    "set_default": params.set_default,
                    "resolve_mode": params.resolve_mode,
                    "confirm": params.confirm,
                }),
                finalization.source_refs,
                &result,
            )
            .await;
        self.finalize_with_provenance_timed(
            "workspace_prepare",
            started_at,
            result,
            provenance_result,
            None,
        )
    }

    fn capped_workspace_index_paths(paths: &[String]) -> (Vec<String>, bool) {
        let truncated = paths.len() > Self::WORKSPACE_INDEX_RESPONSE_PATH_LIMIT;
        (
            paths
                .iter()
                .take(Self::WORKSPACE_INDEX_RESPONSE_PATH_LIMIT)
                .cloned()
                .collect(),
            truncated,
        )
    }

    #[tool(
        name = "workspace_index",
        description = "Refresh Frigg index state for a repository and adopt it; requires confirmation.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = false
        )
    )]
    pub async fn workspace_index(
        &self,
        meta: Meta,
        client: Peer<RoleServer>,
        params: Parameters<WorkspaceIndexParams>,
    ) -> Result<Json<WorkspaceIndexResponse>, ErrorData> {
        let params = params.0;
        Self::require_confirm("workspace_index", params.confirm)?;
        let set_default = params.set_default.unwrap_or(true);
        let resolve_mode = params.resolve_mode.unwrap_or(WorkspaceResolveMode::GitRoot);
        let started_at = Instant::now();
        let (workspace, resolved_from, resolution, _resolution_guard) = self
            .resolve_workspace_target(
                params.path.as_deref(),
                params.repository_id.as_deref(),
                resolve_mode,
            )?;
        info!(
            repository_id = %workspace.repository_id,
            root = %workspace.root.display(),
            set_default,
            resolve_mode = ?resolve_mode,
            requested_path = params.path.as_deref().unwrap_or(""),
            requested_repository_id = params.repository_id.as_deref().unwrap_or(""),
            "workspace index started"
        );
        let mut task_guard = match self.try_start_repository_runtime_task(
            &workspace,
            RuntimeTaskKind::WorkspaceIndex,
            "workspace_index",
            Some(format!("index {}", workspace.root.display())),
        ) {
            Ok(task_guard) => task_guard,
            Err(active_tasks) => {
                warn!(
                    repository_id = %workspace.repository_id,
                    root = %workspace.root.display(),
                    active_tasks = active_tasks.len(),
                    duration_ms = started_at.elapsed().as_millis() as u64,
                    "workspace index rejected because runtime work is active"
                );
                return Err(Self::invalid_params(
                    "repository already has active runtime work",
                    Some(json!({
                        "repository_id": workspace.repository_id,
                        "active_tasks": active_tasks,
                    })),
                ));
            }
        };
        Self::notify_progress(&meta, &client, 0.0, 4.0, "resolve target").await;
        Self::notify_progress(&meta, &client, 1.0, 4.0, "index refresh").await;
        let semantic_runtime = self.config.semantic_runtime.clone();
        let index_result = match Self::run_blocking_task("workspace_index", {
            let workspace = workspace.clone();
            move || -> Result<crate::indexer::IndexSummary, String> {
                let db_path = ensure_provenance_db_parent_dir(&workspace.root)
                    .map_err(|err| err.to_string())?;
                let credentials = SemanticRuntimeCredentials::from_process_env();
                index_repository_with_runtime_config(
                    &workspace.runtime_repository_id,
                    &workspace.root,
                    &db_path,
                    IndexMode::ChangedOnly,
                    &semantic_runtime,
                    &credentials,
                )
                .map_err(|err| err.to_string())
            }
        })
        .await
        {
            Ok(result) => result,
            Err(error) => {
                task_guard.finish(RuntimeTaskStatus::Failed, Some(error.message.to_string()));
                return Err(error);
            }
        };
        self.invalidate_workspace_index_runtime_caches(&workspace, true);
        let index_summary = index_result.map_err(|err| {
            warn!(
                repository_id = %workspace.repository_id,
                root = %workspace.root.display(),
                duration_ms = started_at.elapsed().as_millis() as u64,
                error = %err,
                "workspace index failed during index refresh"
            );
            task_guard.finish(RuntimeTaskStatus::Failed, Some(err.clone()));
            Self::internal(
                err,
                Some(json!({ "repository_id": workspace.repository_id })),
            )
        })?;

        Self::notify_progress(&meta, &client, 2.0, 4.0, "invalidate caches").await;

        Self::notify_progress(&meta, &client, 3.0, 4.0, "finalize").await;
        self.adopt_workspace(&workspace, set_default)
            .inspect_err(|error| {
                warn!(
                    repository_id = %workspace.repository_id,
                    root = %workspace.root.display(),
                    duration_ms = started_at.elapsed().as_millis() as u64,
                    error = %error.message,
                    "workspace index failed while adopting workspace"
                );
                task_guard.finish(RuntimeTaskStatus::Failed, Some(error.message.to_string()));
            })?;
        let precise_generation_action = self.maybe_spawn_workspace_precise_generation_for_paths(
            &workspace,
            &index_summary.changed_paths,
            &index_summary.deleted_paths,
        );
        let precise = self
            .workspace_precise_summary_for_workspace(&workspace, Some(precise_generation_action));
        let precise_lifecycle = self.workspace_precise_lifecycle_summary(
            &workspace,
            precise_generation_action,
            &precise,
            false,
            false,
        );
        let mut repository = self.public_repository_summary(&workspace);
        let storage = repository
            .storage
            .clone()
            .unwrap_or_else(|| Self::workspace_storage_summary(&workspace));
        repository.storage = None;
        let (changed_paths, changed_paths_truncated) =
            Self::capped_workspace_index_paths(&index_summary.changed_paths);
        let (deleted_paths, deleted_paths_truncated) =
            Self::capped_workspace_index_paths(&index_summary.deleted_paths);
        let mut response = WorkspaceIndexResponse {
            repository,
            resolved_from,
            resolution,
            session_default: self.current_repository_id().as_deref()
                == Some(workspace.repository_id.as_str()),
            storage,
            snapshot_id: index_summary.snapshot_id.clone(),
            files_scanned: index_summary.files_scanned,
            files_changed: index_summary.files_changed,
            files_deleted: index_summary.files_deleted,
            diagnostics_count: index_summary.diagnostics.total_count(),
            changed_paths,
            deleted_paths,
            paths_truncated: changed_paths_truncated || deleted_paths_truncated,
            precise_lifecycle,
        };
        if params.wait_for_precise.unwrap_or(true) {
            let repository_id = response.repository.repository_id.clone();
            let completed = self
                .wait_for_repository_precise_generation(&repository_id, Duration::from_secs(30))
                .await;
            if let Some(workspace) = self.workspace_by_repository_id(&repository_id) {
                let generation_action = response.precise_lifecycle.generation_action;
                let precise = self
                    .workspace_precise_summary_for_workspace(&workspace, Some(generation_action));
                let mut repository = self.public_repository_summary(&workspace);
                let storage = repository
                    .storage
                    .clone()
                    .unwrap_or_else(|| Self::workspace_storage_summary(&workspace));
                repository.storage = None;
                response.repository = repository;
                response.storage = storage;
                response.precise_lifecycle = self.workspace_precise_lifecycle_summary(
                    &workspace,
                    generation_action,
                    &precise,
                    true,
                    !completed,
                );
            }
        }
        info!(
            repository_id = %response.repository.repository_id,
            root = %response.repository.root_path,
            resolution = ?response.resolution,
            session_default = response.session_default,
            snapshot_id = %response.snapshot_id,
            files_scanned = response.files_scanned,
            files_changed = response.files_changed,
            files_deleted = response.files_deleted,
            diagnostics_count = response.diagnostics_count,
            precise_phase = ?response.precise_lifecycle.phase,
            duration_ms = started_at.elapsed().as_millis() as u64,
            "workspace index completed"
        );
        task_guard.finish(RuntimeTaskStatus::Succeeded, None);
        Self::notify_progress(&meta, &client, 4.0, 4.0, "done").await;

        let finalization = self.tool_execution_finalization(
            json!({
                "repository_id": response.repository.repository_id.clone(),
                "snapshot_id": response.snapshot_id,
                "files_scanned": response.files_scanned,
                "files_changed": response.files_changed,
                "files_deleted": response.files_deleted,
                "diagnostics_count": response.diagnostics_count,
                "changed_paths": response.changed_paths,
                "deleted_paths": response.deleted_paths,
                "paths_truncated": response.paths_truncated,
                "session_default": response.session_default,
                "precise_lifecycle": {
                    "phase": response.precise_lifecycle.phase,
                    "waited_for_completion": response.precise_lifecycle.waited_for_completion,
                    "generation_action": response.precise_lifecycle.generation_action,
                },
            }),
            Some(FriggMcpServer::provenance_normalized_workload_metadata(
                "workspace_index",
                std::slice::from_ref(&response.repository.repository_id),
                WorkloadPrecisionMode::Exact,
                None,
                None,
                None,
            )),
        );
        let result = Ok(Json(response));
        let provenance_result = self
            .record_provenance_blocking(
                "workspace_index",
                None,
                json!({
                    "path": params.path.as_deref().map(Self::bounded_text),
                    "repository_id": params.repository_id,
                    "set_default": params.set_default,
                    "resolve_mode": params.resolve_mode,
                    "confirm": params.confirm,
                    "wait_for_precise": params.wait_for_precise,
                }),
                finalization.source_refs,
                &result,
            )
            .await;
        self.finalize_with_provenance_timed(
            "workspace_index",
            started_at,
            result,
            provenance_result,
            None,
        )
    }

    #[tool(
        name = "workspace_current",
        description = "Inspect the session default, adopted repositories, and runtime tasks without computing detailed index diagnostics.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn workspace_current(
        &self,
        params: Parameters<WorkspaceCurrentParams>,
    ) -> Result<Json<WorkspaceCurrentResponse>, ErrorData> {
        let _params = params.0;
        let execution_context = self.read_only_tool_execution_context("workspace_current", None);
        let current_workspace = self.current_workspace();
        let current_repository = current_workspace
            .as_ref()
            .map(|workspace| self.public_repository_summary(workspace));
        let repositories = self
            .attached_workspaces()
            .into_iter()
            .map(|workspace| self.public_repository_summary(&workspace))
            .collect::<Vec<_>>();
        let runtime = self.runtime_status_summary();
        let response = WorkspaceCurrentResponse {
            repository: current_repository,
            session_default: current_workspace.is_some(),
            repositories,
            precise: None,
            precise_ingest: None,
            runtime: Some(runtime),
        };
        let repository_ids = response
            .repositories
            .iter()
            .map(|repository| repository.repository_id.clone())
            .collect::<Vec<_>>();
        let source_refs = json!({
            "repository_id": response
                .repository
                .as_ref()
                .map(|repository| repository.repository_id.clone()),
            "repository_ids": repository_ids,
            "runtime_profile": response
                .runtime
                .as_ref()
                .map(|runtime| runtime.profile.as_str().to_owned()),
            "watch_active": response.runtime.as_ref().map(|runtime| runtime.watch_active),
            "active_task_count": response
                .runtime
                .as_ref()
                .map(|runtime| runtime.active_tasks.len()),
            "recent_task_count": response
                .runtime
                .as_ref()
                .map(|runtime| runtime.recent_tasks.len()),
        });
        let normalized_workload =
            execution_context.normalized_workload(&repository_ids, WorkloadPrecisionMode::Exact);
        let finalization = self.tool_execution_finalization(source_refs, Some(normalized_workload));
        let result = Ok(Json(response));
        let provenance_result = self
            .record_provenance_blocking_with_metadata(
                "workspace_current",
                None,
                json!({}),
                finalization.source_refs,
                finalization.normalized_workload,
                &result,
            )
            .await;
        self.finalize_read_only_tool(&execution_context, result, provenance_result)
    }

    #[tool(
        name = "read_file",
        description = "Read a bounded repository-relative source file or line window.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn read_file(
        &self,
        params: Parameters<ReadFileParams>,
    ) -> Result<CallToolResult, ErrorData> {
        let params = params.0;
        let response = self.read_file_impl(params.clone()).await?;
        self.present_read_file_result(&params, response)
    }

    #[tool(
        name = "read_match",
        description = "Read a bounded source window for a prior result_handle and match_id.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn read_match(
        &self,
        params: Parameters<ReadMatchParams>,
    ) -> Result<CallToolResult, ErrorData> {
        let params = params.0;
        let response = self.read_match_impl(params.clone()).await?;
        self.present_read_match_result(&params, response)
    }

    #[tool(
        name = "explore",
        description = "Search or read a bounded window inside one repository file.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn explore(
        &self,
        params: Parameters<ExploreParams>,
    ) -> Result<CallToolResult, ErrorData> {
        let params = params.0;
        let response = self.explore_impl(params.clone()).await?;
        self.present_explore_result(&params, response)
    }

    #[tool(
        name = "search_text",
        description = "Search repository source text with literal or regex matching and optional path filters.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn search_text(
        &self,
        params: Parameters<SearchTextParams>,
    ) -> Result<Json<SearchTextResponse>, ErrorData> {
        self.search_text_impl(params.0).await
    }

    #[tool(
        name = "search_hybrid",
        description = "Find discovery pivots for broad code questions without a known string, symbol, or path.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn search_hybrid(
        &self,
        params: Parameters<SearchHybridParams>,
    ) -> Result<Json<SearchHybridResponse>, ErrorData> {
        self.search_hybrid_impl(params.0).await
    }

    #[tool(
        name = "search_symbol",
        description = "Find indexed symbols by API, type, function, class, method, or identifier name.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn search_symbol(
        &self,
        params: Parameters<SearchSymbolParams>,
    ) -> Result<Json<SearchSymbolResponse>, ErrorData> {
        self.search_symbol_impl(params.0).await
    }

    #[tool(
        name = "find_references",
        description = "Find definition and usage rows for a symbol or cursor location.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn find_references(
        &self,
        params: Parameters<FindReferencesParams>,
    ) -> Result<Json<FindReferencesResponse>, ErrorData> {
        self.find_references_impl(params.0).await
    }

    #[tool(
        name = "go_to_definition",
        description = "Find likely definitions for a symbol or cursor location.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn go_to_definition(
        &self,
        params: Parameters<GoToDefinitionParams>,
    ) -> Result<Json<GoToDefinitionResponse>, ErrorData> {
        self.go_to_definition_impl(params.0).await
    }

    #[tool(
        name = "find_declarations",
        description = "Find declaration anchors for a symbol or cursor location.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn find_declarations(
        &self,
        params: Parameters<FindDeclarationsParams>,
    ) -> Result<Json<FindDeclarationsResponse>, ErrorData> {
        self.find_declarations_impl(params.0).await
    }

    #[tool(
        name = "find_implementations",
        description = "Find implementations for a symbol or cursor location.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn find_implementations(
        &self,
        params: Parameters<FindImplementationsParams>,
    ) -> Result<Json<FindImplementationsResponse>, ErrorData> {
        self.find_implementations_impl(params.0).await
    }

    #[tool(
        name = "incoming_calls",
        description = "Find callers for a callable symbol or cursor location.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn incoming_calls(
        &self,
        params: Parameters<IncomingCallsParams>,
    ) -> Result<Json<IncomingCallsResponse>, ErrorData> {
        self.incoming_calls_impl(params.0).await
    }

    #[tool(
        name = "outgoing_calls",
        description = "Find callees for a callable symbol or cursor location.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn outgoing_calls(
        &self,
        params: Parameters<OutgoingCallsParams>,
    ) -> Result<Json<OutgoingCallsResponse>, ErrorData> {
        self.outgoing_calls_impl(params.0).await
    }

    #[tool(
        name = "document_symbols",
        description = "Return a symbol outline for one supported source file.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn document_symbols(
        &self,
        params: Parameters<DocumentSymbolsParams>,
    ) -> Result<Json<DocumentSymbolsResponse>, ErrorData> {
        self.document_symbols_impl(params.0).await
    }

    #[tool(
        name = "inspect_syntax_tree",
        description = "Return AST focus, ancestor, and child nodes around a source location.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn inspect_syntax_tree(
        &self,
        params: Parameters<InspectSyntaxTreeParams>,
    ) -> Result<Json<InspectSyntaxTreeResponse>, ErrorData> {
        self.inspect_syntax_tree_impl(params.0).await
    }

    #[tool(
        name = "search_structural",
        description = "Run a bounded Tree-sitter query and return structural matches.",
        annotations(
            read_only_hint = true,
            destructive_hint = false,
            idempotent_hint = true
        )
    )]
    pub async fn search_structural(
        &self,
        params: Parameters<SearchStructuralParams>,
    ) -> Result<Json<SearchStructuralResponse>, ErrorData> {
        self.search_structural_impl(params.0).await
    }

    #[cfg_attr(
        feature = "playbook",
        tool(
            name = "playbook_run",
            description = "Run a trace-oriented playbook and return the resulting trace artifact.",
            annotations(
                read_only_hint = true,
                destructive_hint = false,
                idempotent_hint = true
            )
        )
    )]
    #[cfg(feature = "playbook")]
    pub async fn playbook_run(
        &self,
        params: Parameters<PlaybookRunParams>,
    ) -> Result<Json<PlaybookRunResponse>, ErrorData> {
        self.playbook_run_impl(params.0.into()).await
    }

    #[cfg_attr(
        feature = "playbook",
        tool(
            name = "playbook_replay",
            description = "Replay a playbook against an expected trace artifact and report whether it still matches.",
            annotations(
                read_only_hint = true,
                destructive_hint = false,
                idempotent_hint = true
            )
        )
    )]
    #[cfg(feature = "playbook")]
    pub async fn playbook_replay(
        &self,
        params: Parameters<PlaybookReplayParams>,
    ) -> Result<Json<PlaybookReplayResponse>, ErrorData> {
        self.playbook_replay_impl(params.0).await
    }

    #[cfg_attr(
        feature = "playbook",
        tool(
            name = "playbook_compose_citations",
            description = "Compose citation payloads from an existing playbook trace artifact.",
            annotations(
                read_only_hint = true,
                destructive_hint = false,
                idempotent_hint = true
            )
        )
    )]
    #[cfg(feature = "playbook")]
    pub async fn playbook_compose_citations(
        &self,
        params: Parameters<PlaybookComposeCitationsParams>,
    ) -> Result<Json<PlaybookComposeCitationsResponse>, ErrorData> {
        self.playbook_compose_citations_impl(params.0).await
    }
}

#[tool_handler(router = self.tool_router)]
impl ServerHandler for FriggMcpServer {
    fn get_info(&self) -> ServerInfo {
        let tool_surface_profile = self.tool_surface_profile.as_str();
        let runtime_profile = self.runtime_state.runtime_profile.as_str();
        let tool_surface_note = if self.tool_surface_profile == ToolSurfaceProfile::Extended {
            format!(
                "The extended tool surface is enabled by default. Set `{TOOL_SURFACE_PROFILE_ENV}=core` to restrict the runtime to the stable core subset."
            )
        } else if cfg!(feature = "playbook") {
            format!(
                "The runtime is pinned to the restricted core tool surface. Set `{TOOL_SURFACE_PROFILE_ENV}=extended` to expose `explore` and compiled-in playbook tools."
            )
        } else {
            format!(
                "The runtime is pinned to the restricted core tool surface. Set `{TOOL_SURFACE_PROFILE_ENV}=extended` to expose `explore`."
            )
        };
        let playbook_guidance = if cfg!(feature = "playbook") {
            " Use playbook tools only for explicit trace workflows when those tools are present in the active profile."
        } else {
            ""
        };
        ServerInfo::new(
            ServerCapabilities::builder()
                .enable_prompts()
                .enable_resources()
                .enable_tools()
                .build(),
        )
            .with_server_info(
                Implementation::new("frigg", env!("CARGO_PKG_VERSION"))
                    .with_title("Frigg MCP")
                    .with_description("Local-first code search + navigation MCP server"),
            )
            .with_instructions(agent_directive::mcp_instructions(&format!(
                "Runtime profile: `{runtime_profile}`. Tool surface: `{tool_surface_profile}`. Omit repository_id in normal single-repo work; call workspace for compact status or to adopt a target path/repository. Repo-aware tools auto-adopt sensible defaults when possible. Detailed routing lives in `{SUPPORT_MATRIX_RESOURCE_URI}`, `{TOOL_SURFACE_RESOURCE_URI}`, `{SHELL_REPLACEMENT_MAP_RESOURCE_URI}`, `{SHELL_GUIDANCE_RESOURCE_URI}`, and prompt `{ROUTING_GUIDE_PROMPT_NAME}`.{playbook_guidance} {tool_surface_note}"
            )))
    }

    async fn on_initialized(&self, _context: rmcp::service::NotificationContext<rmcp::RoleServer>) {
    }

    async fn on_roots_list_changed(
        &self,
        _context: rmcp::service::NotificationContext<rmcp::RoleServer>,
    ) {
    }

    async fn list_resources(
        &self,
        _request: Option<rmcp::model::PaginatedRequestParams>,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<rmcp::model::ListResourcesResult, ErrorData> {
        Ok(rmcp::model::ListResourcesResult::with_all_items(
            policy_resources(),
        ))
    }

    async fn read_resource(
        &self,
        request: rmcp::model::ReadResourceRequestParams,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<rmcp::model::ReadResourceResult, ErrorData> {
        read_policy_resource(&request.uri, self.tool_surface_profile).ok_or_else(|| {
            Self::resource_not_found(
                format!("unknown resource `{}`", request.uri),
                Some(json!({ "uri": request.uri })),
            )
        })
    }

    async fn list_prompts(
        &self,
        _request: Option<rmcp::model::PaginatedRequestParams>,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<rmcp::model::ListPromptsResult, ErrorData> {
        Ok(rmcp::model::ListPromptsResult::with_all_items(
            guidance_prompts(),
        ))
    }

    async fn get_prompt(
        &self,
        request: rmcp::model::GetPromptRequestParams,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<rmcp::model::GetPromptResult, ErrorData> {
        read_guidance_prompt(
            &request.name,
            request.arguments.as_ref(),
            self.tool_surface_profile,
        )
        .ok_or_else(|| {
            Self::invalid_params(
                format!("unknown prompt `{}`", request.name),
                Some(json!({ "name": request.name })),
            )
        })
    }
}

#[cfg(test)]
mod runtime_gate_tests;
