kodegen_tools_introspection 0.2.2

KODEGEN.ᴀɪ: Memory-efficient, Blazing-Fast, MCP tools for code generation agents.
Documentation
use kodegen_mcp_tool::Tool;
use kodegen_mcp_tool::error::McpError;
use kodegen_mcp_tool::tool_history;
use kodegen_mcp_schema::introspection::{InspectToolCallsArgs, InspectToolCallsPromptArgs, INSPECT_TOOL_CALLS};
use rmcp::model::{Content, PromptArgument, PromptMessage, PromptMessageContent, PromptMessageRole};
use serde_json::json;

// ============================================================================
// TOOL STRUCT
// ============================================================================

#[derive(Clone, Default)]
pub struct InspectToolCallsTool;

impl InspectToolCallsTool {
    #[must_use]
    pub fn new() -> Self {
        Self
    }
}

// ============================================================================
// TOOL IMPLEMENTATION
// ============================================================================

impl Tool for InspectToolCallsTool {
    type Args = InspectToolCallsArgs;
    type PromptArgs = InspectToolCallsPromptArgs;

    fn name() -> &'static str {
        INSPECT_TOOL_CALLS
    }

    fn description() -> &'static str {
        "Get recent tool call history with their arguments and outputs. \
         Returns chronological list of tool calls made during this session. \
         Supports pagination via offset parameter (negative for tail behavior).\n\n\
         Useful for:\n\
         - Onboarding new chats about work already done\n\
         - Recovering context after chat history loss\n\
         - Debugging tool call sequences\n\
         - Navigating large tool histories with pagination\n\n\
         Note: Does not track its own calls or other meta/query tools. \
         History kept in memory (last 1000 calls, persisted to disk)."
    }

    fn read_only() -> bool {
        true
    }

    fn destructive() -> bool {
        false
    }

    fn idempotent() -> bool {
        true
    }

    fn open_world() -> bool {
        false
    }

    async fn execute(&self, args: Self::Args) -> Result<Vec<Content>, McpError> {
        let history = tool_history::get_global_history()
            .ok_or_else(|| McpError::Other(anyhow::anyhow!("Tool history not initialized")))?;

        let calls = history
            .get_recent_calls(
                args.max_results,
                args.offset,
                args.tool_name.as_deref(),
                args.since.as_deref(),
            )
            .await;

        let stats = history.get_stats().await;

        let mut contents = Vec::new();

        // Content 1: Terminal formatted summary
        let summary = if calls.is_empty() {
            format!(
                "📝 Tool Call History\n\n\
                 No tool calls found matching your criteria.\n\n\
                 Total entries in memory: {}",
                stats.total_entries
            )
        } else {
            let mut output = String::new();
            output.push_str(&format!(
                "📝 Tool Call History\n\n\
                 Results: {} of {} total in memory\n",
                calls.len(),
                stats.total_entries
            ));
            
            if let Some(ref tool_name) = args.tool_name {
                output.push_str(&format!("Filtered by tool: {}\n", tool_name));
            }
            
            if let Some(ref since) = args.since {
                output.push_str(&format!("Since: {}\n", since));
            }
            
            output.push_str("\n📋 Recent Calls:\n\n");
            
            // Show first 5 calls in detail
            for (i, call) in calls.iter().take(5).enumerate() {
                output.push_str(&format!(
                    "{}. {} ({}ms)\n   Time: {}\n   Args: {}\n\n",
                    i + 1,
                    call.tool_name,
                    call.duration_ms.unwrap_or(0),
                    call.timestamp,
                    serde_json::to_string(&call.arguments).unwrap_or_else(|_| "{}".to_string())
                ));
            }
            
            if calls.len() > 5 {
                output.push_str(&format!("... and {} more calls (see JSON for full list)\n", calls.len() - 5));
            }
            
            output
        };
        
        contents.push(Content::text(summary));

        // Content 2: JSON metadata for machine parsing
        let metadata = json!({
            "success": true,
            "total_entries_in_memory": stats.total_entries,
            "returned_count": calls.len(),
            "filter_tool_name": args.tool_name,
            "filter_since": args.since,
            "offset": args.offset,
            "max_results": args.max_results,
            "calls": calls
        });
        
        let json_str = serde_json::to_string_pretty(&metadata)
            .unwrap_or_else(|_| "{}".to_string());
        contents.push(Content::text(json_str));

        Ok(contents)
    }

    fn prompt_arguments() -> Vec<PromptArgument> {
        vec![]
    }

    async fn prompt(&self, _args: Self::PromptArgs) -> Result<Vec<PromptMessage>, McpError> {
        Ok(vec![
            PromptMessage {
                role: PromptMessageRole::User,
                content: PromptMessageContent::text(
                    "How do I use inspect_tool_calls to see what work has been done?",
                ),
            },
            PromptMessage {
                role: PromptMessageRole::Assistant,
                content: PromptMessageContent::text(
                    "The inspect_tool_calls tool helps you understand what tools have been \
                     executed and what they did. This is especially useful when:\n\n\
                     1. **New chat context**: You join a new chat and want to understand what \
                     work was already done\n\n\
                     2. **Debugging**: You want to trace the sequence of operations that led \
                     to the current state\n\n\
                     3. **Learning**: You want to see how tools were used together to accomplish \
                     a task\n\n\
                     Usage examples:\n\n\
                     ```\n\
                     # Get first 50 tool calls (default)\n\
                     inspect_tool_calls({})\n\n\
                     # Get first 100 calls\n\
                     inspect_tool_calls({ max_results: 100 })\n\n\
                     # Get calls 50-99 (pagination)\n\
                     inspect_tool_calls({ offset: 50, max_results: 50 })\n\n\
                     # Get last 20 calls (most recent)\n\
                     inspect_tool_calls({ offset: -20 })\n\n\
                     # Get last 10 read_file calls\n\
                     inspect_tool_calls({ tool_name: \"read_file\", offset: -10 })\n\n\
                     # Get only read_file calls\n\
                     inspect_tool_calls({ tool_name: \"read_file\" })\n\n\
                     # Get calls since a specific timestamp\n\
                     inspect_tool_calls({ since: \"2024-10-12T20:00:00Z\" })\n\
                     ```\n\n\
                     The response includes:\n\
                     - Timestamp of each call\n\
                     - Tool name\n\
                     - Arguments passed\n\
                     - Output received\n\
                     - Execution duration in milliseconds\n\n\
                     Note: History is kept in memory (last 1000 calls) and persisted to \
                     ~/.config/kodegen-mcp/tool-history.jsonl for durability across restarts.",
                ),
            },
        ])
    }
}