diff --git a/apps/elf-eval/tests/real_world_job_benchmark/dreaming_reports.rs b/apps/elf-eval/tests/real_world_job_benchmark/dreaming_reports.rs index 66044c41..f16482d4 100644 --- a/apps/elf-eval/tests/real_world_job_benchmark/dreaming_reports.rs +++ b/apps/elf-eval/tests/real_world_job_benchmark/dreaming_reports.rs @@ -664,7 +664,7 @@ fn dreaming_review_queue_report_wires_reviewable_policy_contract() -> color_eyre )?; let service_lib = fs::read_to_string(workspace.join("packages/elf-service/src/lib.rs"))?; let routes = read_rust_module_sources(&workspace.join("apps/elf-api/src"), "routes")?; - let mcp = fs::read_to_string(workspace.join("apps/elf-mcp/src/server.rs"))?; + let mcp = fs::read_to_string(workspace.join("apps/elf-mcp/src/app/server.rs"))?; let consolidation_spec = fs::read_to_string(workspace.join("docs/spec/system_consolidation_proposals_v1.md"))?; let service_spec = diff --git a/apps/elf-eval/tests/real_world_job_benchmark/recall_debug_reports.rs b/apps/elf-eval/tests/real_world_job_benchmark/recall_debug_reports.rs index 129fa87c..5bdd813a 100644 --- a/apps/elf-eval/tests/real_world_job_benchmark/recall_debug_reports.rs +++ b/apps/elf-eval/tests/real_world_job_benchmark/recall_debug_reports.rs @@ -101,8 +101,11 @@ fn recall_debug_panel_report_wires_cross_layer_debug_contract() -> Result<()> { let service_lib = fs::read_to_string(workspace.join("packages/elf-service/src/lib.rs"))?; let routes = rust_module_sources(&workspace, "apps/elf-api/src/routes.rs", "apps/elf-api/src/routes")?; - let mcp = - rust_module_sources(&workspace, "apps/elf-mcp/src/server.rs", "apps/elf-mcp/src/server")?; + let mcp = rust_module_sources( + &workspace, + "apps/elf-mcp/src/app/server.rs", + "apps/elf-mcp/src/app/server", + )?; let recall_spec = fs::read_to_string(workspace.join("docs/spec/system_recall_debug_panel_v1.md"))?; let service_spec = diff --git a/apps/elf-eval/tests/real_world_job_benchmark/trace_replay_reports.rs b/apps/elf-eval/tests/real_world_job_benchmark/trace_replay_reports.rs index 358032b7..c3ace188 100644 --- a/apps/elf-eval/tests/real_world_job_benchmark/trace_replay_reports.rs +++ b/apps/elf-eval/tests/real_world_job_benchmark/trace_replay_reports.rs @@ -48,7 +48,7 @@ fn graph_topic_map_report_wires_source_backed_graph_lite_readback() -> Result<() let api_routes = fs::read_to_string(support::workspace_root()?.join("apps/elf-api/src/routes.rs"))?; let mcp_server = - fs::read_to_string(support::workspace_root()?.join("apps/elf-mcp/src/server.rs"))?; + fs::read_to_string(support::workspace_root()?.join("apps/elf-mcp/src/app/server.rs"))?; let graph_spec = fs::read_to_string( support::workspace_root()?.join("docs/spec/system_graph_memory_postgres_v1.md"), )?; diff --git a/apps/elf-mcp/src/app.rs b/apps/elf-mcp/src/app.rs index 3dc073f0..62caab5c 100644 --- a/apps/elf-mcp/src/app.rs +++ b/apps/elf-mcp/src/app.rs @@ -1,4 +1,4 @@ -#[path = "server.rs"] mod server; +mod server; use std::{net::SocketAddr, path::PathBuf}; diff --git a/apps/elf-mcp/src/server.rs b/apps/elf-mcp/src/app/server.rs similarity index 55% rename from apps/elf-mcp/src/server.rs rename to apps/elf-mcp/src/app/server.rs index 1b655fb9..1d060ded 100644 --- a/apps/elf-mcp/src/server.rs +++ b/apps/elf-mcp/src/app/server.rs @@ -1,32 +1,28 @@ -#[path = "server/runtime.rs"] mod runtime; -#[path = "server/schemas.rs"] mod schemas; -#[path = "server/state.rs"] mod state; -#[path = "server/support.rs"] mod support; +mod runtime; +mod schemas; +mod state; +mod support; +mod tools; pub use runtime::serve_mcp; use color_eyre::Result; use rmcp::{ ErrorData, + handler::server::router::tool::ToolRouter, model::{CallToolResult, JsonObject}, }; use schemas::{ - admin_ingestion_profile_default_get_schema, admin_ingestion_profile_default_set_schema, - admin_ingestion_profile_get_schema, admin_ingestion_profile_versions_list_schema, - admin_ingestion_profiles_create_schema, admin_ingestion_profiles_list_schema, - admin_memory_history_get_schema, admin_note_provenance_get_schema, - admin_trace_bundle_get_schema, admin_trace_get_schema, admin_trace_item_get_schema, - admin_traces_recent_list_schema, admin_trajectory_get_schema, core_blocks_get_schema, - docs_excerpts_get_schema, docs_get_schema, docs_put_schema, docs_search_l0_schema, - dreaming_review_queue_schema, entity_memory_get_schema, events_ingest_schema, - graph_query_schema, graph_report_schema, notes_get_schema, notes_ingest_schema, - notes_list_schema, notes_patch_schema, notes_publish_schema, notes_unpublish_schema, - recall_debug_panel_schema, searches_create_schema, searches_get_schema, searches_notes_schema, - searches_timeline_schema, space_grant_revoke_schema, space_grant_upsert_schema, - space_grants_list_schema, work_journal_entry_create_schema, work_journal_entry_get_schema, - work_journal_session_readback_schema, + core_blocks_get_schema, dreaming_review_queue_schema, entity_memory_get_schema, + events_ingest_schema, graph_query_schema, graph_report_schema, notes_get_schema, + notes_ingest_schema, notes_list_schema, notes_patch_schema, notes_publish_schema, + notes_unpublish_schema, recall_debug_panel_schema, searches_create_schema, searches_get_schema, + searches_notes_schema, searches_timeline_schema, space_grant_revoke_schema, + space_grant_upsert_schema, space_grants_list_schema, work_journal_entry_create_schema, + work_journal_entry_get_schema, work_journal_session_readback_schema, }; +#[cfg(test)] use schemas::{docs_excerpts_get_schema, docs_put_schema, docs_search_l0_schema}; use state::{ElfContextHeaders, ElfMcp, HttpMethod}; #[cfg(test)] use support::is_authorized; use support::{ @@ -40,7 +36,7 @@ const HEADER_READ_PROFILE: &str = "X-ELF-Read-Profile"; const HEADER_REQUEST_ID: &str = "X-ELF-Request-Id"; const HEADER_AUTHORIZATION: &str = "Authorization"; -#[rmcp::tool_router] +#[rmcp::tool_router(router = core_tool_router, vis = "pub(in crate::app::server)")] impl ElfMcp { #[rmcp::tool( name = "elf_notes_ingest", @@ -78,63 +74,6 @@ impl ElfMcp { self.forward(HttpMethod::Post, "/v2/events/ingest", params, None).await } - #[rmcp::tool( - name = "elf_docs_put", - description = "Store a document (evidence source) in ELF Doc Extension v1.", - input_schema = docs_put_schema() - )] - async fn elf_docs_put(&self, params: JsonObject) -> Result { - self.forward(HttpMethod::Post, "/v2/docs", params, None).await - } - - #[rmcp::tool( - name = "elf_docs_get", - description = "Fetch a single document's metadata by doc_id.", - input_schema = docs_get_schema() - )] - async fn elf_docs_get(&self, mut params: JsonObject) -> Result { - let doc_id = support::take_required_string(&mut params, "doc_id")?; - let path = format!("/v2/docs/{doc_id}"); - - self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await - } - - #[rmcp::tool( - name = "elf_docs_delete", - description = "Delete a Source Library document by doc_id and enqueue derived doc-vector removal.", - input_schema = docs_get_schema() - )] - async fn elf_docs_delete(&self, mut params: JsonObject) -> Result { - let doc_id = support::take_required_string(&mut params, "doc_id")?; - let path = format!("/v2/docs/{doc_id}"); - - self.forward(HttpMethod::Delete, &path, JsonObject::new(), None).await - } - - #[rmcp::tool( - name = "elf_docs_search_l0", - description = "Run a minimal Doc search (L0): chunk-level results with short snippets.", - input_schema = docs_search_l0_schema() - )] - async fn elf_docs_search_l0( - &self, - mut params: JsonObject, - ) -> Result { - // read_profile is part of the MCP server configuration and is not client-controlled. - let _ = support::take_optional_string(&mut params, "read_profile")?; - - self.forward(HttpMethod::Post, "/v2/docs/search/l0", params, None).await - } - - #[rmcp::tool( - name = "elf_docs_excerpts_get", - description = "Hydrate a verifiable excerpt (L1 or L2) from a stored document.", - input_schema = docs_excerpts_get_schema() - )] - async fn elf_docs_excerpts_get(&self, params: JsonObject) -> Result { - self.forward(HttpMethod::Post, "/v2/docs/excerpts", params, None).await - } - #[rmcp::tool( name = "elf_work_journal_entry_create", description = "Capture one source-adjacent Work Journal entry with source refs, redaction, next-step, rejected-option, and promotion-boundary metadata. Journal content is not authoritative memory.", @@ -406,201 +345,12 @@ impl ElfMcp { self.forward(HttpMethod::Post, &path, params, None).await } +} - #[rmcp::tool( - name = "elf_admin_traces_recent_list", - description = "List recent traces by tenant/project with optional cursor and filters.", - input_schema = admin_traces_recent_list_schema() - )] - async fn elf_admin_traces_recent_list( - &self, - params: JsonObject, - ) -> Result { - self.forward(HttpMethod::Get, "/v2/admin/traces/recent", params, None).await - } - - #[rmcp::tool( - name = "elf_admin_trace_get", - description = "Fetch trace metadata, items, and optional trajectory summary by trace_id.", - input_schema = admin_trace_get_schema() - )] - async fn elf_admin_trace_get( - &self, - mut params: JsonObject, - ) -> Result { - let trace_id = support::take_required_string(&mut params, "trace_id")?; - let path = format!("/v2/admin/traces/{trace_id}"); - - self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await - } - - #[rmcp::tool( - name = "elf_admin_trajectory_get", - description = "Fetch trace trajectory and stage payload by trace_id.", - input_schema = admin_trajectory_get_schema() - )] - async fn elf_admin_trajectory_get( - &self, - mut params: JsonObject, - ) -> Result { - let trace_id = support::take_required_string(&mut params, "trace_id")?; - let path = format!("/v2/admin/trajectories/{trace_id}"); - - self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await - } - - #[rmcp::tool( - name = "elf_admin_trace_item_get", - description = "Fetch a trace item explain payload by item_id.", - input_schema = admin_trace_item_get_schema() - )] - async fn elf_admin_trace_item_get( - &self, - mut params: JsonObject, - ) -> Result { - let item_id = support::take_required_string(&mut params, "item_id")?; - let path = format!("/v2/admin/trace-items/{item_id}"); - - self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await - } - - #[rmcp::tool( - name = "elf_admin_note_provenance_get", - description = "Fetch provenance bundle and related history for one note.", - input_schema = admin_note_provenance_get_schema() - )] - async fn elf_admin_note_provenance_get( - &self, - mut params: JsonObject, - ) -> Result { - let note_id = support::take_required_string(&mut params, "note_id")?; - let path = format!("/v2/admin/notes/{note_id}/provenance"); - - self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await - } - - #[rmcp::tool( - name = "elf_admin_memory_history_get", - description = "Fetch chronological memory history for one note.", - input_schema = admin_memory_history_get_schema() - )] - async fn elf_admin_memory_history_get( - &self, - mut params: JsonObject, - ) -> Result { - let note_id = support::take_required_string(&mut params, "note_id")?; - let path = format!("/v2/admin/notes/{note_id}/history"); - - self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await - } - - #[rmcp::tool( - name = "elf_admin_trace_bundle_get", - description = "Fetch trace bundle for replay and diagnostics by trace_id.", - input_schema = admin_trace_bundle_get_schema() - )] - async fn elf_admin_trace_bundle_get( - &self, - mut params: JsonObject, - ) -> Result { - let trace_id = support::take_required_string(&mut params, "trace_id")?; - let path = format!("/v2/admin/traces/{trace_id}/bundle"); - - self.forward(HttpMethod::Get, &path, params, None).await - } - - #[rmcp::tool( - name = "elf_admin_events_ingestion_profiles_list", - description = "List latest ingestion profiles for add_event.", - input_schema = admin_ingestion_profiles_list_schema() - )] - async fn elf_admin_events_ingestion_profiles_list( - &self, - _params: JsonObject, - ) -> Result { - self.forward( - HttpMethod::Get, - "/v2/admin/events/ingestion-profiles", - JsonObject::new(), - None, - ) - .await - } - - #[rmcp::tool( - name = "elf_admin_events_ingestion_profiles_create", - description = "Create a new ingestion profile version for add_event.", - input_schema = admin_ingestion_profiles_create_schema() - )] - async fn elf_admin_events_ingestion_profiles_create( - &self, - params: JsonObject, - ) -> Result { - self.forward(HttpMethod::Post, "/v2/admin/events/ingestion-profiles", params, None).await - } - - #[rmcp::tool( - name = "elf_admin_events_ingestion_profile_get", - description = "Get a single ingestion profile by id/version for add_event.", - input_schema = admin_ingestion_profile_get_schema() - )] - async fn elf_admin_events_ingestion_profile_get( - &self, - mut params: JsonObject, - ) -> Result { - let profile_id = support::take_required_string(&mut params, "profile_id")?; - let path = format!("/v2/admin/events/ingestion-profiles/{profile_id}"); - - self.forward(HttpMethod::Get, &path, params, None).await - } - - #[rmcp::tool( - name = "elf_admin_events_ingestion_profile_versions_list", - description = "List all versions of one ingestion profile for add_event.", - input_schema = admin_ingestion_profile_versions_list_schema() - )] - async fn elf_admin_events_ingestion_profile_versions_list( - &self, - mut params: JsonObject, - ) -> Result { - let profile_id = support::take_required_string(&mut params, "profile_id")?; - let path = format!("/v2/admin/events/ingestion-profiles/{profile_id}/versions"); - - self.forward(HttpMethod::Get, &path, params, None).await - } - - #[rmcp::tool( - name = "elf_admin_events_ingestion_profile_default_get", - description = "Get the active default ingestion profile for add_event.", - input_schema = admin_ingestion_profile_default_get_schema() - )] - async fn elf_admin_events_ingestion_profile_default_get( - &self, - _params: JsonObject, - ) -> Result { - self.forward( - HttpMethod::Get, - "/v2/admin/events/ingestion-profiles/default", - JsonObject::new(), - None, - ) - .await - } - - #[rmcp::tool( - name = "elf_admin_events_ingestion_profile_default_set", - description = "Set the default ingestion profile for add_event.", - input_schema = admin_ingestion_profile_default_set_schema() - )] - async fn elf_admin_events_ingestion_profile_default_set( - &self, - params: JsonObject, - ) -> Result { - self.forward(HttpMethod::Put, "/v2/admin/events/ingestion-profiles/default", params, None) - .await +impl ElfMcp { + pub(in crate::app::server) fn tool_router() -> ToolRouter { + Self::core_tool_router() + Self::docs_tool_router() + Self::admin_tool_router() } } -#[cfg(test)] -#[path = "server/tests.rs"] -mod tests; +#[cfg(test)] mod tests; diff --git a/apps/elf-mcp/src/server/runtime.rs b/apps/elf-mcp/src/app/server/runtime.rs similarity index 100% rename from apps/elf-mcp/src/server/runtime.rs rename to apps/elf-mcp/src/app/server/runtime.rs diff --git a/apps/elf-mcp/src/server/schemas.rs b/apps/elf-mcp/src/app/server/schemas.rs similarity index 77% rename from apps/elf-mcp/src/server/schemas.rs rename to apps/elf-mcp/src/app/server/schemas.rs index db1ca4a0..5e8cbc3b 100644 --- a/apps/elf-mcp/src/server/schemas.rs +++ b/apps/elf-mcp/src/app/server/schemas.rs @@ -1,12 +1,12 @@ -#[path = "schemas/admin.rs"] mod admin; -#[path = "schemas/docs.rs"] mod docs; -#[path = "schemas/events.rs"] mod events; -#[path = "schemas/graph.rs"] mod graph; -#[path = "schemas/memory.rs"] mod memory; -#[path = "schemas/notes.rs"] mod notes; -#[path = "schemas/search.rs"] mod search; -#[path = "schemas/sharing.rs"] mod sharing; -#[path = "schemas/work_journal.rs"] mod work_journal; +mod admin; +mod docs; +mod events; +mod graph; +mod memory; +mod notes; +mod search; +mod sharing; +mod work_journal; pub(in crate::app::server) use self::{ admin::{ diff --git a/apps/elf-mcp/src/server/schemas/admin.rs b/apps/elf-mcp/src/app/server/schemas/admin.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/admin.rs rename to apps/elf-mcp/src/app/server/schemas/admin.rs diff --git a/apps/elf-mcp/src/server/schemas/docs.rs b/apps/elf-mcp/src/app/server/schemas/docs.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/docs.rs rename to apps/elf-mcp/src/app/server/schemas/docs.rs diff --git a/apps/elf-mcp/src/server/schemas/events.rs b/apps/elf-mcp/src/app/server/schemas/events.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/events.rs rename to apps/elf-mcp/src/app/server/schemas/events.rs diff --git a/apps/elf-mcp/src/server/schemas/graph.rs b/apps/elf-mcp/src/app/server/schemas/graph.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/graph.rs rename to apps/elf-mcp/src/app/server/schemas/graph.rs diff --git a/apps/elf-mcp/src/server/schemas/memory.rs b/apps/elf-mcp/src/app/server/schemas/memory.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/memory.rs rename to apps/elf-mcp/src/app/server/schemas/memory.rs diff --git a/apps/elf-mcp/src/server/schemas/notes.rs b/apps/elf-mcp/src/app/server/schemas/notes.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/notes.rs rename to apps/elf-mcp/src/app/server/schemas/notes.rs diff --git a/apps/elf-mcp/src/server/schemas/search.rs b/apps/elf-mcp/src/app/server/schemas/search.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/search.rs rename to apps/elf-mcp/src/app/server/schemas/search.rs diff --git a/apps/elf-mcp/src/server/schemas/sharing.rs b/apps/elf-mcp/src/app/server/schemas/sharing.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/sharing.rs rename to apps/elf-mcp/src/app/server/schemas/sharing.rs diff --git a/apps/elf-mcp/src/server/schemas/work_journal.rs b/apps/elf-mcp/src/app/server/schemas/work_journal.rs similarity index 100% rename from apps/elf-mcp/src/server/schemas/work_journal.rs rename to apps/elf-mcp/src/app/server/schemas/work_journal.rs diff --git a/apps/elf-mcp/src/server/state.rs b/apps/elf-mcp/src/app/server/state.rs similarity index 100% rename from apps/elf-mcp/src/server/state.rs rename to apps/elf-mcp/src/app/server/state.rs diff --git a/apps/elf-mcp/src/server/support.rs b/apps/elf-mcp/src/app/server/support.rs similarity index 100% rename from apps/elf-mcp/src/server/support.rs rename to apps/elf-mcp/src/app/server/support.rs diff --git a/apps/elf-mcp/src/server/tests.rs b/apps/elf-mcp/src/app/server/tests.rs similarity index 100% rename from apps/elf-mcp/src/server/tests.rs rename to apps/elf-mcp/src/app/server/tests.rs diff --git a/apps/elf-mcp/src/app/server/tools.rs b/apps/elf-mcp/src/app/server/tools.rs new file mode 100644 index 00000000..7c61555f --- /dev/null +++ b/apps/elf-mcp/src/app/server/tools.rs @@ -0,0 +1,2 @@ +mod admin; +mod docs; diff --git a/apps/elf-mcp/src/app/server/tools/admin.rs b/apps/elf-mcp/src/app/server/tools/admin.rs new file mode 100644 index 00000000..b1d0b8b6 --- /dev/null +++ b/apps/elf-mcp/src/app/server/tools/admin.rs @@ -0,0 +1,214 @@ +use color_eyre::Result; +use rmcp::{ + ErrorData, + model::{CallToolResult, JsonObject}, +}; + +use crate::app::server::{ + ElfMcp, HttpMethod, + schemas::{ + admin_ingestion_profile_default_get_schema, admin_ingestion_profile_default_set_schema, + admin_ingestion_profile_get_schema, admin_ingestion_profile_versions_list_schema, + admin_ingestion_profiles_create_schema, admin_ingestion_profiles_list_schema, + admin_memory_history_get_schema, admin_note_provenance_get_schema, + admin_trace_bundle_get_schema, admin_trace_get_schema, admin_trace_item_get_schema, + admin_traces_recent_list_schema, admin_trajectory_get_schema, + }, + support, +}; + +#[rmcp::tool_router(router = admin_tool_router, vis = "pub(in crate::app::server)")] +impl ElfMcp { + #[rmcp::tool( + name = "elf_admin_traces_recent_list", + description = "List recent traces by tenant/project with optional cursor and filters.", + input_schema = admin_traces_recent_list_schema() + )] + async fn elf_admin_traces_recent_list( + &self, + params: JsonObject, + ) -> Result { + self.forward(HttpMethod::Get, "/v2/admin/traces/recent", params, None).await + } + + #[rmcp::tool( + name = "elf_admin_trace_get", + description = "Fetch trace metadata, items, and optional trajectory summary by trace_id.", + input_schema = admin_trace_get_schema() + )] + async fn elf_admin_trace_get( + &self, + mut params: JsonObject, + ) -> Result { + let trace_id = support::take_required_string(&mut params, "trace_id")?; + let path = format!("/v2/admin/traces/{trace_id}"); + + self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await + } + + #[rmcp::tool( + name = "elf_admin_trajectory_get", + description = "Fetch trace trajectory and stage payload by trace_id.", + input_schema = admin_trajectory_get_schema() + )] + async fn elf_admin_trajectory_get( + &self, + mut params: JsonObject, + ) -> Result { + let trace_id = support::take_required_string(&mut params, "trace_id")?; + let path = format!("/v2/admin/trajectories/{trace_id}"); + + self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await + } + + #[rmcp::tool( + name = "elf_admin_trace_item_get", + description = "Fetch a trace item explain payload by item_id.", + input_schema = admin_trace_item_get_schema() + )] + async fn elf_admin_trace_item_get( + &self, + mut params: JsonObject, + ) -> Result { + let item_id = support::take_required_string(&mut params, "item_id")?; + let path = format!("/v2/admin/trace-items/{item_id}"); + + self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await + } + + #[rmcp::tool( + name = "elf_admin_note_provenance_get", + description = "Fetch provenance bundle and related history for one note.", + input_schema = admin_note_provenance_get_schema() + )] + async fn elf_admin_note_provenance_get( + &self, + mut params: JsonObject, + ) -> Result { + let note_id = support::take_required_string(&mut params, "note_id")?; + let path = format!("/v2/admin/notes/{note_id}/provenance"); + + self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await + } + + #[rmcp::tool( + name = "elf_admin_memory_history_get", + description = "Fetch chronological memory history for one note.", + input_schema = admin_memory_history_get_schema() + )] + async fn elf_admin_memory_history_get( + &self, + mut params: JsonObject, + ) -> Result { + let note_id = support::take_required_string(&mut params, "note_id")?; + let path = format!("/v2/admin/notes/{note_id}/history"); + + self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await + } + + #[rmcp::tool( + name = "elf_admin_trace_bundle_get", + description = "Fetch trace bundle for replay and diagnostics by trace_id.", + input_schema = admin_trace_bundle_get_schema() + )] + async fn elf_admin_trace_bundle_get( + &self, + mut params: JsonObject, + ) -> Result { + let trace_id = support::take_required_string(&mut params, "trace_id")?; + let path = format!("/v2/admin/traces/{trace_id}/bundle"); + + self.forward(HttpMethod::Get, &path, params, None).await + } + + #[rmcp::tool( + name = "elf_admin_events_ingestion_profiles_list", + description = "List latest ingestion profiles for add_event.", + input_schema = admin_ingestion_profiles_list_schema() + )] + async fn elf_admin_events_ingestion_profiles_list( + &self, + _params: JsonObject, + ) -> Result { + self.forward( + HttpMethod::Get, + "/v2/admin/events/ingestion-profiles", + JsonObject::new(), + None, + ) + .await + } + + #[rmcp::tool( + name = "elf_admin_events_ingestion_profiles_create", + description = "Create a new ingestion profile version for add_event.", + input_schema = admin_ingestion_profiles_create_schema() + )] + async fn elf_admin_events_ingestion_profiles_create( + &self, + params: JsonObject, + ) -> Result { + self.forward(HttpMethod::Post, "/v2/admin/events/ingestion-profiles", params, None).await + } + + #[rmcp::tool( + name = "elf_admin_events_ingestion_profile_get", + description = "Get a single ingestion profile by id/version for add_event.", + input_schema = admin_ingestion_profile_get_schema() + )] + async fn elf_admin_events_ingestion_profile_get( + &self, + mut params: JsonObject, + ) -> Result { + let profile_id = support::take_required_string(&mut params, "profile_id")?; + let path = format!("/v2/admin/events/ingestion-profiles/{profile_id}"); + + self.forward(HttpMethod::Get, &path, params, None).await + } + + #[rmcp::tool( + name = "elf_admin_events_ingestion_profile_versions_list", + description = "List all versions of one ingestion profile for add_event.", + input_schema = admin_ingestion_profile_versions_list_schema() + )] + async fn elf_admin_events_ingestion_profile_versions_list( + &self, + mut params: JsonObject, + ) -> Result { + let profile_id = support::take_required_string(&mut params, "profile_id")?; + let path = format!("/v2/admin/events/ingestion-profiles/{profile_id}/versions"); + + self.forward(HttpMethod::Get, &path, params, None).await + } + + #[rmcp::tool( + name = "elf_admin_events_ingestion_profile_default_get", + description = "Get the active default ingestion profile for add_event.", + input_schema = admin_ingestion_profile_default_get_schema() + )] + async fn elf_admin_events_ingestion_profile_default_get( + &self, + _params: JsonObject, + ) -> Result { + self.forward( + HttpMethod::Get, + "/v2/admin/events/ingestion-profiles/default", + JsonObject::new(), + None, + ) + .await + } + + #[rmcp::tool( + name = "elf_admin_events_ingestion_profile_default_set", + description = "Set the default ingestion profile for add_event.", + input_schema = admin_ingestion_profile_default_set_schema() + )] + pub(in crate::app::server) async fn elf_admin_events_ingestion_profile_default_set( + &self, + params: JsonObject, + ) -> Result { + self.forward(HttpMethod::Put, "/v2/admin/events/ingestion-profiles/default", params, None) + .await + } +} diff --git a/apps/elf-mcp/src/app/server/tools/docs.rs b/apps/elf-mcp/src/app/server/tools/docs.rs new file mode 100644 index 00000000..c10c7dd9 --- /dev/null +++ b/apps/elf-mcp/src/app/server/tools/docs.rs @@ -0,0 +1,71 @@ +use color_eyre::Result; +use rmcp::{ + ErrorData, + model::{CallToolResult, JsonObject}, +}; + +use crate::app::server::{ + ElfMcp, HttpMethod, + schemas::{docs_excerpts_get_schema, docs_get_schema, docs_put_schema, docs_search_l0_schema}, + support, +}; + +#[rmcp::tool_router(router = docs_tool_router, vis = "pub(in crate::app::server)")] +impl ElfMcp { + #[rmcp::tool( + name = "elf_docs_put", + description = "Store a document (evidence source) in ELF Doc Extension v1.", + input_schema = docs_put_schema() + )] + async fn elf_docs_put(&self, params: JsonObject) -> Result { + self.forward(HttpMethod::Post, "/v2/docs", params, None).await + } + + #[rmcp::tool( + name = "elf_docs_get", + description = "Fetch a single document's metadata by doc_id.", + input_schema = docs_get_schema() + )] + async fn elf_docs_get(&self, mut params: JsonObject) -> Result { + let doc_id = support::take_required_string(&mut params, "doc_id")?; + let path = format!("/v2/docs/{doc_id}"); + + self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await + } + + #[rmcp::tool( + name = "elf_docs_delete", + description = "Delete a Source Library document by doc_id and enqueue derived doc-vector removal.", + input_schema = docs_get_schema() + )] + async fn elf_docs_delete(&self, mut params: JsonObject) -> Result { + let doc_id = support::take_required_string(&mut params, "doc_id")?; + let path = format!("/v2/docs/{doc_id}"); + + self.forward(HttpMethod::Delete, &path, JsonObject::new(), None).await + } + + #[rmcp::tool( + name = "elf_docs_search_l0", + description = "Run a minimal Doc search (L0): chunk-level results with short snippets.", + input_schema = docs_search_l0_schema() + )] + async fn elf_docs_search_l0( + &self, + mut params: JsonObject, + ) -> Result { + // read_profile is part of the MCP server configuration and is not client-controlled. + let _ = support::take_optional_string(&mut params, "read_profile")?; + + self.forward(HttpMethod::Post, "/v2/docs/search/l0", params, None).await + } + + #[rmcp::tool( + name = "elf_docs_excerpts_get", + description = "Hydrate a verifiable excerpt (L1 or L2) from a stored document.", + input_schema = docs_excerpts_get_schema() + )] + async fn elf_docs_excerpts_get(&self, params: JsonObject) -> Result { + self.forward(HttpMethod::Post, "/v2/docs/excerpts", params, None).await + } +} diff --git a/docs/evidence/2026-06-23-local-agent-loop-drift-audit.md b/docs/evidence/2026-06-23-local-agent-loop-drift-audit.md index 9def741f..0d667840 100644 --- a/docs/evidence/2026-06-23-local-agent-loop-drift-audit.md +++ b/docs/evidence/2026-06-23-local-agent-loop-drift-audit.md @@ -17,7 +17,8 @@ code_refs: - scripts/local-agent-loop.sh - config/local/elf.docker.toml - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs + - apps/elf-mcp/src/app/server/tools/docs.rs related: - docs/runbook/agent-setup.md - docs/runbook/agent_skills_cookbook.md @@ -27,7 +28,8 @@ drift_watch: - scripts/local-agent-loop.sh - config/local/elf.docker.toml - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs + - apps/elf-mcp/src/app/server/tools/docs.rs --- # Local Agent Loop Drift Audit @@ -67,7 +69,8 @@ provider quality evidence. - `POST /v2/admin/consolidation/runs` - `POST /v2/admin/consolidation/proposals/{proposal_id}/review` - `POST /v2/admin/notes/{note_id}/corrections` -- `apps/elf-mcp/src/server.rs` exposes the agent-facing MCP tools: +- `apps/elf-mcp/src/app/server.rs` and + `apps/elf-mcp/src/app/server/tools/docs.rs` expose the agent-facing MCP tools: - `elf_docs_put` - `elf_notes_ingest` - `elf_searches_create` @@ -97,4 +100,5 @@ pass - `scripts/local-agent-loop.sh` - `config/local/elf.docker.toml` - `apps/elf-api/src/routes.rs` -- `apps/elf-mcp/src/server.rs` +- `apps/elf-mcp/src/app/server.rs` +- `apps/elf-mcp/src/app/server/tools/docs.rs` diff --git a/docs/evidence/2026-06-23-privacy-delete-export-boundaries-drift-audit.md b/docs/evidence/2026-06-23-privacy-delete-export-boundaries-drift-audit.md index af253d8c..2ba21ad9 100644 --- a/docs/evidence/2026-06-23-privacy-delete-export-boundaries-drift-audit.md +++ b/docs/evidence/2026-06-23-privacy-delete-export-boundaries-drift-audit.md @@ -16,7 +16,7 @@ source_refs: - docs/runbook/privacy_delete_export.md code_refs: - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server/tools/docs.rs - packages/elf-service/src/docs.rs - packages/elf-service/src/graph_query.rs - packages/elf-service/src/graph_report.rs @@ -39,7 +39,7 @@ drift_watch: - docs/spec/system_graph_memory_postgres_v1.md - docs/spec/system_knowledge_pages_v1.md - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server/tools/docs.rs - packages/elf-service/src/docs.rs - packages/elf-service/src/graph_query.rs - packages/elf-service/src/graph_report.rs @@ -79,7 +79,7 @@ Library, Knowledge Workspace, graph memory, and core service specs. - `apps/elf-api/src/routes.rs` exposes `DELETE /v2/docs/{doc_id}` through the public docs router and OpenAPI path list. -- `apps/elf-mcp/src/server.rs` exposes `elf_docs_delete` as a thin MCP forwarding +- `apps/elf-mcp/src/app/server/tools/docs.rs` exposes `elf_docs_delete` as a thin MCP forwarding tool with no policy logic. - `packages/elf-service/src/docs.rs` marks owned Source Library documents deleted and enqueues one doc-index `DELETE` outbox job per persisted chunk. diff --git a/docs/evidence/2026-06-27-work-journal-drift-audit.md b/docs/evidence/2026-06-27-work-journal-drift-audit.md index 56e44ced..ff364e82 100644 --- a/docs/evidence/2026-06-27-work-journal-drift-audit.md +++ b/docs/evidence/2026-06-27-work-journal-drift-audit.md @@ -19,7 +19,7 @@ code_refs: - packages/elf-storage/src/models.rs - sql/tables/042_work_journal_entries.sql - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs - packages/elf-service/tests/acceptance/work_journal.rs related: - docs/spec/system_work_journal_v1.md @@ -31,7 +31,7 @@ drift_watch: - packages/elf-storage/src/models.rs - sql/tables/042_work_journal_entries.sql - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs - docs/spec/system_work_journal_v1.md --- # Work Journal Drift Audit @@ -74,7 +74,7 @@ or benchmark competitor interpretation. - `POST /v2/work-journal/entries` - `GET /v2/work-journal/entries/{entry_id}` - `POST /v2/work-journal/readback` -- `apps/elf-mcp/src/server.rs` exposes: +- `apps/elf-mcp/src/app/server.rs` exposes: - `elf_work_journal_entry_create` - `elf_work_journal_entry_get` - `elf_work_journal_session_readback` @@ -114,5 +114,5 @@ pass - `packages/elf-storage/src/work_journal.rs` - `packages/elf-service/src/work_journal.rs` - `apps/elf-api/src/routes.rs` -- `apps/elf-mcp/src/server.rs` +- `apps/elf-mcp/src/app/server.rs` - `packages/elf-service/tests/acceptance/work_journal.rs` diff --git a/docs/evidence/benchmarking/2026-06-20-dreaming-review-queue-report.md b/docs/evidence/benchmarking/2026-06-20-dreaming-review-queue-report.md index 1975d05f..45a106b0 100644 --- a/docs/evidence/benchmarking/2026-06-20-dreaming-review-queue-report.md +++ b/docs/evidence/benchmarking/2026-06-20-dreaming-review-queue-report.md @@ -20,7 +20,7 @@ correction proposals. Inputs: `packages/elf-service/src/dreaming_review_queue.rs`, -`apps/elf-api/src/routes.rs`, `apps/elf-mcp/src/server.rs`, +`apps/elf-api/src/routes.rs`, `apps/elf-mcp/src/app/server.rs`, `docs/spec/system_consolidation_proposals_v1.md`, and `apps/elf-eval/fixtures/report_snapshots/2026-06-20-dreaming-review-queue-report.json`. diff --git a/docs/evidence/benchmarking/2026-06-20-recall-debug-panel-report.md b/docs/evidence/benchmarking/2026-06-20-recall-debug-panel-report.md index 4dd08d88..12d3baa7 100644 --- a/docs/evidence/benchmarking/2026-06-20-recall-debug-panel-report.md +++ b/docs/evidence/benchmarking/2026-06-20-recall-debug-panel-report.md @@ -20,7 +20,7 @@ Notes, Source Library documents, Knowledge Workspace pages, graph facts, and Dre review proposals. Inputs: `packages/elf-service/src/recall_debug.rs`, `apps/elf-api/src/routes.rs`, -`apps/elf-mcp/src/server.rs`, `docs/spec/system_recall_debug_panel_v1.md`, and +`apps/elf-mcp/src/app/server.rs`, `docs/spec/system_recall_debug_panel_v1.md`, and `apps/elf-eval/fixtures/report_snapshots/2026-06-20-recall-debug-panel-report.json`. ## Executive Judgment diff --git a/docs/runbook/agent-setup.md b/docs/runbook/agent-setup.md index b250ef1b..d803e754 100644 --- a/docs/runbook/agent-setup.md +++ b/docs/runbook/agent-setup.md @@ -23,7 +23,7 @@ drift_watch: - scripts/local-agent-loop.sh - config/local/elf.docker.toml - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs --- # Agent Setup Runbook diff --git a/docs/runbook/privacy_delete_export.md b/docs/runbook/privacy_delete_export.md index f5c044ec..b3494cf7 100644 --- a/docs/runbook/privacy_delete_export.md +++ b/docs/runbook/privacy_delete_export.md @@ -15,7 +15,8 @@ tags: source_refs: [] code_refs: - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs + - apps/elf-mcp/src/app/server/tools/docs.rs - packages/elf-service/src/delete.rs - packages/elf-service/src/docs.rs - packages/elf-service/src/graph_query.rs @@ -33,7 +34,8 @@ related: drift_watch: - docs/runbook/privacy_delete_export.md - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs + - apps/elf-mcp/src/app/server/tools/docs.rs - packages/elf-service/src/delete.rs - packages/elf-service/src/docs.rs - packages/elf-service/src/graph_query.rs diff --git a/docs/spec/system_doc_source_ref_v1.md b/docs/spec/system_doc_source_ref_v1.md index 19aec54f..d3b5a321 100644 --- a/docs/spec/system_doc_source_ref_v1.md +++ b/docs/spec/system_doc_source_ref_v1.md @@ -12,7 +12,7 @@ tags: - spec source_refs: [] code_refs: - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server/tools/docs.rs - packages/elf-service/src/docs.rs - packages/elf-service/src/knowledge.rs - packages/elf-storage/src/docs.rs diff --git a/docs/spec/system_recall_debug_panel_v1.md b/docs/spec/system_recall_debug_panel_v1.md index 4443a8c6..9ee0739b 100644 --- a/docs/spec/system_recall_debug_panel_v1.md +++ b/docs/spec/system_recall_debug_panel_v1.md @@ -16,7 +16,7 @@ source_refs: [] code_refs: - packages/elf-service/src/recall_debug.rs - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs related: - docs/spec/system_elf_memory_service_v2.md - docs/spec/system_doc_extension_v1_trajectory.md diff --git a/docs/spec/system_version_registry.md b/docs/spec/system_version_registry.md index 44ed05ba..3a82e5e5 100644 --- a/docs/spec/system_version_registry.md +++ b/docs/spec/system_version_registry.md @@ -120,7 +120,7 @@ This document is normative. When a new versioned identifier is introduced, it mu - Type: Filter parameters and required Qdrant payload/index requirements for `docs_search_l0` (HTTP/MCP). - Defined in: `docs/spec/system_doc_extension_v1_filters.md`. -- Consumers: `apps/elf-api/src/routes.rs`, `apps/elf-mcp/src/server.rs`, `packages/elf-service/src/docs.rs`. +- Consumers: `apps/elf-api/src/routes.rs`, `apps/elf-mcp/src/app/server/tools/docs.rs`, `packages/elf-service/src/docs.rs`. - Bump rule: Introduce `docs_search_filters/v2` only if accepted filter keys, value constraints, evaluation semantics, or required Qdrant filter/index fields become incompatible. @@ -247,7 +247,7 @@ This document is normative. When a new versioned identifier is introduced, it mu - Identifier: `search_filter_expr/v1`. - Type: JSON envelope schema for structured search filters (`filter` request payload on search endpoints). -- Defined in: `docs/spec/system_search_filter_expr_v1.md`, `apps/elf-api/src/routes.rs`, `apps/elf-mcp/src/server.rs`, `packages/elf-service/src/search.rs` (`SearchFilter`). +- Defined in: `docs/spec/system_search_filter_expr_v1.md`, `apps/elf-api/src/routes.rs`, `apps/elf-mcp/src/app/server.rs`, `packages/elf-service/src/search.rs` (`SearchFilter`). - Consumers: Search creation endpoints (`/v2/searches`, `/v2/admin/searches/raw`) and admin/observability surfaces. - Bump rule: Introduce `search_filter_expr/v2` only if filter field allowlist, operators, parsing limits, value typing, or parse error model become incompatible. diff --git a/docs/spec/system_work_journal_v1.md b/docs/spec/system_work_journal_v1.md index 12b74885..b898df9c 100644 --- a/docs/spec/system_work_journal_v1.md +++ b/docs/spec/system_work_journal_v1.md @@ -18,7 +18,7 @@ code_refs: - packages/elf-storage/src/work_journal.rs - sql/tables/042_work_journal_entries.sql - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs related: - docs/spec/system_elf_memory_service_v2.md - docs/spec/system_consolidation_proposals_v1.md @@ -27,7 +27,7 @@ drift_watch: - packages/elf-service/src/work_journal.rs - sql/tables/042_work_journal_entries.sql - apps/elf-api/src/routes.rs - - apps/elf-mcp/src/server.rs + - apps/elf-mcp/src/app/server.rs --- # Work Journal v1 Specification