1use crate::{MCPToolResult, get_memory_system};
4use anyhow::Result;
5use post_cortex_core::summary::SummaryGenerator;
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!(
170 "Analyzed {} entities (showing {})",
171 total_entities,
172 if truncated {
173 entity_limit
174 } else {
175 after_filter
176 }
177 ),
178 Some(result),
179 ))
180}
181
182pub async fn get_entity_network_view(
184 session_id: String,
185 center_entity: Option<String>,
186 max_entities: Option<usize>,
187 max_relationships: Option<usize>,
188) -> Result<MCPToolResult> {
189 let uuid =
190 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
191
192 let system = get_memory_system().await?;
193 let session_arc = system
194 .get_session(uuid)
195 .await
196 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
197
198 let session = session_arc.load();
199
200 let max_entities = max_entities.unwrap_or(50);
201 let max_relationships = max_relationships.unwrap_or(100);
202
203 let entities: Vec<serde_json::Value> = session
204 .entity_graph
205 .get_most_important_entities(max_entities)
206 .into_iter()
207 .map(|e| {
208 serde_json::json!({
209 "name": e.name,
210 "entity_type": format!("{:?}", e.entity_type),
211 "importance_score": e.importance_score,
212 "mention_count": e.mention_count
213 })
214 })
215 .collect();
216
217 let relationships: Vec<serde_json::Value> = if let Some(center) = ¢er_entity {
218 session
219 .entity_graph
220 .trace_entity_relationships(center, 2)
221 .into_iter()
222 .take(max_relationships)
223 .map(|(entity, rel_type, target)| {
224 serde_json::json!({
225 "from": entity,
226 "type": format!("{:?}", rel_type),
227 "to": target
228 })
229 })
230 .collect()
231 } else {
232 session
233 .entity_graph
234 .get_all_relationships()
235 .into_iter()
236 .take(max_relationships)
237 .map(|r| {
238 serde_json::json!({
239 "from": r.from_entity,
240 "type": format!("{:?}", r.relation_type),
241 "to": r.to_entity
242 })
243 })
244 .collect()
245 };
246
247 Ok(MCPToolResult::success(
248 format!(
249 "Network view: {} entities, {} relationships",
250 entities.len(),
251 relationships.len()
252 ),
253 Some(serde_json::json!({
254 "session_id": session_id,
255 "center_entity": center_entity,
256 "entities": entities,
257 "relationships": relationships
258 })),
259 ))
260}
261
262pub async fn get_session_statistics(session_id: String) -> Result<MCPToolResult> {
264 let uuid =
265 Uuid::parse_str(&session_id).map_err(|e| anyhow::anyhow!("Invalid session ID: {}", e))?;
266
267 let system = get_memory_system().await?;
268 let session_arc = system
269 .get_session(uuid)
270 .await
271 .map_err(|e| anyhow::anyhow!("Failed to load session: {}", e))?;
272
273 let session = session_arc.load();
274
275 let hot_updates = session.hot_context.len();
276 let warm_updates = session.warm_context.len();
277 let incremental_updates = session.incremental_updates.len();
278 let total_updates = hot_updates + warm_updates + incremental_updates;
279 let entity_count = session.entity_graph.entities.len();
280 let relationship_count = session.entity_graph.get_all_relationships().len();
281 let code_refs = session.code_references.len();
282 let change_history = session.change_history.len();
283
284 let first_update = session
285 .incremental_updates
286 .iter()
287 .min_by_key(|u| u.timestamp)
288 .map(|u| u.timestamp.to_rfc3339());
289 let last_update = session
290 .incremental_updates
291 .iter()
292 .max_by_key(|u| u.timestamp)
293 .map(|u| u.timestamp.to_rfc3339());
294
295 let duration = match (first_update.clone(), last_update.clone()) {
296 (Some(first), Some(last)) => {
297 let first_dt = chrono::DateTime::parse_from_rfc3339(&first).ok();
298 let last_dt = chrono::DateTime::parse_from_rfc3339(&last).ok();
299 match (first_dt, last_dt) {
300 (Some(f), Some(l)) => Some((l - f).num_minutes()),
301 _ => None,
302 }
303 }
304 _ => None,
305 };
306
307 Ok(MCPToolResult::success(
308 format!("Session statistics for {}", session_id),
309 Some(serde_json::json!({
310 "session_id": session_id,
311 "name": session.name(),
312 "description": session.description(),
313 "created_at": session.created_at().to_rfc3339(),
314 "last_updated": session.last_updated.to_rfc3339(),
315 "hot_updates": hot_updates,
316 "warm_updates": warm_updates,
317 "incremental_updates": incremental_updates,
318 "total_updates": total_updates,
319 "entity_count": entity_count,
320 "relationship_count": relationship_count,
321 "code_references": code_refs,
322 "change_history": change_history,
323 "first_update": first_update,
324 "last_update": last_update,
325 "duration_minutes": duration,
326 "vectorized_count": 0
327 })),
328 ))
329}
330
331pub async fn get_tool_catalog() -> Result<MCPToolResult> {
333 let catalog = serde_json::json!({
334 "total_tools": 26,
335 "categories": {
336 "Session Management": {
337 "description": "Tools for creating, loading, and managing conversation sessions",
338 "tool_count": 5,
339 "tools": [
340 {
341 "name": "create_session",
342 "description": "Start new conversation session with optional name/description",
343 "use_when": "Beginning a new project, conversation, or knowledge context"
344 },
345 {
346 "name": "load_session",
347 "description": "Resume existing session into active memory",
348 "use_when": "Continuing work on a previous conversation or project"
349 },
350 {
351 "name": "list_sessions",
352 "description": "View all sessions with metadata and statistics",
353 "use_when": "Finding available sessions or checking session details"
354 },
355 {
356 "name": "search_sessions",
357 "description": "Find sessions by name or description (text search)",
358 "use_when": "Looking for specific sessions when you know the name/topic"
359 },
360 {
361 "name": "update_session_metadata",
362 "description": "Change session name or description",
363 "use_when": "Renaming or updating session information"
364 }
365 ]
366 },
367 "Context Operations": {
368 "description": "Tools for adding and querying conversation knowledge",
369 "tool_count": 3,
370 "tools": [
371 {
372 "name": "update_conversation_context",
373 "description": "Add knowledge: QA, decisions, problems solved, code changes",
374 "use_when": "Storing information for future retrieval and learning",
375 "interaction_types": ["qa", "decision_made", "problem_solved", "code_change", "requirement_added", "concept_defined"]
376 },
377 {
378 "name": "query_conversation_context",
379 "description": "Search using entities, keywords, or structured queries",
380 "use_when": "Finding exact entities or keyword-based searches (faster than semantic)",
381 "query_types": ["find_related_entities", "get_entity_context", "search_updates", "get_most_important_entities"]
382 },
383 {
384 "name": "create_session_checkpoint",
385 "description": "Create snapshot of current session state",
386 "use_when": "Creating restore points before major changes"
387 }
388 ]
389 },
390 "Semantic Search": {
391 "description": "AI-powered conceptual search using embeddings (requires embeddings feature)",
392 "tool_count": 5,
393 "tools": [
394 {
395 "name": "semantic_search_session",
396 "description": "AI search within session - auto-loads and auto-vectorizes!",
397 "use_when": "Finding information by concept/meaning, not exact keywords",
398 "note": "Automatically vectorizes on first use - no manual setup needed"
399 },
400 {
401 "name": "semantic_search_global",
402 "description": "AI search across ALL sessions",
403 "use_when": "Finding related knowledge across multiple conversations"
404 },
405 {
406 "name": "find_related_content",
407 "description": "Discover connections between different sessions",
408 "use_when": "Finding similar discussions from other conversations"
409 },
410 {
411 "name": "vectorize_session",
412 "description": "Generate embeddings for semantic search (optional - auto-called)",
413 "use_when": "Rarely needed - semantic_search_session auto-vectorizes",
414 "note": "Only useful for batch processing or manual control"
415 },
416 {
417 "name": "get_vectorization_stats",
418 "description": "Check embedding statistics and performance metrics",
419 "use_when": "Debugging or monitoring semantic search system"
420 }
421 ]
422 },
423 "Analysis & Insights": {
424 "description": "Tools for extracting insights, summaries, and analyzing session data",
425 "tool_count": 7,
426 "tools": [
427 {
428 "name": "get_structured_summary",
429 "description": "Comprehensive summary with auto-compact for large sessions",
430 "use_when": "Getting overview of decisions, entities, questions, concepts",
431 "note": "Auto-compacts if response > 25K tokens - prevents MCP overflow"
432 },
433 {
434 "name": "get_key_decisions",
435 "description": "Timeline of decisions with confidence levels",
436 "use_when": "Reviewing architectural choices and their rationale"
437 },
438 {
439 "name": "get_key_insights",
440 "description": "Extract top insights from session data",
441 "use_when": "Quick overview of most important discoveries"
442 },
443 {
444 "name": "get_entity_importance_analysis",
445 "description": "Entity rankings with mention counts and relationships",
446 "use_when": "Understanding which concepts/technologies are most central"
447 },
448 {
449 "name": "get_entity_network_view",
450 "description": "Visualize entity relationships as network graph",
451 "use_when": "Understanding how concepts connect to each other"
452 },
453 {
454 "name": "get_session_statistics",
455 "description": "Session metrics: updates, entities, activity level, duration",
456 "use_when": "Checking session health and growth metrics"
457 },
458 {
459 "name": "get_tool_catalog",
460 "description": "View all 26 tools organized by category with usage guidance",
461 "use_when": "First time using post-cortex or discovering available tools",
462 "note": "Returns categories, workflows, tips, and most-used tools list"
463 }
464 ]
465 },
466 "Workspace Management": {
467 "description": "Tools for organizing related sessions (e.g., microservices, monorepo projects)",
468 "tool_count": 6,
469 "tools": [
470 {
471 "name": "create_workspace",
472 "description": "Create workspace to group related sessions",
473 "use_when": "Starting a multi-service project or organizing session groups"
474 },
475 {
476 "name": "get_workspace",
477 "description": "Retrieve workspace details including all sessions and metadata",
478 "use_when": "Viewing workspace configuration and session associations"
479 },
480 {
481 "name": "list_workspaces",
482 "description": "List all workspaces with session counts",
483 "use_when": "Finding available workspaces or checking workspace overview"
484 },
485 {
486 "name": "delete_workspace",
487 "description": "Delete workspace (sessions remain intact)",
488 "use_when": "Removing workspace organization without deleting sessions"
489 },
490 {
491 "name": "add_session_to_workspace",
492 "description": "Add session to workspace with role (primary/related/dependency/shared)",
493 "use_when": "Organizing session into workspace with specific relationship role"
494 },
495 {
496 "name": "remove_session_from_workspace",
497 "description": "Remove session from workspace",
498 "use_when": "Reorganizing sessions or removing from workspace group"
499 }
500 ]
501 }
502 },
503 "getting_started": [
504 "1. create_session → get session_id",
505 "2. update_conversation_context → add knowledge (qa, decisions, problems, code)",
506 "3. semantic_search_session → AI-powered search (auto-vectorizes on first use!)",
507 "4. get_structured_summary → comprehensive overview"
508 ],
509 "most_used_tools": [
510 "create_session",
511 "load_session",
512 "update_conversation_context",
513 "semantic_search_session",
514 "get_structured_summary"
515 ],
516 "tips": [
517 "Semantic search auto-vectorizes - no manual vectorize_session needed!",
518 "Use semantic_search for concepts, query_conversation_context for keywords",
519 "get_structured_summary has auto-compact - safe for large sessions",
520 "Similarity scores: 0.65-0.75 = excellent, 0.45-0.55 = good, <0.30 = weak",
521 "All sessions persist across Claude Code conversations"
522 ]
523 });
524
525 Ok(MCPToolResult::success(
526 "Retrieved tool catalog with 26 tools across 5 categories".to_string(),
527 Some(catalog),
528 ))
529}