1use post_cortex_core::summary::SummaryGenerator;
4use crate::{get_memory_system, MCPToolResult};
5use anyhow::Result;
6use uuid::Uuid;
7
8pub async fn get_structured_summary(
10 session_id: String,
11 decisions_limit: Option<usize>,
12 entities_limit: Option<usize>,
13 questions_limit: Option<usize>,
14 concepts_limit: Option<usize>,
15 min_confidence: Option<f32>,
16 compact: Option<bool>,
17) -> Result<MCPToolResult> {
18 let uuid =
19 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
20
21 let system = get_memory_system().await?;
22 let session_arc = system
23 .get_session(uuid)
24 .await
25 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
26
27 let session = session_arc.load();
28
29 use post_cortex_core::summary::SummaryOptions;
30 let user_requested_compact = compact.unwrap_or(false);
31 const MAX_TOKENS: usize = 50_000;
32
33 let (estimated_tokens, should_compact) =
34 SummaryGenerator::estimate_summary_size(&session, MAX_TOKENS);
35 let auto_compacted = !user_requested_compact && should_compact;
36
37 if auto_compacted {
38 log::info!(
39 "Pre-estimated {} tokens > {} max. Using compact mode directly.",
40 estimated_tokens,
41 MAX_TOKENS
42 );
43 }
44
45 let options = if user_requested_compact || auto_compacted {
46 SummaryOptions::compact()
47 } else {
48 SummaryOptions {
49 decisions_limit,
50 entities_limit,
51 questions_limit,
52 concepts_limit,
53 min_confidence,
54 compact: false,
55 }
56 };
57
58 let summary = SummaryGenerator::generate_structured_summary_filtered(&session, &options);
59
60 let message = if user_requested_compact {
61 "Generated compact structured summary".to_string()
62 } else if auto_compacted {
63 format!(
64 "Auto-compacted summary (was too large for MCP). Showing: {} decisions, {} entities, {} questions, {} concepts",
65 summary.key_decisions.len(),
66 summary.entity_summaries.len(),
67 summary.open_questions.len(),
68 summary.key_concepts.len()
69 )
70 } else {
71 format!(
72 "Generated structured summary (decisions: {}, entities: {}, questions: {}, concepts: {})",
73 summary.key_decisions.len(),
74 summary.entity_summaries.len(),
75 summary.open_questions.len(),
76 summary.key_concepts.len()
77 )
78 };
79
80 Ok(MCPToolResult::success(
81 message,
82 Some(serde_json::to_value(summary)?),
83 ))
84}
85
86pub async fn get_key_decisions(session_id: String) -> Result<MCPToolResult> {
88 let uuid =
89 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
90
91 let system = get_memory_system().await?;
92 let session_arc = system
93 .get_session(uuid)
94 .await
95 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
96
97 let session = session_arc.load();
98 let decisions = SummaryGenerator::extract_decision_timeline(&session);
99
100 Ok(MCPToolResult::success(
101 format!("Found {} key decisions", decisions.len()),
102 Some(serde_json::to_value(decisions)?),
103 ))
104}
105
106pub async fn get_key_insights(session_id: String, limit: Option<usize>) -> Result<MCPToolResult> {
108 let uuid =
109 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
110
111 let system = get_memory_system().await?;
112 let session_arc = system
113 .get_session(uuid)
114 .await
115 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
116
117 let session = session_arc.load();
118 let insights = SummaryGenerator::extract_key_insights(&session, limit.unwrap_or(5));
119
120 Ok(MCPToolResult::success(
121 format!("Found {} key insights", insights.len()),
122 Some(serde_json::to_value(insights)?),
123 ))
124}
125
126pub async fn get_entity_importance_analysis(
128 session_id: String,
129 limit: Option<usize>,
130 min_importance: Option<f32>,
131) -> Result<MCPToolResult> {
132 let uuid =
133 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
134
135 let system = get_memory_system().await?;
136 let session_arc = system
137 .get_session(uuid)
138 .await
139 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
140
141 let session = session_arc.load();
142 let mut analysis = session.entity_graph.analyze_entity_importance();
143
144 let total_entities = analysis.len();
145
146 if let Some(min_imp) = min_importance {
147 analysis.retain(|entity| entity.importance_score >= min_imp);
148 }
149
150 let after_filter = analysis.len();
151
152 let entity_limit = limit.unwrap_or(100);
153 let truncated = analysis.len() > entity_limit;
154 if truncated {
155 analysis.truncate(entity_limit);
156 }
157
158 let result = serde_json::json!({
159 "entities": analysis,
160 "pagination": {
161 "total_entities": total_entities,
162 "after_filter": after_filter,
163 "shown": if truncated { entity_limit } else { after_filter },
164 "truncated": truncated
165 }
166 });
167
168 Ok(MCPToolResult::success(
169 format!("Analyzed {} entities (showing {})", total_entities, if truncated { entity_limit } else { after_filter }),
170 Some(result),
171 ))
172}
173
174pub async fn get_entity_network_view(
176 session_id: String,
177 center_entity: Option<String>,
178 max_entities: Option<usize>,
179 max_relationships: Option<usize>,
180) -> Result<MCPToolResult> {
181 let uuid =
182 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
183
184 let system = get_memory_system().await?;
185 let session_arc = system
186 .get_session(uuid)
187 .await
188 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
189
190 let session = session_arc.load();
191
192 let max_entities = max_entities.unwrap_or(50);
193 let max_relationships = max_relationships.unwrap_or(100);
194
195 let entities: Vec<serde_json::Value> = session
196 .entity_graph
197 .get_most_important_entities(max_entities)
198 .into_iter()
199 .map(|e| {
200 serde_json::json!({
201 "name": e.name,
202 "entity_type": format!("{:?}", e.entity_type),
203 "importance_score": e.importance_score,
204 "mention_count": e.mention_count
205 })
206 })
207 .collect();
208
209 let relationships: Vec<serde_json::Value> = if let Some(center) = ¢er_entity {
210 session
211 .entity_graph
212 .trace_entity_relationships(center, 2)
213 .into_iter()
214 .take(max_relationships)
215 .map(|(entity, rel_type, target)| {
216 serde_json::json!({
217 "from": entity,
218 "type": format!("{:?}", rel_type),
219 "to": target
220 })
221 })
222 .collect()
223 } else {
224 session
225 .entity_graph
226 .get_all_relationships()
227 .into_iter()
228 .take(max_relationships)
229 .map(|r| {
230 serde_json::json!({
231 "from": r.from_entity,
232 "type": format!("{:?}", r.relation_type),
233 "to": r.to_entity
234 })
235 })
236 .collect()
237 };
238
239 Ok(MCPToolResult::success(
240 format!(
241 "Network view: {} entities, {} relationships",
242 entities.len(),
243 relationships.len()
244 ),
245 Some(serde_json::json!({
246 "session_id": session_id,
247 "center_entity": center_entity,
248 "entities": entities,
249 "relationships": relationships
250 })),
251 ))
252}
253
254pub async fn get_session_statistics(session_id: String) -> Result<MCPToolResult> {
256 let uuid =
257 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
258
259 let system = get_memory_system().await?;
260 let session_arc = system
261 .get_session(uuid)
262 .await
263 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
264
265 let session = session_arc.load();
266
267 let hot_updates = session.hot_context.len();
268 let warm_updates = session.warm_context.len();
269 let incremental_updates = session.incremental_updates.len();
270 let total_updates = hot_updates + warm_updates + incremental_updates;
271 let entity_count = session.entity_graph.entities.len();
272 let relationship_count = session.entity_graph.get_all_relationships().len();
273 let code_refs = session.code_references.len();
274 let change_history = session.change_history.len();
275
276 let first_update = session
277 .incremental_updates
278 .iter()
279 .min_by_key(|u| u.timestamp)
280 .map(|u| u.timestamp.to_rfc3339());
281 let last_update = session
282 .incremental_updates
283 .iter()
284 .max_by_key(|u| u.timestamp)
285 .map(|u| u.timestamp.to_rfc3339());
286
287 let duration = match (first_update.clone(), last_update.clone()) {
288 (Some(first), Some(last)) => {
289 let first_dt = chrono::DateTime::parse_from_rfc3339(&first).ok();
290 let last_dt = chrono::DateTime::parse_from_rfc3339(&last).ok();
291 match (first_dt, last_dt) {
292 (Some(f), Some(l)) => Some((l - f).num_minutes()),
293 _ => None,
294 }
295 }
296 _ => None,
297 };
298
299 Ok(MCPToolResult::success(
300 format!("Session statistics for {}", session_id),
301 Some(serde_json::json!({
302 "session_id": session_id,
303 "name": session.name(),
304 "description": session.description(),
305 "created_at": session.created_at().to_rfc3339(),
306 "last_updated": session.last_updated.to_rfc3339(),
307 "hot_updates": hot_updates,
308 "warm_updates": warm_updates,
309 "incremental_updates": incremental_updates,
310 "total_updates": total_updates,
311 "entity_count": entity_count,
312 "relationship_count": relationship_count,
313 "code_references": code_refs,
314 "change_history": change_history,
315 "first_update": first_update,
316 "last_update": last_update,
317 "duration_minutes": duration,
318 "vectorized_count": 0
319 })),
320 ))
321}
322
323pub async fn get_tool_catalog() -> Result<MCPToolResult> {
325 let catalog = serde_json::json!({
326 "total_tools": 26,
327 "categories": {
328 "Session Management": {
329 "description": "Tools for creating, loading, and managing conversation sessions",
330 "tool_count": 5,
331 "tools": [
332 {
333 "name": "create_session",
334 "description": "Start new conversation session with optional name/description",
335 "use_when": "Beginning a new project, conversation, or knowledge context"
336 },
337 {
338 "name": "load_session",
339 "description": "Resume existing session into active memory",
340 "use_when": "Continuing work on a previous conversation or project"
341 },
342 {
343 "name": "list_sessions",
344 "description": "View all sessions with metadata and statistics",
345 "use_when": "Finding available sessions or checking session details"
346 },
347 {
348 "name": "search_sessions",
349 "description": "Find sessions by name or description (text search)",
350 "use_when": "Looking for specific sessions when you know the name/topic"
351 },
352 {
353 "name": "update_session_metadata",
354 "description": "Change session name or description",
355 "use_when": "Renaming or updating session information"
356 }
357 ]
358 },
359 "Context Operations": {
360 "description": "Tools for adding and querying conversation knowledge",
361 "tool_count": 3,
362 "tools": [
363 {
364 "name": "update_conversation_context",
365 "description": "Add knowledge: QA, decisions, problems solved, code changes",
366 "use_when": "Storing information for future retrieval and learning",
367 "interaction_types": ["qa", "decision_made", "problem_solved", "code_change", "requirement_added", "concept_defined"]
368 },
369 {
370 "name": "query_conversation_context",
371 "description": "Search using entities, keywords, or structured queries",
372 "use_when": "Finding exact entities or keyword-based searches (faster than semantic)",
373 "query_types": ["find_related_entities", "get_entity_context", "search_updates", "get_most_important_entities"]
374 },
375 {
376 "name": "create_session_checkpoint",
377 "description": "Create snapshot of current session state",
378 "use_when": "Creating restore points before major changes"
379 }
380 ]
381 },
382 "Semantic Search": {
383 "description": "AI-powered conceptual search using embeddings (requires embeddings feature)",
384 "tool_count": 5,
385 "tools": [
386 {
387 "name": "semantic_search_session",
388 "description": "AI search within session - auto-loads and auto-vectorizes!",
389 "use_when": "Finding information by concept/meaning, not exact keywords",
390 "note": "Automatically vectorizes on first use - no manual setup needed"
391 },
392 {
393 "name": "semantic_search_global",
394 "description": "AI search across ALL sessions",
395 "use_when": "Finding related knowledge across multiple conversations"
396 },
397 {
398 "name": "find_related_content",
399 "description": "Discover connections between different sessions",
400 "use_when": "Finding similar discussions from other conversations"
401 },
402 {
403 "name": "vectorize_session",
404 "description": "Generate embeddings for semantic search (optional - auto-called)",
405 "use_when": "Rarely needed - semantic_search_session auto-vectorizes",
406 "note": "Only useful for batch processing or manual control"
407 },
408 {
409 "name": "get_vectorization_stats",
410 "description": "Check embedding statistics and performance metrics",
411 "use_when": "Debugging or monitoring semantic search system"
412 }
413 ]
414 },
415 "Analysis & Insights": {
416 "description": "Tools for extracting insights, summaries, and analyzing session data",
417 "tool_count": 7,
418 "tools": [
419 {
420 "name": "get_structured_summary",
421 "description": "Comprehensive summary with auto-compact for large sessions",
422 "use_when": "Getting overview of decisions, entities, questions, concepts",
423 "note": "Auto-compacts if response > 25K tokens - prevents MCP overflow"
424 },
425 {
426 "name": "get_key_decisions",
427 "description": "Timeline of decisions with confidence levels",
428 "use_when": "Reviewing architectural choices and their rationale"
429 },
430 {
431 "name": "get_key_insights",
432 "description": "Extract top insights from session data",
433 "use_when": "Quick overview of most important discoveries"
434 },
435 {
436 "name": "get_entity_importance_analysis",
437 "description": "Entity rankings with mention counts and relationships",
438 "use_when": "Understanding which concepts/technologies are most central"
439 },
440 {
441 "name": "get_entity_network_view",
442 "description": "Visualize entity relationships as network graph",
443 "use_when": "Understanding how concepts connect to each other"
444 },
445 {
446 "name": "get_session_statistics",
447 "description": "Session metrics: updates, entities, activity level, duration",
448 "use_when": "Checking session health and growth metrics"
449 },
450 {
451 "name": "get_tool_catalog",
452 "description": "View all 26 tools organized by category with usage guidance",
453 "use_when": "First time using post-cortex or discovering available tools",
454 "note": "Returns categories, workflows, tips, and most-used tools list"
455 }
456 ]
457 },
458 "Workspace Management": {
459 "description": "Tools for organizing related sessions (e.g., microservices, monorepo projects)",
460 "tool_count": 6,
461 "tools": [
462 {
463 "name": "create_workspace",
464 "description": "Create workspace to group related sessions",
465 "use_when": "Starting a multi-service project or organizing session groups"
466 },
467 {
468 "name": "get_workspace",
469 "description": "Retrieve workspace details including all sessions and metadata",
470 "use_when": "Viewing workspace configuration and session associations"
471 },
472 {
473 "name": "list_workspaces",
474 "description": "List all workspaces with session counts",
475 "use_when": "Finding available workspaces or checking workspace overview"
476 },
477 {
478 "name": "delete_workspace",
479 "description": "Delete workspace (sessions remain intact)",
480 "use_when": "Removing workspace organization without deleting sessions"
481 },
482 {
483 "name": "add_session_to_workspace",
484 "description": "Add session to workspace with role (primary/related/dependency/shared)",
485 "use_when": "Organizing session into workspace with specific relationship role"
486 },
487 {
488 "name": "remove_session_from_workspace",
489 "description": "Remove session from workspace",
490 "use_when": "Reorganizing sessions or removing from workspace group"
491 }
492 ]
493 }
494 },
495 "getting_started": [
496 "1. create_session → get session_id",
497 "2. update_conversation_context → add knowledge (qa, decisions, problems, code)",
498 "3. semantic_search_session → AI-powered search (auto-vectorizes on first use!)",
499 "4. get_structured_summary → comprehensive overview"
500 ],
501 "most_used_tools": [
502 "create_session",
503 "load_session",
504 "update_conversation_context",
505 "semantic_search_session",
506 "get_structured_summary"
507 ],
508 "tips": [
509 "Semantic search auto-vectorizes - no manual vectorize_session needed!",
510 "Use semantic_search for concepts, query_conversation_context for keywords",
511 "get_structured_summary has auto-compact - safe for large sessions",
512 "Similarity scores: 0.65-0.75 = excellent, 0.45-0.55 = good, <0.30 = weak",
513 "All sessions persist across Claude Code conversations"
514 ]
515 });
516
517 Ok(MCPToolResult::success(
518 "Retrieved tool catalog with 26 tools across 5 categories".to_string(),
519 Some(catalog),
520 ))
521}